Compare commits

...

48 Commits

Author SHA1 Message Date
8258f21f7b Remove references to legacy couch adapter 2020-07-23 17:47:00 -07:00
44bfcf33ef Removed placeholder provider 2020-07-23 17:45:49 -07:00
669415d362 Removed commented code 2020-07-23 17:45:01 -07:00
8601ec441f Remove comments 2020-07-23 17:44:05 -07:00
9a57a20404 Use new couch provider 2020-07-23 17:43:10 -07:00
1a3bff9813 Fixed some issues in CouchObjectProvider 2020-07-23 17:39:34 -07:00
baa5f21640 Added legacy persistence service adapter 2020-07-23 17:39:02 -07:00
af9dceee3c [WIP] CouchDB object provider 2020-07-23 16:02:10 -07:00
41138a1731 Merge pull request #3205 from nasa/data-dropout-fixes
### Reviewer Checklist

1. Changes appear to address issue? Y
2. Appropriate unit tests included? Y
3. Code style and in-line documentation are appropriate? Y
4. Commit messages meet standards? Y
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue) Y
2020-07-21 10:08:31 -07:00
a54a2f8f84 Merge branch 'master' into data-dropout-fixes 2020-07-21 10:01:01 -07:00
5bbe710552 Merge pull request #3215 from nasa/circle-ci-chromeheadless-test-failure
Removing use of ChromeHeadless and using FirefoxHeadless for Circle CI builds
2020-07-21 09:53:40 -07:00
f2d34d7c33 For the short term, removing use of ChromeHeadless and using FirefoxHeadless instead. (added npm dependency)
Also increasing browserNoActivityTimeout to 90000

Resolves #3214
2020-07-20 15:12:53 -07:00
8fa1770885 Merge branch 'master' into data-dropout-fixes 2020-07-20 15:01:11 -07:00
7221dc1ac6 make clear data indicator a configurable option (#3206) 2020-07-17 16:48:14 -07:00
25bb9939d6 Merge pull request #3193 from nasa/fix-safari-3192
Fix Safari display issues for #3192
2020-07-17 15:59:38 -07:00
e7e12504f2 Merge branch 'master' into fix-safari-3192 2020-07-17 15:19:32 -07:00
63bf856d89 Enable persistence operations from Object Providers (#3200)
* Implement 'save' method in Object API

* Refactor legacy persistence code to work with new save object API

* Added 'isPersistable' check to object API

* Fixed incompatibility between object API changes and composition policies

* Make save method private

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-07-17 09:58:03 -07:00
e3dcd51f8d Disallow editor Edit mode when object is locked (#3208)
* Don't allow editor edit if object is locked

* Adds log statement

* Use capture phase for onDragOver

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-07-17 09:23:51 -07:00
cb63f4eca1 Fix is-missing layout problem #3194 (#3195)
- Fixes related to `is-missing` including fixes for Display Layout
alphanumeric views and Tabs view tabs;
2020-07-16 12:43:37 -07:00
3f60c3c0f1 Merge branch 'master' into data-dropout-fixes 2020-07-16 11:55:02 -07:00
16bb22e834 Added regression test 2020-07-16 11:50:03 -07:00
b1467548da Fix Safari display issues #3192
- Tweaks to fix `c-tab` elements, fix clip-path for webkit;
- Fix Notebook Snapshots header;
2020-07-14 23:40:42 -07:00
baa7c0bc58 Fix Safari display issues #3192
- Tweak to Status area indicator hover bubbles;
2020-07-14 21:51:22 -07:00
73b81e38e7 Fix Safari display issues #3192
- Fix collapsed Status area indicators width problem;
2020-07-14 19:22:18 -07:00
8b088b7a2c Fix Safari display issues #3192
- Fix Status area indicators width problem;
- Also fixes collapsing-status-area-indicator-bubbles transition problem
 as well!;
2020-07-14 18:53:30 -07:00
894da25461 Fix Safari display issues #3192
- Fix Inspector `__content` to properly use flex column layout;
- Change `u-angular-object-view-wrapper` to `display: contents`;
- Fix `gl-plot` to properly use `flex: 1 1 auto` instead of width and
height;
2020-07-14 18:42:52 -07:00
87d63806b9 [Plots] Better Pan/Marquee handling (#3165)
* check for alt key pressed on mouse events

* allows for release of alt key during drag

* eliminates non-browser event states like alt-tab switching apps in windows

* do not listen to plot mouse events on browser context (ctrl) click

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-07-13 12:39:37 -07:00
f0e7f8cfc0 Fix incorrect property inspection (#3180)
- Fixed Folder grid and list views;
- Fixed plot collapsed and expanded legends;

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-07-13 11:36:21 -07:00
db597e1e93 [Tabs View] Fix tab not being added (#3160) 2020-07-13 10:24:22 -07:00
98db273f5d Remove unsubscribe callback 2020-07-10 15:10:33 -07:00
8a6f944655 Missing items (#3125)
* Missing objects styling WIP

- Grabbing prior work from `missing-items` branch;

* Missing objects styling WIP

- Grabbing prior work on hover and missing theme constants from
`missing-items` branch;
- Refined theme constants values;
- Renamed relevant mixins and classes from "isUnknown" to "isMissing";
- Applied new hover and missing/unknown styling to Folder-view grid
items;

* Missing objects styling WIP

- Significant refinements and additions to `is-missing`;
- Normalize object type icons as a markup `*__type-icon` to support
styling and positioning of `is-missing__indicator` as a markup element;
- Application to tree items, l-browse-bar in main view, c-object-label,
grid view;
- Change hover approach in grid-items and tree to use filters;

* Missing objects styling WIP

- Styles added to object-name component in Inspector, markup simplified;
- Styles added to Tabs view;

* Missing objects styling WIP

- Simplified and consolidated `is-missing` approach into
`.c-object-label` class;
- Modded `.c-object-label` class to use flex 1 1 auto, instead of 0 1
auto - be on the outlook for regression problems!;
- TODO: wire up `is-missing` for real and Folder List view;

* Missing objects styling WIP

- Added `is-missing` styling to Folder list view;
- Cleanups, simplification and normalization with tree items in
list-item and list-view.scss;
- Using `c-object-label` now in Folder list view;
- Removed too-broad `<a>` color definition in table.scss;

* Missing objects styling WIP

- `is-missing` added to layout frames, with support for hidden
frames and telemetry views.
- Further styles enhancement;
- Continued added wiring points into markup;

* Missing objects styling WIP

- `is-missing` added to mct-plot;
- Significant improvements for cursor lock indicators in plots;

* Missing objects styling WIP

- Plot legend fixes, added overflow scrolling for collapsed and expanded
 legends;
- Removed conmmented code;

* Wire up 'is-missing'

- Added property checks on domainObject for status 'missing';

* Fix linting issues

* remove carat from eslint package

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-07-10 15:08:14 -07:00
bacad24811 Delete telemetry cache only when count reaches 0 2020-07-10 13:27:30 -07:00
8cc58946cf Remove Karma Report that was accidentally checked in (#3097)
* remove karma report

* add report.*.json to gitignore

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2020-07-10 12:47:52 -07:00
3338bc1000 upgrade painterro to +1.00.35 (#3172) 2020-07-09 13:41:52 -07:00
80c20b3d05 [Plots] Allow user to change marker style (#3135)
* lineWidth is not supported in modern browsers

* allow marker shape selection in plot inspector

add shapes for canvas2d - point, cross, star

* change line style to line method in anticipation of adding true line style attribute

* add canvas2d circle marker shape

* allow point shapes for webGL plots

add circle shape

* refactor for clarity

* refactor shape to shape code

* add missing semi-colon

* helper function for marker options display in inspector

refactor for clarity

* access correct file

* add diamond marker shape

* add triangle shape to canvas

* add webgl draw triangle marker shape

* refactor fragment shader for clarity
2020-07-09 13:14:32 -07:00
0d9558b891 Merge pull request #3162 from nasa/display-layout-fix-3161
Fixes issue created when removing Lodash function
2020-07-07 16:54:54 -07:00
c29c3c386f fix issue created by lodash upgrade 2020-07-07 15:39:21 -07:00
9ceb3c5b1e Merge pull request #3130 from nasa/display-layout-fix-3128
[Display Layouts] Prevent duplicate from being added when composition 'add' is fired
2020-07-06 11:36:25 -07:00
bee3a9eedf Merge branch 'master' into display-layout-fix-3128 2020-07-02 10:48:53 -07:00
e515d19acd Merge pull request #3144 from nasa/switching-type-error
Fix for non working switch from alpha to tables and plots
2020-07-02 10:38:01 -07:00
dd13efe065 Merge branch 'master' into switching-type-error 2020-07-02 10:26:13 -07:00
b5dfbe268c Merge pull request #3146 from nasa/new-folder-action-fix-07012020
Added unnamed folder and made it required
2020-07-01 16:48:10 -07:00
a9b9107cc3 change icon and action name 2020-07-01 16:30:03 -07:00
cfda4e4214 added unnamed folder and required 2020-07-01 16:20:33 -07:00
0a657de4b2 Fix for non working switch from alpha to tables 2020-07-01 16:09:05 -07:00
8153edb9cb Update any/all criterion when telemetry is removed (#3138)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-07-01 12:22:47 -07:00
dadb6120c2 fix lint error 2020-06-26 14:00:39 -07:00
d9a94db59d prevent composition from adding a dupe into layout 2020-06-26 13:51:03 -07:00
72 changed files with 1267 additions and 448 deletions

3
.gitignore vendored
View File

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

View File

@ -43,7 +43,9 @@
openmct.legacyRegistry.enable.bind(openmct.legacyRegistry) openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
); );
openmct.install(openmct.plugins.LocalStorage()); //openmct.install(openmct.plugins.LocalStorage());
//Change 'vipersim' to whatever your local db is named
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/vipersim'));
openmct.install(openmct.plugins.Espresso()); openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.MyItems()); openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.Generator()); openmct.install(openmct.plugins.Generator());
@ -113,7 +115,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

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

View File

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

View File

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

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

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,30 +112,12 @@ 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...
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); return rejectIfFalsey(result, self.$q);
}).catch(function (error) { }).catch(function (error) {
return notifyOnError(error, domainObject, self.notificationService, self.$q); 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,32 +152,39 @@ 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()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(false);
expect(mockNofificationService.error).not.toHaveBeenCalled(); 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()
.catch(() => rejected = true)
.then(() => {
expect(rejected).toBe(true);
expect(mockNofificationService.error).toHaveBeenCalled(); expect(mockNofificationService.error).toHaveBeenCalled();
}); });
}); });
}); });
});
} }
); );

View File

@ -35,7 +35,8 @@ define([
'./services/LegacyObjectAPIInterceptor', './services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews', './views/installLegacyViews',
'./policies/LegacyCompositionPolicyAdapter', './policies/LegacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter' './actions/LegacyActionAdapter',
'./services/LegacyPersistenceAdapter'
], function ( ], function (
ActionDialogDecorator, ActionDialogDecorator,
AdapterCapability, AdapterCapability,
@ -51,7 +52,8 @@ define([
LegacyObjectAPIInterceptor, LegacyObjectAPIInterceptor,
installLegacyViews, installLegacyViews,
legacyCompositionPolicyAdapter, legacyCompositionPolicyAdapter,
LegacyActionAdapter LegacyActionAdapter,
LegacyPersistenceAdapter
) { ) {
return { return {
name: 'src/adapter', name: 'src/adapter',
@ -114,6 +116,13 @@ define([
"instantiate", "instantiate",
"topic" "topic"
] ]
},
{
provides: "persistenceService",
type: "provider",
priority: "fallback",
implementation: LegacyPersistenceAdapter.default,
depends: ["openmct"]
} }
], ],
policies: [ policies: [

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

@ -0,0 +1,28 @@
import objectUtils from 'objectUtils';
function LegacyPersistenceProvider(openmct) {
this.openmct = openmct;
}
LegacyPersistenceProvider.prototype.listObjects = function () {
return Promise.resolve([]);
}
LegacyPersistenceProvider.prototype.listSpaces = function () {
return Promise.resolve(Object.keys(this.openmct.objects.providers));
}
LegacyPersistenceProvider.prototype.updateObject = function (legacyDomainObject) {
return this.openmct.objects.save(legacyDomainObject.useCapability('adapter'));
}
LegacyPersistenceProvider.prototype.updateObject = function (legacyDomainObject) {
return this.openmct.objects.save(legacyDomainObject.useCapability('adapter'));
}
LegacyPersistenceProvider.prototype.readObject = function (keystring) {
let identifier = objectUtils.parseKeyString(keystring);
return this.openmct.legacyObject(this.openmct.objects.get(identifier));
}
export default LegacyPersistenceProvider;

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

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

View File

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

View File

@ -29,10 +29,13 @@ 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) {
if (installIndicator) {
let component = new Vue ({ let component = new Vue ({
provide: { provide: {
openmct openmct
@ -47,6 +50,7 @@ define([
}; };
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

@ -103,6 +103,8 @@ export default class ConditionManager extends EventEmitter {
criterion.operation = ''; criterion.operation = '';
conditionChanged = true; conditionChanged = true;
} }
} else {
conditionChanged = true;
} }
}); });
if (conditionChanged) { if (conditionChanged) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,37 +1,17 @@
/******************************* LIST ITEM */ /******************************* LIST ITEM */
.c-list-item { .c-list-item {
&__name a {
display: flex;
> * + * { margin-left: $interiorMarginSm; }
}
&__type-icon { &__type-icon {
// Have to do it this way instead of using icon-* class, due to need to apply alias to the icon color: $colorItemTreeIcon;
color: $colorKey;
display: inline-block;
width: 1em;
margin-right:$interiorMarginSm;
} }
&__name-value { &__name {
@include ellipsize(); @include ellipsize();
} }
&.is-alias { &.is-alias {
// Object is an alias to an original. // Object is an alias to an original.
[class*='__type-icon'] { [class*='__type-icon'] {
&:after { @include isAlias();
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,7 +13,8 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: $colorListItemBgHov; background: $colorItemTreeHoverBg;
filter: $filterHov;
transition: $transIn; transition: $transIn;
} }
} }

View File

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

View File

@ -2,14 +2,17 @@
<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">
<div class="c-object-label__type-icon icon-notebook"></div>
<div class="c-object-label__name">
Notebook Snapshots Notebook Snapshots
<span v-if="snapshots.length" <span v-if="snapshots.length"
class="l-browse-bar__object-details" class="l-browse-bar__object-details"
>&nbsp;{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }} >&nbsp;{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
</span> </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

@ -0,0 +1,53 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* A CouchDocument describes domain object model in a format
* which is easily read-written to CouchDB. This includes
* Couch's _id and _rev fields, as well as a separate
* metadata field which contains a subset of information found
* in the model itself (to support search optimization with
* CouchDB views.)
* @memberof platform/persistence/couch
* @constructor
* @param {string} id the id under which to store this mode
* @param {object} model the model to store
* @param {string} rev the revision to include (or undefined,
* if no revision should be noted for couch)
* @param {boolean} whether or not to mark this document as
* deleted (see CouchDB docs for _deleted)
*/
export default function CouchDocument(id, model, rev, markDeleted) {
return {
"_id": id,
"_rev": rev,
"_deleted": markDeleted,
"metadata": {
"category": "domain object",
"type": model.type,
"owner": "admin",
"name": model.name,
"created": Date.now()
},
"model": model
};
}

View File

@ -0,0 +1,65 @@
import CouchDocument from "./CouchDocument";
const REV = "_rev";
const ID = "_id";
export default class CouchObjectProvider {
constructor(openmct, url, namespace) {
this.openmct = openmct;
this.url = url;
this.namespace = namespace; this.namespace = namespace; this.namespace = namespace;
this.revs = {};
}
request(subPath, method, value) {
console.log(subPath, method, value);
return fetch(this.url + '/' + subPath, {
method: method,
body: value
}).then(response => response.json())
.then(function (response) {
return response;
}, function () {
return undefined;
});
}
// Check the response to a create/update/delete request;
// track the rev if it's valid, otherwise return false to
// indicate that the request failed.
checkResponse(response) {
if (response && response.ok) {
this.revs[response.id] = response.rev;
return response.ok;
} else {
return false;
}
}
getModel(response) {
if (response && response.model) {
let key = response[ID];
this.revs[key] = response[REV];
let object = response.model;
object.identifier = {
namespace: this.namespace,
key: key
};
return object;
} else {
return undefined;
}
}
get(identifier) {
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
}
create(model) {
return this.request(model.identifier, "PUT", new CouchDocument(model.identifier.key, model)).then(this.checkResponse.bind(this));
}
update(model) {
return this.request(model.identifier, "PUT", model).then(this.checkResponse.bind(this));
}
}

View File

@ -0,0 +1,8 @@
import CouchObjectProvider from './CouchObjectProvider';
const NAMESPACE = '';
export default function CouchPlugin(config) {
return function install(openmct) {
openmct.objects.addProvider(NAMESPACE, new CouchObjectProvider(openmct, config, NAMESPACE));
}
}

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* 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 CouchPlugin from './plugin.js';
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
fdescribe("the plugin", () => {
let openmct;
let element;
let child;
let provider;
let testSpace = "testSpace";
let testPath = "/test/db";
let mockDomainObject = { identifier: {namespace: '', key: "some-value"} };
beforeEach((done) => {
openmct = createOpenMct(false);
openmct.install(new CouchPlugin(testSpace, testPath));
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.on('start', done);
openmct.startHeadless();
provider = openmct.objects.getProvider(mockDomainObject.identifier);
});
it('registers a provider for objects', () => {
expect(provider).toBeDefined();
});
it('gets an object', () => {
openmct.objects.get(mockDomainObject.identifier).then((result) => {
expect(provider.get).toHaveBeenCalled();
expect(result).toBeDefined();
});
});
it('creates an object', () => {
openmct.objects.save(mockDomainObject).then((result) => {
expect(provider.create).toHaveBeenCalled();
expect(result).toBeDefined();
});
});
it('updates an object', () => {
openmct.objects.save(mockDomainObject).then((result) => {
openmct.objects.save(mockDomainObject).then((updatedResult) => {
expect(provider.update).toHaveBeenCalled();
expect(updatedResult).toBeDefined();
});
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,90 @@
/*****************************************************************************
* 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

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

View File

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

View File

@ -54,7 +54,8 @@ define([
'./themes/snow', './themes/snow',
'./URLTimeSettingsSynchronizer/plugin', './URLTimeSettingsSynchronizer/plugin',
'./notificationIndicator/plugin', './notificationIndicator/plugin',
'./newFolderAction/plugin' './newFolderAction/plugin',
'./persistence/couch/plugin'
], function ( ], function (
_, _,
UTCTimeSystem, UTCTimeSystem,
@ -89,12 +90,12 @@ define([
Snow, Snow,
URLTimeSettingsSynchronizer, URLTimeSettingsSynchronizer,
NotificationIndicator, NotificationIndicator,
NewFolderAction NewFolderAction,
CouchDBPlugin
) { ) {
var bundleMap = { var bundleMap = {
LocalStorage: 'platform/persistence/local', LocalStorage: 'platform/persistence/local',
MyItems: 'platform/features/my-items', MyItems: 'platform/features/my-items',
CouchDB: 'platform/persistence/couch',
Elasticsearch: 'platform/persistence/elastic' Elasticsearch: 'platform/persistence/elastic'
}; };
@ -126,27 +127,7 @@ define([
plugins.Conductor = TimeConductorPlugin.default; plugins.Conductor = TimeConductorPlugin.default;
plugins.CouchDB = function (url) { plugins.CouchDB = CouchDBPlugin.default;
return function (openmct) {
if (url) {
var bundleName = "config/couch";
openmct.legacyRegistry.register(bundleName, {
"extensions": {
"constants": [
{
"key": "COUCHDB_PATH",
"value": url,
"priority": "mandatory"
}
]
}
});
openmct.legacyRegistry.enable(bundleName);
}
openmct.legacyRegistry.enable(bundleMap.CouchDB);
};
};
plugins.Elasticsearch = function (url) { plugins.Elasticsearch = function (url) {
return function (openmct) { return function (openmct) {

View File

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

View File

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

View File

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

View File

@ -151,6 +151,10 @@ $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1);
$filterItemMissing: brightness(0.6) grayscale(1);
$opacityMissing: 0.5;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color

View File

@ -155,6 +155,10 @@ $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1);
$filterItemMissing: contrast(0.2);
$opacityMissing: 0.5;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color

View File

@ -151,6 +151,10 @@ $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing $browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px; $browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4); $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(0.9);
$filterItemMissing: contrast(0.2);
$opacityMissing: 0.4;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color

View File

@ -61,7 +61,7 @@ $plotXBarH: 35px;
$plotLegendH: 20px; $plotLegendH: 20px;
$plotLegendWidthCollapsed: 20%; $plotLegendWidthCollapsed: 20%;
$plotLegendWidthExpanded: 50%; $plotLegendWidthExpanded: 50%;
$plotSwatchD: 10px; $plotSwatchD: 12px;
$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW); // 1: Top, 2: right, 3: bottom, 4: left $plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW); // 1: Top, 2: right, 3: bottom, 4: left
$plotMinH: 95px; $plotMinH: 95px;
$controlBarH: 25px; $controlBarH: 25px;

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,21 +430,15 @@ select {
margin: 1px 1px 0 0; margin: 1px 1px 0 0;
padding: $interiorMargin $interiorMarginLg; padding: $interiorMargin $interiorMarginLg;
white-space: nowrap; white-space: nowrap;
clip-path: $clipPath;
-webkit-clip-path: $clipPath; // Safari
--notchSize: 7px; > * + * {
margin-left: $interiorMargin;
clip-path: }
polygon(
0% 0%,
calc(100% - var(--notchSize)) 0%,
100% var(--notchSize),
100% calc(100% - var(--notchSize)),
100% 100%,
0% 100%
);
@include hover() { @include hover() {
background: $colorBtnBgHov; filter: $filterHov;
} }
&.is-current { &.is-current {

View File

@ -43,6 +43,8 @@ mct-plot {
.c-plot, .c-plot,
.gl-plot { .gl-plot {
overflow: hidden;
.s-status-taking-snapshot & { .s-status-taking-snapshot & {
.c-control-bar { .c-control-bar {
display: none; display: none;
@ -51,6 +53,17 @@ mct-plot {
display: none; display: none;
} }
} }
/*********************** MISSING ITEM INDICATORS */
.is-missing__indicator {
display: none;
}
.is-missing {
@include isMissing();
.is-missing__indicator {
font-size: 0.8em;
}
}
} }
.c-plot { .c-plot {
@ -74,6 +87,7 @@ mct-plot {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
overflow: hidden;
} }
&--stacked { &--stacked {
@ -102,18 +116,15 @@ mct-plot {
} }
} }
.gl-plot { .gl-plot {
display: flex; display: flex;
position: relative; flex: 1 1 auto;
width: 100%;
height: 100%;
min-height: $plotMinH;
/*********************** AXIS AND DISPLAY AREA */ /*********************** AXIS AND DISPLAY AREA */
.plot-wrapper-axis-and-display-area { .plot-wrapper-axis-and-display-area {
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
min-height: $plotMinH;
} }
.gl-plot-wrapper-display-area-and-x-axis { .gl-plot-wrapper-display-area-and-x-axis {
@ -196,7 +207,6 @@ mct-plot {
left: 0; top: 0; right: auto; bottom: 0; left: 0; top: 0; right: auto; bottom: 0;
padding-left: 5px; padding-left: 5px;
text-orientation: mixed; text-orientation: mixed;
//overflow: hidden;
writing-mode: vertical-lr; writing-mode: vertical-lr;
&:before { &:before {
// Icon denoting configurability // Icon denoting configurability
@ -368,12 +378,6 @@ mct-plot {
z-index: -10; z-index: -10;
.l-view-section { .l-view-section {
//$m: $interiorMargin;
//top: $m !important;
//right: $m;
//bottom: $m;
//left: $m;
.s-status-timeconductor-unsynced .holder-plot { .s-status-timeconductor-unsynced .holder-plot {
.t-object-alert.t-alert-unsynced { .t-object-alert.t-alert-unsynced {
display: none; display: none;
@ -429,14 +433,18 @@ mct-plot {
/****************** _LEGEND.SCSS */ /****************** _LEGEND.SCSS */
.gl-plot-legend, .gl-plot-legend,
.c-plot-legend { .c-plot-legend {
overflow: hidden;
&__wrapper { &__wrapper {
// Holds view-control and both collapsed and expanded legends // Holds view-control and both collapsed and expanded legends
flex: 1 1 auto; flex: 1 1 auto;
overflow: auto; // Prevents collapsed legend from forcing scrollbars on higher parent containers height: 100%;
overflow: auto;
padding: 2px;
} }
&__view-control { &__view-control {
padding-top: 2px; padding-top: 4px;
margin-right: $interiorMarginSm; margin-right: $interiorMarginSm;
} }
@ -481,15 +489,21 @@ mct-plot {
/***************** GENERAL STYLES, ALL STATES */ /***************** GENERAL STYLES, ALL STATES */
.plot-legend-item { .plot-legend-item {
// General styles for legend items, both expanded and collapsed legend states // General styles for legend items, both expanded and collapsed legend states
> * + * {
margin-left: $interiorMarginSm;
}
.plot-series-color-swatch { .plot-series-color-swatch {
border-radius: $smallCr; border-radius: 30%; //$smallCr;
border: 1px solid $colorBodyBg; border: 1px solid $colorBodyBg;
display: inline-block; display: inline-block;
flex: 0 0 auto;
height: $plotSwatchD; height: $plotSwatchD;
width: $plotSwatchD; width: $plotSwatchD;
} }
.plot-series-name { .plot-series-name {
display: inline; display: inline;
@include ellipsize();
} }
.plot-series-value { .plot-series-value {
@ -497,6 +511,16 @@ mct-plot {
} }
} }
.plot-series-swatch-and-name {
display: flex;
flex: 0 1 auto;
align-items: center;
> * + * {
margin-left: $interiorMarginSm;
}
}
.plot-wrapper-expanded-legend { .plot-wrapper-expanded-legend {
flex: 1 1 auto; flex: 1 1 auto;
} }
@ -505,9 +529,6 @@ mct-plot {
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; } &.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
&.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; } &.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; }
&.plot-legend-collapsed .icon-cursor-lock::before { padding-right: 5px; }
&.plot-legend-expanded .icon-cursor-lock::before { padding-right: 5px; }
&.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; } &.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; }
&.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; } &.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; }
&.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; } &.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
@ -521,19 +542,13 @@ mct-plot {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: stretch; justify-content: stretch;
&:not(:first-child) {
margin-left: $interiorMarginLg;
}
.plot-series-swatch-and-name, .plot-series-swatch-and-name,
.plot-series-value { .plot-series-value {
@include ellipsize(); @include ellipsize();
flex: 1 1 auto; flex: 1 1 auto;
} }
.plot-series-swatch-and-name {
margin-right: $interiorMarginSm;
}
.plot-series-value { .plot-series-value {
text-align: left; text-align: left;
} }
@ -543,7 +558,7 @@ mct-plot {
/***************** GENERAL STYLES, EXPANDED */ /***************** GENERAL STYLES, EXPANDED */
&.plot-legend-expanded { &.plot-legend-expanded {
.gl-plot-legend { .gl-plot-legend {
// max-height: 70%; max-height: 70%;
} }
.plot-wrapper-expanded-legend { .plot-wrapper-expanded-legend {
@ -564,6 +579,11 @@ mct-plot {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
overflow: hidden; overflow: hidden;
> .plot-legend-item + .plot-legend-item {
// Space between plot items
margin-left: $interiorMarginLg;
}
} }
} }
} }
@ -595,12 +615,17 @@ mct-plot {
min-width: 0; min-width: 0;
flex: 1 1 auto; flex: 1 1 auto;
overflow-y: auto; overflow-y: auto;
> * + * {
// Space between plot items
margin-top: $interiorMarginSm;
}
} }
.plot-legend-item { .plot-legend-item {
margin-bottom: 1px;
margin-left: 0; margin-left: 0;
flex-wrap: wrap; flex-wrap: nowrap;
.plot-series-swatch-and-name { .plot-series-swatch-and-name {
@include ellipsize();
flex: 0 1 auto; flex: 0 1 auto;
min-width: 20%; min-width: 20%;
} }
@ -654,3 +679,24 @@ mct-plot {
display: inline-block !important; 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

@ -117,6 +117,33 @@
} }
} }
@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) { @mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
background-image: linear-gradient(-45deg, background-image: linear-gradient(-45deg,
rgba($c, $a) 25%, transparent 25%, rgba($c, $a) 25%, transparent 25%,

View File

@ -49,8 +49,6 @@ table {
td { td {
vertical-align: top; vertical-align: top;
} }
a { color: $colorBtnMajorBg; }
} }
.is-editing { .is-editing {

View File

@ -13,7 +13,6 @@
@import "../plugins/filters/components/filters-view.scss"; @import "../plugins/filters/components/filters-view.scss";
@import "../plugins/filters/components/global-filters.scss"; @import "../plugins/filters/components/global-filters.scss";
@import "../plugins/flexibleLayout/components/flexible-layout.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/grid-view.scss";
@import "../plugins/folderView/components/list-item.scss"; @import "../plugins/folderView/components/list-item.scss";
@import "../plugins/folderView/components/list-view.scss"; @import "../plugins/folderView/components/list-view.scss";

View File

@ -24,13 +24,24 @@
class="c-so-view has-local-controls" class="c-so-view has-local-controls"
:class="{ :class="{
'c-so-view--no-frame': !hasFrame, 'c-so-view--no-frame': !hasFrame,
'has-complex-content': complexContent 'has-complex-content': complexContent,
'is-missing': domainObject.status === 'missing'
}" }"
> >
<div class="c-so-view__header"> <div class="c-so-view__header">
<div class="c-object-label" <div class="c-object-label"
:class="[cssClass, classList]" :class="{
classList,
'is-missing': domainObject.status === 'missing'
}"
> >
<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"> <div class="c-object-label__name">
{{ domainObject && domainObject.name }} {{ domainObject && domainObject.name }}
</div> </div>
@ -46,6 +57,9 @@
@click="expand" @click="expand"
></button> ></button>
</div> </div>
<div class="is-missing__indicator"
title="This item is missing"
></div>
<object-view <object-view
ref="objectView" ref="objectView"
class="c-so-view__object-view" class="c-so-view__object-view"

View File

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

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();
} }
@ -301,7 +304,7 @@ export default {
objectPath= this.currentObjectPath || this.objectPath, objectPath= this.currentObjectPath || this.objectPath,
parentObject = objectPath[1]; parentObject = objectPath[1];
return [browseObject, parentObject, this.currentObject].every(object => !object.locked); return [browseObject, parentObject, this.currentObject].every(object => object && !object.locked);
} }
} }
} }

View File

@ -2,6 +2,10 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&.is-missing {
border: $borderMissing;
}
/*************************** HEADER */ /*************************** HEADER */
&__header { &__header {
flex: 0 0 auto; flex: 0 0 auto;
@ -39,6 +43,15 @@
> .c-so-view__local-controls { > .c-so-view__local-controls {
top: $interiorMarginSm; right: $interiorMarginSm; top: $interiorMarginSm; right: $interiorMarginSm;
} }
&.is-missing {
@include isMissing($absPos: true);
.is-missing__indicator {
top: $interiorMargin;
left: $interiorMargin;
}
}
} }
&__local-controls { &__local-controls {
@ -61,13 +74,11 @@
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();
} }
} }
}
.c-click-icon, .c-click-icon,
.c-button { .c-button {
@ -78,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

@ -7,19 +7,33 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
> * + * { margin-left: $interiorMargin; }
&__name { &__name {
@include ellipsize(); @include ellipsize();
display: inline; display: inline;
} }
&__type-icon, &__type-icon {
&:before {
// Type icon. Must be an HTML entity to allow inclusion of alias indicator. // Type icon. Must be an HTML entity to allow inclusion of alias indicator.
display: block; display: block;
flex: 0 0 auto; flex: 0 0 auto;
font-size: 1.1em; font-size: 1.1em;
opacity: 0.6; }
margin-right: $interiorMargin;
&.is-missing {
@include isMissing($absPos: true);
[class*='__type-icon']:before,
[class*='__type-icon']:after{
opacity: $opacityMissing;
}
.is-missing__indicator {
right: -3px;
top: -3px;
transform: scale(0.7);
}
} }
} }
@ -27,6 +41,8 @@
border-radius: $controlCr; border-radius: $controlCr;
padding: $interiorMarginSm 1px; padding: $interiorMarginSm 1px;
> * + * { margin-left: $interiorMarginSm; }
&__name { &__name {
display: inline; display: inline;
width: 100%; width: 100%;

View File

@ -1,16 +1,24 @@
<template> <template>
<div class="c-inspector__header"> <div class="c-inspector__header">
<div v-if="!multiSelect && !singleSelectNonObject" <div v-if="!multiSelect"
class="c-inspector__selected-w c-object-label" class="c-inspector__selected-w c-object-label"
:class="{'is-missing': domainObject.status === 'missing' }"
>
<div class="c-object-label__type-icon"
:class="typeCssClass" :class="typeCssClass"
> >
<span class="c-inspector__selected c-object-label__name">{{ item.name }}</span> <span class="is-missing__indicator"
title="This item is missing"
></span>
</div> </div>
<div v-if="singleSelectNonObject"
class="c-inspector__selected-w c-object-label" <span v-if="!singleSelectNonObject"
:class="typeCssClass" class="c-inspector__selected c-object-label__name"
> >{{ item.name }}</span>
<span class="c-inspector__selected c-object-label__name c-inspector__selected--non-domain-object">Layout Object</span> <span v-if="singleSelectNonObject"
class="c-inspector__selected c-object-label__name c-inspector__selected--non-domain-object"
>Layout Object</span>
</div> </div>
<div v-if="multiSelect" <div v-if="multiSelect"
class="c-inspector__multiple-selected-w" class="c-inspector__multiple-selected-w"

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

@ -8,8 +8,18 @@
></button> ></button>
<div <div
class="l-browse-bar__object-name--w c-object-label" class="l-browse-bar__object-name--w c-object-label"
:class="[ type.cssClass, classList ]" :class="{
classList,
'is-missing': domainObject.status === 'missing'
}"
> >
<div class="c-object-label__type-icon"
:class="type.cssClass"
>
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<span <span
class="l-browse-bar__object-name c-object-label__name c-input-inline" class="l-browse-bar__object-name c-object-label__name c-input-inline"
contenteditable contenteditable

View File

@ -354,9 +354,9 @@
@include headerFont(1.4em); @include headerFont(1.4em);
min-width: 0; min-width: 0;
&:before { .is-missing__indicator {
// Icon right: -5px !important;
margin-right: $interiorMargin; top: -4px !important;
} }
} }

View File

@ -52,12 +52,13 @@
padding: $interiorMarginSm $interiorMargin; padding: $interiorMarginSm $interiorMargin;
transition: background 150ms ease; transition: background 150ms ease;
&__type-icon {
color: $colorItemTreeIcon;
}
&:hover { &:hover {
background: $colorItemTreeHoverBg; background: $colorItemTreeHoverBg;
filter: $filterHov;
[class*="__name"] {
color: $colorItemTreeHoverFg;
}
} }
&.is-navigated-object, &.is-navigated-object,
@ -81,12 +82,6 @@
margin-left: $interiorMarginSm; margin-left: $interiorMarginSm;
} }
&:hover {
.c-tree__item__type-icon:before {
color: $colorItemTreeIconHover;
}
}
&.is-navigated-object, &.is-navigated-object,
&.is-selected { &.is-selected {
.c-tree__item__type-icon:before { .c-tree__item__type-icon:before {
@ -115,10 +110,6 @@
color: $colorItemTreeFg; color: $colorItemTreeFg;
} }
&__type-icon {
color: $colorItemTreeIcon;
}
&.is-alias { &.is-alias {
// Object is an alias to an original. // Object is an alias to an original.
[class*='__type-icon'] { [class*='__type-icon'] {

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 {