Compare commits

...

91 Commits

Author SHA1 Message Date
2d8afa1456 parsing datum itself rather than specific key of datum, let telemetry api do the work 2020-07-24 11:26:28 -07:00
f4ef82ef74 Merge branch 'master' into imagery-timestamp-parsing
Merg'n master
2020-07-24 11:23:15 -07:00
f43121a38a updating karma timeouts for testing purposes 2020-07-24 10:57:29 -07:00
9124f4f566 allow table row to control object path for context menu actions (#3232)
Need for changes in VISTA
2020-07-24 10:41:36 -07:00
9f7d788c86 weird merge issues 2020-07-24 09:36:19 -07:00
d2374920ff merging 2020-07-24 09:32:26 -07:00
a6b9ba184c Merge latest master, resolve conflicts and fix constants 2020-07-23 14:33:48 -07:00
c00ab72d04 linting fixes 2020-07-23 11:46:37 -07:00
98fb507f01 removing "my other items" testing references 2020-07-23 11:43:25 -07:00
5258227d87 Merge branch 'new-tree-refactor' of https://github.com/nasa/openmct into new-tree-refactor
Merging
2020-07-23 11:38:05 -07:00
f43ba8f8f4 fixed issues when multiple root children exist, added plugin to change the name of the default root object 2020-07-23 11:37:30 -07:00
d9baa94970 UI enhancement fixes 2 (#3225)
- Fixed incorrect CSS naming: `c-frame-edit__move` changed to
 `c-frame__move-bar`;
 - Fixed `display: contents` that was erroneously applied to
 `u-angular-object-view-wrapper` and preventing styling from being
 applied to plots, renamed class to `.l-angular-ov-wrapper`;
 - Removed commented CSS;
2020-07-22 20:20:17 -07:00
afeb89a51a [VIPEROMCT-16] Creates a closure for telemetryObject so that requests can resolve correctly even if the telemetryObject is destroyed (#3210)
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-07-22 15:22:31 -07:00
07992f0b2a [Tabs View] add ability to remove tabs from tabs view interface (#3147) (#3148)
* add ability to remove tabs from tabs view interface (#3147)

* an "X" on each tab is visible in edit mode

* replaced custom removeDialog with openmct.overlays.dialog

* Minor mods to markup and CSS

- Changed tab from button to div to allow a cleaner approach to the
nested close button;
- Changed close "icon-x" span to a button and added `c-click-icon` style
tag;
- Tweaked class naming to simplify and align with a more functional/
descriptive approach;

* use ES6 arrow func to avoid self=this

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-07-22 15:17:35 -07:00
a5c4508578 add minified vue to production (#3183)
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2020-07-22 14:47:46 -07:00
a4fab3ce8a UI enhancement fixes (#3222)
- Added min-width and min-height to Display Layout lines to allow
 easier selection and move when line is purely vertical or horizontal;
 - Fixed spacing, size and icon of overlay close button;
2020-07-22 11:35:06 -07:00
97d80f57cc UI enhancements (#3217)
* UI enhancements for #3176

- Large overlay now displays fullscreen;

* UI enhancements for #3176

- Adding new ".is-in-small-container" CSS - VERY WIP!
- TODO: fix table implementation;

* UI fixes for NIRVSS client #170

- Hide table header filter inputs when table is in small container;

* UI fixes for NIRVSS client #170

- Fixing legends and plot layout when small, and within a stacked plot;
- Add new `hideLegendWhenSmall` property;
- Remove 'hidden' from plot legend position options;
- Reduced opacity of tabular headers in Espresso theme;
- VERY, VERY WIP right now!

* UI fixes for NIRVSS client #170

- Fixing legends and plot layout when small, and within a stacked plot;
- Cleanups, indention, removed commented CSS;
- Tightened up spacing in plot Y axis;

* UI enhancements for #3176

- Move local controls for plots and imagery, prevent overlapping with
 view large button when in a hidden frame in a layout;
 - Finesse local control styling for increased legibility;
 - Move l-state-indicators to avoid overlap with repositioned local
 controls, finesse styling;

* UI enhancements for #3176

- Tweak large overlay close button for better visual alignment;

* UI enhancements for #3176

- Significant improvements to lines in Display Layouts;
- Increased border-width for lines and boxes;
- Code enhanced for proper handling of horizontal and
vertical lines - but still isn't working properly;
- Renamed box-view.scss to box-and-line-views.scss;
- VERY WIP!

* Fixed incorrect grid array reference

* UI enhancements for #3176

- Fixed final issue with Display Layout line drawing object, thank you
@deeptailor!;

* UI enhancements for #3176

- Contrast enhancements and markup normalization for `c-object-label`
elements in main view, Layout frames, Inspector and overlay;
- Enhanced `l-overlay-large` layout;
- Tightened up margins and spacing in plots;
- Refined `is-paused` styling in Telemetry Tables;
- Now hide Telemetry Tables 'Export Data' button if rows are selected,
which use a separate export button;
- Layout frames now hide button's text labels when small;
- Layout frames spacing tightened up and improved;

* UI enhancements for #3176

- Tweak Snow theme constants;

* UI enhancements for #3176

- Fixed ObjectFrame getOverlayElement method, added a wrapper div
around the viewed object to properly control resulting layout in the
overlay;
- Simplified preview CSS to remove background, border and padding;
- Layout tweaks to add space between scrollbar and thumbs in Imagery
view;
- Removed dev "-info" element in LineView.vue;

* UI enhancements for #3176

- Improved styling for 'edit lock' button;

* UI enhancements for #3176

- Show Display Layout frame "-move" bar on hover, rather than select, to
 make it easier to select items with hidden frames, and only show -move
  bar's drag grippy when that frame is selected;
- `pointer-events: none` applied to table's body and plot's plot areas
when placed in a Layout and being edited, prevents distracting
interactions (plot zoom/pan, table row selection) when selecting and
moving elements in a Layout;
- Refined hover styles for c-button to use $filterHov, simplified and
normalized hover styling;
- Converted a number of old `<a>` tags to `<buttons>` to normalize
styling and use the appropriate control;
- Edit lock button is now colored when locked;

* Fix linting issue

* Minor tweaks

- Tweaked control positioning;

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2020-07-21 21:01:38 -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
0ff9821091 Merge latest master, resolve conflicts 2020-07-17 15:23:36 -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
353bdb5ca5 Remove unneeded hard-coded CSS color property 2020-07-16 17:16:58 -07:00
28a93fb262 some PR comment updates!; 2020-07-16 16:15:11 -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
8882e5f3b3 updated addChild and removeChild functions as they were not working at all 2020-07-02 12:06:43 -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
9cfabe0054 Merge branch 'master' into new-tree-refactor
Merging master
2020-07-02 10:15:39 -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
7975cf89ef lint fixes 2020-06-29 15:35:59 -07:00
a9c0e5a139 dynamic item height for desktop and mobile views 2020-06-29 15:10:39 -07:00
92a7ac18a2 Styling for new-tree-refactor
- Using new glyphs for items expand/collapse in Status area;
2020-06-26 18:13:55 -07:00
240bc9e713 Adding new glyphs
- Added `icon-items-collapse` and `icon-items-expand`;
2020-06-26 18:10:26 -07:00
d09013ba28 Styling for new-tree-refactor
- Normalizing status area expand/collapse iconography to match new
approach in panes;
2020-06-26 17:58:27 -07:00
d99d414c84 Styling for new-tree-refactor
- Fix styles for Snow theme;
- Sync Maelstrom and Espresso themes;
- Remove too-broad `<a>` hover styling from global.scss;
- Disallow pointer-events on `is-navigated` object's label (click on
c-nav__down element still allowed);
2020-06-26 17:17:18 -07:00
2638302dab Styling for new-tree-refactor
- Significant consolidation and cleanups in mct-tree.scss;
- Normalize base and hover styles across new tree, legacy tree,
list-items (used in Notebook) and Folder List View;
- Class naming normalization, change `c-list-item__name-value` to
`c-list-item__name`;
- Add styling to override and remove `<a> outline: dotted` coming from
normalize-min;
- Removed too-broad `<a>` coloring in tables;
2020-06-26 16:28:19 -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
8241ed8bb8 Styling for new-tree-refactor WIP
- Nav up arrows now left-align properly;
2020-06-25 21:28:46 -07:00
23dffe33a9 Styling for new-tree-refactor WIP
- WIP!
- Refinements to "empty" object element;
- Changed sync-tree icon glyph;
2020-06-25 19:23:21 -07:00
92e08f19bd Merge branch 'new-tree-refactor' of https://github.com/nasa/openmct into new-tree-refactor 2020-06-25 14:12:08 -07:00
c644e5266a remove arrows for search results, hightlight "my items" correctly, added empty folder notic 2020-06-25 13:32:09 -07:00
7fa93c18f3 More new glyphs, updated art
- New glyphs: icon-unlocked and icon-target;
- Updated art for icon-lock glyph;
2020-06-25 10:48:02 -07:00
5b6e61d95a Styling for new-tree-refactor WIP
- WIP!
- Significant mods and simplification in pane.vue and assoc CSS for
expand/collapse functionality;
- Wait spinner when in tree: cleanups, simplification;
2020-06-24 19:05:44 -07:00
474b1ed2bf Styling for new-tree-refactor WIP
- WIP!
- Added new `c-click-link` CSS class;
- Move tree sync button into tree pane area;
- Added named "controls" slot to pane.vue;
- _up and _down arrows now use visibility instead of opacity to prevent
accidental clicks;
2020-06-24 16:49:37 -07:00
0d0de1ed64 Styling for new-tree-refactor WIP
- WIP!
- New glyphs, markup changes in BrowseBar.vue;
- Refinements to tree items, WIP;
- TODO: move hard-coded CSS values into _constants, make
theme-compatible;
2020-06-23 19:30:37 -07:00
85ab5cb319 Adding new glyphs
- Multiple new glyphs cherrypicked from branch `add-new-glyphs-062320`;
2020-06-23 18:28:30 -07:00
299005982f eslint fix 2020-06-18 14:12:04 -07:00
8d3b277ef4 modified so search now uses the same container and virtual scroll 2020-06-18 13:39:58 -07:00
8eae707833 Merge branch 'new-tree-refactor' of https://github.com/nasa/openmct into new-tree-refactor
Merge.
2020-06-18 10:29:04 -07:00
f62b39054a Merge branch 'master' into new-tree-refactor
Merging master.
.
2020-06-18 10:27:33 -07:00
7515ce504e Merge branch 'master' into new-tree-refactor 2020-06-17 13:37:16 -07:00
0b5ca621ec initial PR review updates 2020-06-17 13:06:18 -07:00
0e0644cd1f loading, sync bug, search issues, opitmization 2020-06-17 12:48:41 -07:00
6089ae3531 added scrollTo on load if in viewed objects directory 2020-06-16 17:33:27 -07:00
876eb59787 checking domainobject composition for length to verify children instead of composition object itself 2020-06-16 10:29:05 -07:00
17be0d7132 removing console logs 2020-06-15 16:34:38 -07:00
ace880dd41 Merge branch 'master' into new-tree-refactor
Merging master.
2020-06-15 13:35:52 -07:00
dc3781c8e5 scrollTo set for sync, bug fixes, window resize handling 2020-06-15 13:35:12 -07:00
b76b4e4098 revised new tree refactor, moved most of the logic to mct-tree instead of tree-item 2020-06-09 12:55:00 -07:00
105 changed files with 2619 additions and 957 deletions

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,28 +20,37 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
function RootObjectProvider(rootRegistry) {
class RootObjectProvider {
constructor(rootRegistry) {
if(!RootObjectProvider.instance) {
this.rootRegistry = rootRegistry;
}
RootObjectProvider.prototype.get = function () {
return this.rootRegistry.getRoots()
.then(function (roots) {
return {
this.rootObject = {
identifier: {
key: "ROOT",
namespace: ""
},
name: 'The root object',
type: 'root',
composition: roots
};
});
composition: []
};
RootObjectProvider.instance = this;
}
return RootObjectProvider.instance;
}
return RootObjectProvider;
});
updateName(name) {
this.rootObject.name = name
}
async get() {
let roots = await this.rootRegistry.getRoots();
this.rootObject.composition = roots;
return this.rootObject;
}
}
const instance = function (rootRegistry) {
return new RootObjectProvider(rootRegistry);
}
export default instance;

View File

@ -31,7 +31,7 @@ define([
beforeEach(function () {
rootRegistry = jasmine.createSpyObj('rootRegistry', ['getRoots']);
rootRegistry.getRoots.and.returnValue(Promise.resolve(['some root']));
rootObjectProvider = new RootObjectProvider(rootRegistry);
rootObjectProvider = new RootObjectProvider.default(rootRegistry);
});
it('supports fetching root', function () {

View File

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

View File

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

View File

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

View File

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

View File

@ -29,10 +29,13 @@ define([
ClearDataAction,
Vue
) {
return function plugin(appliesToObjects) {
return function plugin(appliesToObjects, options = {indicator: true}) {
let installIndicator = options.indicator;
appliesToObjects = appliesToObjects || [];
return function install(openmct) {
if (installIndicator) {
let component = new Vue ({
provide: {
openmct
@ -47,6 +50,7 @@ define([
};
openmct.indicators.add(indicator);
}
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
};

View File

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

View File

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

View File

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

View File

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

View File

@ -127,7 +127,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
if (validatedData) {
if (this.isStalenessCheck()) {
if (this.stalenessSubscription[validatedData.id]) {
if (this.stalenessSubscription && this.stalenessSubscription[validatedData.id]) {
this.stalenessSubscription[validatedData.id].update(validatedData);
}
this.telemetryDataCache[validatedData.id] = false;

View File

@ -142,12 +142,14 @@ export default class TelemetryCriterion extends EventEmitter {
};
}
let telemetryObject = this.telemetryObject;
return this.openmct.telemetry.request(
this.telemetryObject,
options
).then(results => {
const latestDatum = results.length ? results[results.length - 1] : {};
const normalizedDatum = this.createNormalizedDatum(latestDatum, this.telemetryObject);
const normalizedDatum = this.createNormalizedDatum(latestDatum, telemetryObject);
return {
id: this.id,

View File

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

View File

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

View File

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

View File

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

View File

@ -25,14 +25,14 @@
class="l-layout__frame c-frame"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable
'u-inspectable': inspectable,
'is-in-small-container': size.width < 600 || size.height < 600
}"
:style="style"
>
<slot></slot>
<div
class="c-frame-edit__move"
class="c-frame__move-bar"
@mousedown="isEditing ? startMove([1,1], [0,0], $event) : null"
></div>
</div>
@ -61,6 +61,13 @@ export default {
}
},
computed: {
size() {
let {width, height} = this.item;
return {
width: (this.gridSize[0] * width),
height: (this.gridSize[1] * height)
};
},
style() {
let {x, y, width, height} = this.item;
return {

View File

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

View File

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

View File

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

View File

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

View File

@ -7,9 +7,13 @@
> *:first-child {
flex: 1 1 auto;
}
&.is-in-small-container {
//background: rgba(blue, 0.1);
}
}
.c-frame-edit__move {
.c-frame__move-bar {
display: none;
}
@ -29,7 +33,7 @@
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw;
.c-frame-edit__move {
.c-frame__move-bar {
cursor: move;
}
}
@ -37,7 +41,7 @@
/******************* DEFAULT STYLES FOR -EDIT__MOVE */
// All object types
.c-frame-edit__move {
.c-frame__move-bar {
@include abs();
display: block;
}
@ -52,7 +56,7 @@
transition-delay: $moveBarOutDelay;
}
+ .c-frame-edit__move {
+ .c-frame__move-bar {
display: none;
}
@ -61,14 +65,14 @@
.l-layout {
/******************* 0 - 1 ITEM SELECTED */
&:not(.is-multi-selected) {
> .l-layout__frame[s-selected] {
> .l-layout__frame {
> .c-so-view.has-complex-content {
> .c-so-view__local-controls {
transition: transform $transOutTime ease-in-out;
transition-delay: $moveBarOutDelay;
}
+ .c-frame-edit__move {
+ .c-frame__move-bar {
transition: $transOut;
transition-delay: $moveBarOutDelay;
@include userSelectNone();
@ -89,7 +93,7 @@
$lrOffset: 25%;
@include grippy($editFrameMovebarColorFg);
content: '';
display: block;
display: none;
position: absolute;
top: $tbOffset;
right: $lrOffset;
@ -111,7 +115,7 @@
transition-delay: 0s;
}
+ .c-frame-edit__move {
+ .c-frame__move-bar {
transition: $transIn;
transition-delay: 0s;
height: $editFrameMovebarH;
@ -119,12 +123,19 @@
}
}
}
> .l-layout__frame[s-selected] {
> .c-so-view.has-complex-content {
+ .c-frame__move-bar:before {
display: block;
}
}
}
}
/******************* > 1 ITEMS SELECTED */
&.is-multi-selected {
.l-layout__frame[s-selected] {
> .c-so-view.has-complex-content + .c-frame-edit__move {
> .c-so-view.has-complex-content + .c-frame__move-bar {
display: block;
}
}

View File

@ -26,4 +26,15 @@
@include abs();
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>
<a
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"
>
<div
class="c-grid-item__type-icon"
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'"
></div>
>
</div>
<div class="c-grid-item__details">
<!-- Name and metadata -->
<div
@ -22,6 +27,9 @@
</div>
</div>
<div class="c-grid-item__controls">
<div class="is-missing__indicator"
title="This item is missing"
></div>
<div
class="icon-people"
title="Shared"

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@
&:hover {
background: $colorListItemBgHov;
filter: $filterHov;
transition: $transIn;
}
}

View File

@ -31,11 +31,11 @@
<div class="c-imagery__control-bar">
<div class="c-imagery__timestamp">{{ getTime() }}</div>
<div class="h-local-controls flex-elem">
<a
<button
class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
></a>
></button>
</div>
</div>
</div>
@ -228,7 +228,7 @@ export default {
subscribe() {
this.unsubscribe = this.openmct.telemetry
.subscribe(this.domainObject, (datum) => {
let parsedTimestamp = this.timeFormat.parse(datum[this.timeKey]),
let parsedTimestamp = this.timeFormat.parse(datum),
bounds = this.openmct.time.bounds();
if(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {

View File

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

View File

@ -24,14 +24,14 @@ import uuid from 'uuid';
export default class NewFolderAction {
constructor(openmct) {
this.name = 'New Folder';
this.name = 'Add New Folder';
this.key = 'newFolder';
this.description = 'Create a new folder';
this.cssClass = 'icon-folder';
this.cssClass = 'icon-folder-new';
this._openmct = openmct;
this._dialogForm = {
name: "New Folder Name",
name: "Add New Folder",
sections: [
{
rows: [
@ -39,7 +39,9 @@ export default class NewFolderAction {
key: "name",
control: "textfield",
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'),
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,
identifier = {
key: uuid(),

View File

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

View File

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

View File

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

View File

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

View File

@ -371,7 +371,8 @@ function (
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
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)
]),
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 () {

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -54,7 +54,8 @@ define([
'./themes/snow',
'./URLTimeSettingsSynchronizer/plugin',
'./notificationIndicator/plugin',
'./newFolderAction/plugin'
'./newFolderAction/plugin',
'./defaultRootName/plugin'
], function (
_,
UTCTimeSystem,
@ -89,7 +90,8 @@ define([
Snow,
URLTimeSettingsSynchronizer,
NotificationIndicator,
NewFolderAction
NewFolderAction,
DefaultRootName
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
@ -201,6 +203,7 @@ define([
plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer.default;
plugins.NotificationIndicator = NotificationIndicator.default;
plugins.NewFolderAction = NewFolderAction.default;
plugins.DefaultRootName = DefaultRootName.default;
return plugins;
});

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

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

View File

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

View File

@ -74,6 +74,10 @@ define([], function () {
return this.cellLimitClasses;
}
getContextualDomainObject(openmct, objectKeyString) {
return openmct.objects.get(objectKeyString);
}
getContextMenuActions() {
return [];
}

View File

@ -171,7 +171,7 @@ export default {
showContextMenu: function (event) {
event.preventDefault();
this.openmct.objects.get(this.row.objectKeyString).then((domainObject) => {
this.row.getContextualDomainObject(this.openmct, this.row.objectKeyString).then(domainObject => {
let contextualObjectPath = this.objectPath.slice();
contextualObjectPath.unshift(domainObject);

View File

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

View File

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

View File

@ -3,8 +3,8 @@
}
@keyframes rotation-centered {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes clock-hands {

View File

@ -80,10 +80,14 @@ $uiColor: #0093ff; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #ccc;
$colorAHov: #fff;
$filterHov: brightness(1.3); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 10%);
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
$colorSelectedBg: rgba($colorKey, 0.3);
$colorSelectedFg: pullForward($colorBodyFg, 20%);
// Object labels
$objectLabelTypeIconOpacity: 0.7;
$objectLabelNameFilter: brightness(1.3);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
@ -94,7 +98,7 @@ $sideBarHeaderBg: rgba($colorBodyFg, 0.2);
$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #999;
$colorStatusFg: #888;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusInfoFilter: invert(58%) sepia(44%) saturate(405%) hue-rotate(85deg) brightness(102%) contrast(92%);
@ -151,6 +155,10 @@ $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1);
$filterItemMissing: brightness(0.6) grayscale(1);
$opacityMissing: 0.5;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */
$editUIColor: $uiColor; // Base color
@ -201,9 +209,9 @@ $colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBg: $colorStatusAlert;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorBtnCautionFg: $colorBtnBg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
@ -331,7 +339,7 @@ $shdwItemText: none;
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBodyBg: $colorBodyBg;
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
$colorTabHeaderBg: rgba($colorBodyFg, 0.15);
$colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
@ -349,17 +357,18 @@ $stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
$legendTableHeadBg: $colorTabHeaderBg;
// Tree
$colorTreeBg: transparent;
$colorItemTreeHoverBg: rgba(white, 0.07);
$colorItemTreeHoverFg: pullForward($colorBodyFg, 20%);
$colorItemTreeHoverBg: rgba(#fff, 0.03);
$colorItemTreeHoverFg: #fff;
$colorItemTreeIcon: $colorKey; // Used
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
$filterItemTreeSelected: $filterHov;
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
$colorItemTreeEditingFg: $editUIColor;
@ -394,7 +403,7 @@ $splitterBtnColorBg: $colorBtnBg;
$splitterBtnColorFg: #999;
$splitterBtnLabelColorFg: #666;
$splitterCollapsedBtnColorBg: #222;
$splitterCollapsedBtnColorFg: #666;
$splitterCollapsedBtnColorFg: #555;
$splitterCollapsedBtnColorBgHov: $colorKey;
$splitterCollapsedBtnColorFgHov: $colorKeyFg;

View File

@ -80,14 +80,18 @@ $colorKeyHov: #26d8ff;
$colorKeyFilter: invert(36%) sepia(76%) saturate(2514%) hue-rotate(170deg) brightness(99%) contrast(101%);
$colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%) contrast(100%);
$colorKeySelectedBg: $colorKey;
$uiColor: #00b2ff; // Resize bars, splitter bars, etc.
$uiColor: #0093ff; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #ccc;
$colorAHov: #fff;
$filterHov: brightness(1.3); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 10%);
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
$colorSelectedBg: rgba($colorKey, 0.3);
$colorSelectedFg: pullForward($colorBodyFg, 20%);
// Object labels
$objectLabelTypeIconOpacity: 0.7;
$objectLabelNameFilter: brightness(1.3);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
@ -155,6 +159,10 @@ $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(1.2) contrast(1.1);
$filterItemMissing: contrast(0.2);
$opacityMissing: 0.5;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */
$editUIColor: $uiColor; // Base color
@ -205,9 +213,9 @@ $colorBtnMajorBg: $colorKey;
$colorBtnMajorBgHov: $colorKeyHov;
$colorBtnMajorFg: $colorKeyFg;
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBg: $colorStatusAlert;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorBtnCautionFg: $colorBtnBg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
@ -335,7 +343,7 @@ $shdwItemText: none;
$colorTabBorder: pullForward($colorBodyBg, 10%);
$colorTabBodyBg: $colorBodyBg;
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
$colorTabHeaderBg: rgba($colorBodyFg, 0.15);
$colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
@ -357,13 +365,14 @@ $legendTableHeadBg: rgba($colorBodyFg, 0.15);
// Tree
$colorTreeBg: transparent;
$colorItemTreeHoverBg: rgba(white, 0.07);
$colorItemTreeHoverFg: pullForward($colorBodyFg, 20%);
$colorItemTreeHoverBg: rgba(#fff, 0.03);
$colorItemTreeHoverFg: #fff;
$colorItemTreeIcon: $colorKey; // Used
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
$filterItemTreeSelected: $filterHov;
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
$colorItemTreeEditingFg: $editUIColor;
@ -398,7 +407,7 @@ $splitterBtnColorBg: $colorBtnBg;
$splitterBtnColorFg: #999;
$splitterBtnLabelColorFg: #666;
$splitterCollapsedBtnColorBg: #222;
$splitterCollapsedBtnColorFg: #666;
$splitterCollapsedBtnColorFg: #555;
$splitterCollapsedBtnColorBgHov: $colorKey;
$splitterCollapsedBtnColorFgHov: $colorKeyFg;

View File

@ -78,19 +78,23 @@ $colorKeyFilterHov: invert(69%) sepia(87%) saturate(3243%) hue-rotate(151deg) br
$colorKeySelectedBg: $colorKey;
$uiColor: #289fec; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #999;
$colorA: $colorBodyFg;
$colorAHov: $colorKey;
$filterHov: brightness(1.3); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 40%);
$filterHov: brightness(0.8) contrast(2); // Tree, location items
$colorSelectedBg: rgba($colorKey, 0.2);
$colorSelectedFg: pullForward($colorBodyFg, 10%);
// Object labels
$objectLabelTypeIconOpacity: 0.5;
$objectLabelNameFilter: brightness(0.5);
// Layout
$shellMainPad: 4px 0;
$shellPanePad: $interiorMargin, 7px;
$drawerBg: darken($colorBodyBg, 5%);
$drawerFg: darken($colorBodyFg, 5%);
$sideBarBg: $drawerBg;
$sideBarHeaderBg: rgba(black, 0.25);
$sideBarHeaderBg: rgba(black, 0.1);
$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
// Status colors, mainly used for messaging and item ancillary symbols
@ -151,6 +155,10 @@ $browseFrameColor: pullForward($colorBodyBg, 10%);
$browseFrameBorder: 1px solid $browseFrameColor; // Frames in Disp and Flex Layouts when frame is showing
$browseSelectableShdwHov: rgba($colorBodyFg, 0.3) 0 0 3px;
$browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
$filterItemHoverFg: brightness(0.9);
$filterItemMissing: contrast(0.2);
$opacityMissing: 0.4;
$borderMissing: 1px dashed $colorAlert !important;
/************************************************** EDITING */
$editUIColor: $uiColor; // Base color
@ -220,7 +228,7 @@ $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7);
$btnStdH: 24px;
$colorCursorGuide: rgba(black, 0.6);
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
$colorLocalControlOvrBg: rgba($colorBodyFg, 0.8);
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
$colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects
$colorSelectFg: $colorBtnFg;
$colorSelectArw: lighten($colorBtnBg, 20%);
@ -360,7 +368,8 @@ $colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
$filterItemTreeSelected: contrast(1.4);
$colorItemTreeSelectedIcon: $colorItemTreeIcon;
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
$colorItemTreeEditingFg: $editUIColor;
$colorItemTreeEditingIcon: $editUIColor;

View File

@ -40,11 +40,11 @@ $inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
$menuLineH: 1.5rem;
$treeItemIndent: 16px;
$treeTypeIconW: 18px;
$overlayOuterMarginLg: 5%;
$overlayOuterMarginFullscreen: 0%;
$overlayOuterMarginDialog: 20%;
$overlayInnerMargin: 25px;
$mainViewPad: 2px;
$mainViewPad: 0px;
$treeNavArrowD: 20px;
/*************** Items */
$itemPadLR: 5px;
$gridItemDesk: 175px;
@ -57,11 +57,11 @@ $tabularTdPadTB: 2px;
$plotYBarW: 60px;
$plotYLabelMinH: 20px;
$plotYLabelW: 10px;
$plotXBarH: 35px;
$plotXBarH: 32px;
$plotLegendH: 20px;
$plotLegendWidthCollapsed: 20%;
$plotLegendWidthExpanded: 50%;
$plotSwatchD: 10px;
$plotSwatchD: 12px;
$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW); // 1: Top, 2: right, 3: bottom, 4: left
$plotMinH: 95px;
$controlBarH: 25px;
@ -82,14 +82,16 @@ $formLabelMinW: 120px;
$formLabelW: 30%;
/*************** Wait Spinner */
$waitSpinnerD: 32px;
$waitSpinnerTreeD: 20px;
$waitSpinnerBorderW: 5px;
$waitSpinnerTreeD: 20px;
$waitSpinnerTreeBorderW: 3px;
/*************** Messages */
$messageIconD: 80px;
$messageListIconD: 32px;
/*************** Tables */
$tableResizeColHitareaD: 6px;
/*************** Misc */
$drawingObjBorderW: 3px;
/************************** MOBILE */
$mobileMenuIconD: 24px; // Used

View File

@ -57,11 +57,6 @@ button {
line-height: 90%;
padding: 3px 10px;
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
@include desktop() {
font-size: 6px;
}
@ -102,7 +97,8 @@ button {
}
}
.c-click-link {
.c-click-link,
.c-icon-link {
// A clickable element, typically inline, with an icon and label
@include cControl();
cursor: pointer;
@ -117,8 +113,15 @@ button {
}
}
.c-icon-link {
&:before {
// Icon
//color: $colorBtnMajorBg;
}
}
.c-icon-button {
.c-icon-button__label {
&__label {
margin-left: $interiorMargin;
}
@ -411,7 +414,17 @@ select {
.c-tab {
// Used in Tab View, generic tabs
background: $colorBtnBg;
$notchSize: 7px;
$clipPath:
polygon(
0% 0%,
calc(100% - #{$notchSize}) 0%,
100% #{$notchSize},
100% calc(100% - #{$notchSize}),
100% 100%,
0% 100%
);
background: rgba($colorBtnBg, 0.7);
color: $colorBtnFg;
cursor: pointer;
display: flex;
@ -420,21 +433,15 @@ select {
margin: 1px 1px 0 0;
padding: $interiorMargin $interiorMarginLg;
white-space: nowrap;
clip-path: $clipPath;
-webkit-clip-path: $clipPath; // Safari
--notchSize: 7px;
clip-path:
polygon(
0% 0%,
calc(100% - var(--notchSize)) 0%,
100% var(--notchSize),
100% calc(100% - var(--notchSize)),
100% 100%,
0% 100%
);
> * + * {
margin-left: $interiorMargin;
}
@include hover() {
background: $colorBtnBgHov;
filter: $filterHov;
}
&.is-current {

View File

@ -101,8 +101,9 @@ a {
color: $colorA;
cursor: pointer;
text-decoration: none;
&:hover {
color: $colorAHov;
&:focus {
outline: none !important;
}
}
@ -206,10 +207,6 @@ body.desktop .has-local-controls {
&:hover {
box-shadow: $browseSelectableShdwHov;
}
&[s-selected] {
border: $browseSelectedBorder;
}
}
/**************************** EDITING */
@ -284,19 +281,23 @@ body.desktop .has-local-controls {
display: flex;
align-items: center;
padding-left: $spinnerL + $d/2 + $interiorMargin;
background: $colorLoadingBg;
margin-left: $treeNavArrowD + $interiorMargin;
min-height: 5px + $d;
.c-tree__item__label {
font-style: italic;
margin-left: $interiorMargin;
opacity: 0.6;
}
&:before {
left: auto;
top: auto;
transform: translate(0);
height: $d;
width: $d;
border-width: 4px;
left: $spinnerL;
border-width: 3px;
//left: $spinnerL;
position: relative;
}
&:after {
display: none;

View File

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

View File

@ -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) {
background-image: linear-gradient(-45deg,
rgba($c, $a) 25%, transparent 25%,
@ -416,28 +443,18 @@
}
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
filter: $filterHov;
}
&[class*="--major"],
&[class*='is-active']{
background: $colorBtnMajorBg;
color: $colorBtnMajorFg;
@include hover() {
background: $colorBtnMajorBgHov;
color: $colorBtnMajorFgHov;
}
}
&[class*='--caution'] {
background: $colorBtnCautionBg;
color: $colorBtnCautionFg;
&:hover {
background: $colorBtnCautionBgHov;
}
background: $colorBtnCautionBg !important;
color: $colorBtnCautionFg !important;
}
}

View File

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

View File

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

View File

@ -24,13 +24,24 @@
class="c-so-view has-local-controls"
:class="{
'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-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">
{{ domainObject && domainObject.name }}
</div>
@ -46,6 +57,9 @@
@click="expand"
></button>
</div>
<div class="is-missing__indicator"
title="This item is missing"
></div>
<object-view
ref="objectView"
class="c-so-view__object-view"
@ -128,8 +142,11 @@ export default {
getOverlayElement(childElement) {
const fragment = new DocumentFragment();
const header = this.getPreviewHeader();
const wrapper = document.createElement('div');
wrapper.classList.add('l-preview-window__object-view');
wrapper.append(childElement);
fragment.append(header);
fragment.append(childElement);
fragment.append(wrapper);
return fragment;
},

View File

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

View File

@ -53,7 +53,9 @@ export default {
mounted() {
this.currentObject = this.object;
this.updateView();
this.$el.addEventListener('dragover', this.onDragOver);
this.$el.addEventListener('dragover', this.onDragOver, {
capture: true
});
this.$el.addEventListener('drop', this.editIfEditable, {
capture: true
});
@ -140,7 +142,7 @@ export default {
}
this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('u-angular-object-view-wrapper');
this.viewContainer.classList.add('l-angular-ov-wrapper');
this.$el.append(this.viewContainer);
let provider = this.getViewProvider();
if (!provider) {
@ -269,6 +271,7 @@ export default {
if (provider &&
provider.canEdit &&
provider.canEdit(this.currentObject) &&
this.isEditingAllowed() &&
!this.openmct.editor.isEditing()) {
this.openmct.editor.edit();
}
@ -301,7 +304,7 @@ export default {
objectPath= this.currentObjectPath || this.objectPath,
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,29 +2,29 @@
display: flex;
flex-direction: column;
&.is-missing {
border: $borderMissing;
}
/*************************** HEADER */
&__header {
flex: 0 0 auto;
display: flex;
font-size: 1.05em;
align-items: center;
margin-bottom: $interiorMargin;
&__icon {
flex: 0 0 auto;
margin-right: $interiorMarginSm;
opacity: 0.5;
}
margin-bottom: $interiorMarginSm;
padding: 1px 2px;
.c-object-label {
&__name {
@include headerFont(1em);
@include ellipsize();
flex: 0 1 auto;
filter: $objectLabelNameFilter;
}
}
}
&:not(.c-so-view--no-frame) {
border: $browseFrameBorder;
padding: $interiorMargin;
padding: 0 $interiorMarginSm;
.is-editing & {
background: rgba($colorBodyBg, 0.8);
@ -36,15 +36,30 @@
display: none;
}
> .c-so-view__local-controls {
top: $interiorMarginSm; right: $interiorMarginSm;
&.is-missing {
@include isMissing($absPos: true);
.is-missing__indicator {
top: $interiorMargin;
left: $interiorMargin;
}
}
}
&__local-controls {
// View Large button
box-shadow: $colorLocalControlOvrBg 0 0 0 2px;
position: absolute;
top: $interiorMargin; right: $interiorMargin;
z-index: 2;
z-index: 10;
}
.c-click-icon,
.c-button {
// Shrink buttons a bit when they appear in a frame
border-radius: $smallCr !important;
font-size: 0.9em;
padding: 3px 5px;
}
&__view-large {
@ -55,13 +70,11 @@
> .c-so-view__view-large { display: block; }
}
/*************************** OBJECT VIEW */
&__object-view {
flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
.u-angular-object-view-wrapper {
.u-fills-container {
// Expand component types that fill a container
@include abs();
@ -69,17 +82,8 @@
}
}
.c-click-icon,
.c-button {
// Shrink buttons a bit when they appear in a frame
font-size: 0.9em;
padding: 3px 5px;
}
}
.u-angular-object-view-wrapper {
flex: 1 1 auto;
.l-angular-ov-wrapper {
// This element is the recipient for object styling; cannot be display: contents
height: 100%;
width: 100%;
overflow: hidden;
}

View File

@ -7,19 +7,34 @@
overflow: hidden;
white-space: nowrap;
> * + * { margin-left: $interiorMargin; }
&__name {
@include ellipsize();
display: inline;
}
&__type-icon,
&:before {
&__type-icon {
// Type icon. Must be an HTML entity to allow inclusion of alias indicator.
display: block;
flex: 0 0 auto;
font-size: 1.1em;
opacity: 0.6;
margin-right: $interiorMargin;
opacity: $objectLabelTypeIconOpacity;
}
&.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 +42,8 @@
border-radius: $controlCr;
padding: $interiorMarginSm 1px;
> * + * { margin-left: $interiorMarginSm; }
&__name {
display: inline;
width: 100%;

View File

@ -1,10 +1,10 @@
<template>
<span
class="c-disclosure-triangle"
:class="{
'c-disclosure-triangle--expanded' : value,
'is-enabled' : enabled
}"
:class="[
controlClass,
{ 'c-disclosure-triangle--expanded' : value },
{'is-enabled' : enabled }
]"
@click="handleClick"
></span>
</template>
@ -25,6 +25,10 @@ export default {
propagate: {
type: Boolean,
default: true
},
controlClass: {
type: String,
default: 'c-disclosure-triangle'
}
},
methods: {

View File

@ -1,19 +1,30 @@
<template>
<div class="c-inspector__header">
<div v-if="!multiSelect && !singleSelectNonObject"
class="c-inspector__selected-w c-object-label"
<div v-if="!multiSelect"
class="c-inspector__selected c-object-label"
:class="{'is-missing': domainObject.status === 'missing' }"
>
<div class="c-object-label__type-icon"
: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>
<span v-if="!singleSelectNonObject"
class="c-inspector__selected c-object-label__name"
>{{ item.name }}</span>
<div v-if="singleSelectNonObject"
class="c-inspector__selected-w c-object-label"
:class="typeCssClass"
class="c-inspector__selected c-inspector__selected--non-domain-object c-object-label"
>
<span class="c-inspector__selected c-object-label__name c-inspector__selected--non-domain-object">Layout Object</span>
<span class="c-object-label__type-icon"
:class="typeCssClass"
></span>
<span class="c-object-label__name">Layout Object</span>
</div>
</div>
<div v-if="multiSelect"
class="c-inspector__multiple-selected-w"
class="c-inspector__multiple-selected"
>
{{ itemsSelected }} items selected
</div>

View File

@ -4,17 +4,17 @@
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;
}
&__selected-w,
&__multiple-selected-w {
&__selected,
&__multiple-selected {
@include headerFont(1.1em);
padding: $interiorMarginSm 0;
}
&__multiple-selected-w {
&__multiple-selected {
$p: $interiorMarginLg;
background: rgba($colorWarningLo, 0.3);
border-radius: $basicCr;
@ -25,10 +25,11 @@
}
&__selected {
@include ellipsize();
flex: 1 1 auto;
.c-object-label__name {
filter: $objectLabelNameFilter;
}
&--non-domain-object {
&--non-domain-object .c-object-label__name {
font-style: italic;
}
}
@ -41,6 +42,8 @@
&__content {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
&__elements {

View File

@ -3,13 +3,24 @@
<div class="l-browse-bar__start">
<button
v-if="hasParent"
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-pointer-left"
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-arrow-nav-to-parent"
title="Navigate up to parent"
@click="goToParent"
></button>
<div
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
class="l-browse-bar__object-name c-object-label__name c-input-inline"
contenteditable
@ -45,7 +56,7 @@
:title="lockedOrUnlocked"
class="c-button"
:class="{
'icon-lock': domainObject.locked,
'icon-lock c-button--caution': domainObject.locked,
'icon-unlocked': !domainObject.locked
}"
@click="toggleLock(!domainObject.locked)"

View File

@ -15,7 +15,9 @@
<CreateButton class="l-shell__create-button" />
<indicators class="l-shell__head-section l-shell__indicators" />
<button
class="l-shell__head__collapse-button c-button"
class="l-shell__head__collapse-button c-icon-button"
:class="headExpanded ? 'l-shell__head__collapse-button--collapse' : 'l-shell__head__collapse-button--expand'"
:title="`Click to ${headExpanded ? 'collapse' : 'expand'} items`"
@click="toggleShellHead"
></button>
<notification-banner />
@ -47,12 +49,23 @@
label="Browse"
collapsable
>
<mct-tree class="l-shell__tree" />
<button
slot="controls"
class="c-icon-button l-shell__sync-tree-button icon-target"
title="Show selected item in tree"
@click="handleSyncTreeNavigation"
>
</button>
<mct-tree
:sync-tree-navigation="triggerSync"
class="l-shell__tree"
/>
</pane>
<pane class="l-shell__pane-main">
<browse-bar
ref="browseBar"
class="l-shell__main-view-browse-bar"
@sync-tree-navigation="handleSyncTreeNavigation"
/>
<toolbar
v-if="toolbar"
@ -154,7 +167,8 @@ export default {
conductorComponent: undefined,
isEditing: false,
hasToolbar: false,
headExpanded
headExpanded,
triggerSync: false
}
},
computed: {
@ -204,6 +218,9 @@ export default {
}
this.hasToolbar = structure.length > 0;
},
handleSyncTreeNavigation() {
this.triggerSync = !this.triggerSync;
}
}
}

View File

@ -52,7 +52,7 @@
color: $colorKey !important;
position: absolute;
right: -18px;
top: 0;
top: $interiorMarginSm;
transform: translateX(100%);
width: $mobileMenuIconD;
z-index: 2;
@ -100,6 +100,11 @@
&__pane-tree {
background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3));
[class*="expand-button"],
[class*="sync-tree-button"] {
display: none;
}
&[class*="--collapsed"] {
[class*="collapse-button"] {
right: -8px;
@ -153,7 +158,7 @@
}
&__head {
align-items: stretch;
align-items: center;
background: $colorHeadBg;
justify-content: space-between;
padding: $interiorMargin $interiorMargin + 2;
@ -162,14 +167,21 @@
margin-left: $interiorMargin;
}
[class*='__head__collapse-button'] {
align-self: start;
.l-shell__head__collapse-button {
color: $colorBtnMajorBg;
flex: 0 0 auto;
margin-top: 6px;
font-size: 0.9em;
&--collapse {
&:before {
content: $glyph-icon-arrow-down;
font-size: 1.1em;
content: $glyph-icon-items-collapse;
}
}
&--expand {
&:before {
content: $glyph-icon-items-expand;
}
}
}
@ -184,12 +196,6 @@
.c-indicator__label {
transition: none !important;
}
[class*='__head__collapse-button'] {
&:before {
transform: rotate(180deg);
}
}
}
}
@ -304,6 +310,10 @@
display: inline-flex;
}
> * + * {
margin-left: $interiorMarginSm;
}
&__start,
&__end,
&__actions {
@ -313,6 +323,13 @@
&__actions,
&__end {
.c-button {
&[class*='icon-']:before {
min-width: 1em;
text-align: center;
}
}
> * + * {
margin-left: $interiorMargin;
}
@ -320,8 +337,12 @@
&__start {
flex: 1 1 auto;
margin-right: $interiorMargin;
//margin-right: $interiorMargin;
min-width: 0; // Forces interior to compress when pushed on
[class*='button'] {
flex: 0 0 auto;
}
}
&__end {
@ -330,15 +351,15 @@
&__nav-to-parent-button,
&__disclosure-button {
flex: 0 0 auto;
//flex: 0 0 auto;
}
&__nav-to-parent-button {
// This is an icon-button
$p: $interiorMargin;
margin-right: $interiorMargin;
padding-left: $p;
padding-right: $p;
//$p: $interiorMargin;
//margin-right: $interiorMargin;
//padding-left: $p;
//padding-right: $p;
.is-editing & {
display: none;
@ -350,13 +371,18 @@
flex: 0 1 auto;
}
.c-object-label__name {
filter: $objectLabelNameFilter;
}
&__object-name--w {
@include headerFont(1.4em);
@include headerFont(1.5em);
margin-left: $interiorMarginLg;
min-width: 0;
&:before {
// Icon
margin-right: $interiorMargin;
.is-missing__indicator {
right: -5px !important;
top: -4px !important;
}
}

View File

@ -12,10 +12,6 @@
flex: 0 0 auto;
}
&__loading {
flex: 1 1 auto;
}
&__no-results {
font-style: italic;
opacity: 0.6;
@ -26,6 +22,33 @@
height: 0; // Chrome 73 overflow bug fix
padding-right: $interiorMarginSm;
}
.c-tree {
flex: 1 1 auto;
overflow: hidden;
transition: all;
.scrollable-children {
.c-tree__item-h {
width: 100%;
}
}
&__item--empty {
// Styling for empty tree items
// Indent should allow for c-nav view-control width and icon spacing
font-style: italic;
padding: $interiorMarginSm * 2 1px;
opacity: 0.7;
pointer-events: none;
&:before {
content: '';
display: inline-block;
width: $treeNavArrowD + $interiorMarginLg;
}
}
}
}
.c-tree,
@ -43,7 +66,6 @@
}
&__item {
$aPad: $interiorMarginSm;
border-radius: $controlCr;
display: flex;
align-items: center;
@ -52,12 +74,13 @@
padding: $interiorMarginSm $interiorMargin;
transition: background 150ms ease;
&__type-icon {
color: $colorItemTreeIcon;
}
&:hover {
background: $colorItemTreeHoverBg;
[class*="__name"] {
color: $colorItemTreeHoverFg;
}
filter: $filterHov;
}
&.is-navigated-object,
@ -81,28 +104,9 @@
margin-left: $interiorMarginSm;
}
@include desktop {
&:hover {
.c-tree__item__type-icon:before {
color: $colorItemTreeIconHover;
}
}
&.is-navigated-object,
&.is-selected {
.c-tree__item__type-icon:before {
color: $colorItemTreeIconHover;
}
}
&.is-being-edited {
background: $colorItemTreeEditingBg;
.c-tree__item__type-icon:before {
color: $colorItemTreeEditingIcon;
}
.c-tree__item__name {
color: $colorItemTreeEditingFg;
font-style: italic;
background: $colorItemTreeHoverBg;
}
}
@ -111,14 +115,6 @@
flex: 1 1 auto;
}
&__name {
color: $colorItemTreeFg;
}
&__type-icon {
color: $colorItemTreeIcon;
}
&.is-alias {
// Object is an alias to an original.
[class*='__type-icon'] {
@ -134,6 +130,62 @@
width: ceil($mobileTreeItemH * 0.5);
}
}
&.is-navigated-object,
&.is-selected {
background: $colorItemTreeSelectedBg;
[class*="__label"],
[class*="__name"] {
color: $colorItemTreeSelectedFg;
}
[class*="__type-icon"]:before {
color: $colorItemTreeSelectedIcon;
}
}
&.is-navigated-object {
[class*="__label"],
[class*="__name"] {
pointer-events: none;
}
}
}
&__item__label {
@include desktop {
&:hover {
filter: $filterHov;
}
}
}
}
.is-editing .is-navigated-object {
a[class*="__item__label"] {
opacity: 0.4;
[class*="__name"] {
font-style: italic;
}
}
}
.c-tree {
&__item {
body.mobile & {
@include button($bg: $colorMobilePaneLeftTreeItemBg, $fg: $colorMobilePaneLeftTreeItemFg);
height: $mobileTreeItemH;
margin-bottom: $interiorMarginSm;
[class*="view-control"] {
width: ceil($mobileTreeItemH * 0.5);
}
}
}
.c-tree {
margin-left: $treeItemIndent;
}
}
@ -150,6 +202,51 @@
}
}
.c-nav {
$dimension: $treeNavArrowD;
&__up, &__down {
flex: 0 0 auto;
height: $dimension;
width: $dimension;
visibility: hidden;
position: relative;
text-align: center;
&.is-enabled {
visibility: visible;
}
&:before {
// Nav arrow
$dimension: 9px;
$width: 3px;
border: solid $colorItemTreeVC;
border-width: 0 $width $width 0;
content: '';
display: block;
position: absolute;
left: 50%; top: 50%;
height: $dimension;
width: $dimension;
}
@include desktop {
&:hover:before {
border-color: $colorItemTreeHoverFg;
}
}
}
&__up:before {
transform: translate(-30%, -50%) rotate(135deg);
}
&__down:before {
transform: translate(-70%, -50%) rotate(-45deg);
}
}
.c-selector {
.c-tree-and-search__tree.c-tree {
border: 1px solid $colorInteriorBorder;
@ -157,3 +254,32 @@
padding: $interiorMargin;
}
}
// TRANSITIONS
.slide-left,
.slide-right {
animation-duration: 500ms;
animation-iteration-count: 1;
transition: all;
transition-timing-function: ease-in-out;
}
.slide-left {
animation-name: animSlideLeft;
}
.slide-right {
animation-name: animSlideRight;
}
@keyframes animSlideLeft {
0% {opactiy: 0; transform: translateX(100%);}
10% {opacity: 1;}
100% {transform: translateX(0);}
}
@keyframes animSlideRight {
0% {opactiy: 0; transform: translateX(-100%);}
10% {opacity: 1;}
100% {transform: translateX(0);}
}

View File

@ -1,5 +1,8 @@
<template>
<div class="c-tree-and-search">
<div
class="c-tree-and-search"
>
<div class="c-tree-and-search__search">
<search
ref="shell-search"
@ -10,15 +13,8 @@
/>
</div>
<!-- loading -->
<div
v-if="isLoading"
class="c-tree-and-search__loading loading"
></div>
<!-- end loading -->
<div
v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)"
v-if="(searchValue && allTreeItems.length === 0 && !isLoading) || (searchValue && searchResultItems.length === 0)"
class="c-tree-and-search__no-results"
>
No results found
@ -26,30 +22,72 @@
<!-- main tree -->
<ul
v-if="!isLoading"
v-show="!searchValue"
ref="mainTree"
class="c-tree-and-search__tree c-tree"
>
<!-- ancestors -->
<div v-if="!activeSearch">
<tree-item
v-for="treeItem in allTreeItems"
v-for="(ancestor, index) in ancestors"
:key="ancestor.id"
:node="ancestor"
:show-up="index < ancestors.length - 1"
:show-down="false"
:left-offset="index * 10 + 'px'"
:emit-height="getChildHeight"
@emittedHeight="setChildHeight"
@resetTree="handleReset"
/>
<!-- loading -->
<li
v-if="isLoading"
class="c-tree__item c-tree-and-search__loading loading"
>
<span class="c-tree__item__label">Loading...</span>
</li>
<!-- end loading -->
</div>
<!-- currently viewed children -->
<transition
@enter="childrenIn"
>
<li
v-if="!isLoading"
:class="childrenSlideClass"
:style="childrenListStyles()"
>
<ul
ref="scrollable"
class="scrollable-children"
:style="scrollableStyles()"
@scroll="scrollItems"
>
<div :style="{ height: childrenHeight + 'px'}">
<tree-item
v-for="(treeItem, index) in visibleItems"
:key="treeItem.id"
:node="treeItem"
:left-offset="itemLeftOffset"
:item-offset="itemOffset"
:item-index="index"
:item-height="itemHeight"
:virtual-scroll="!noScroll"
:show-down="activeSearch ? false : true"
@expanded="handleExpanded"
/>
<li
v-if="visibleItems.length === 0"
:style="emptyStyles()"
class="c-tree__item c-tree__item--empty"
>
No items
</li>
</div>
</ul>
</li>
</transition>
</ul>
<!-- end main tree -->
<!-- search tree -->
<ul
v-if="searchValue"
class="c-tree-and-search__tree c-tree"
>
<tree-item
v-for="treeItem in filteredTreeItems"
:key="treeItem.id"
:node="treeItem"
/>
</ul>
<!-- end search tree -->
</div>
</template>
@ -57,6 +95,14 @@
import treeItem from './tree-item.vue'
import search from '../components/search.vue';
const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded';
const ROOT_PATH = '/browse/';
const ITEM_BUFFER = 5;
const RECHECK_DELAY = 100;
const RESIZE_FIRE_DELAY_MS = 500;
let windowResizeId = undefined;
let windowResizing = false;
export default {
inject: ['openmct'],
name: 'MctTree',
@ -64,53 +110,383 @@ export default {
search,
treeItem
},
data() {
return {
searchValue: '',
allTreeItems: [],
filteredTreeItems: [],
isLoading: false
props: {
syncTreeNavigation: {
type: Boolean,
required: true
}
},
mounted() {
data() {
let isMobile = this.openmct.$injector.get('agentService');
return {
isLoading: false,
searchValue: '',
allTreeItems: [],
searchResultItems: [],
visibleItems: [],
ancestors: [],
childrenSlideClass: 'slide-left',
availableContainerHeight: 0,
noScroll: true,
updatingView: false,
itemHeight: 28,
itemOffset: 0,
childrenHeight: 0,
scrollable: undefined,
pageThreshold: 50,
activeSearch: false,
getChildHeight: false,
settingChildrenHeight: false,
isMobile: isMobile.mobileName,
multipleRootChildren: false
}
},
computed: {
currentNavigatedPath() {
let ancestorsCopy = [...this.ancestors];
if (this.multipleRootChildren) {
ancestorsCopy.shift(); // remove root
}
return ancestorsCopy
.map((ancestor) => ancestor.id)
.join('/');
},
currentObjectPath() {
let ancestorsCopy = [...this.ancestors];
return ancestorsCopy
.reverse()
.map((ancestor) => ancestor.object);
},
focusedItems() {
return this.activeSearch ? this.searchResultItems : this.allTreeItems;
},
itemLeftOffset() {
return this.activeSearch ? '0px' : this.ancestors.length * 10 + 'px';
}
},
watch: {
syncTreeNavigation() {
const AND_SAVE_PATH = true;
let currentLocationPath = this.openmct.router.currentLocation.path;
let hasParent = this.currentlyViewedObjectParentPath() || (this.multipleRootChildren && !this.currentlyViewedObjectParentPath());
let jumpAndScroll = currentLocationPath &&
hasParent &&
!this.currentPathIsActivePath();
let justScroll = this.currentPathIsActivePath() && !this.noScroll;
if (this.searchValue) {
this.searchValue = '';
}
if (jumpAndScroll) {
this.scrollTo = this.currentlyViewedObjectId();
this.allTreeItems = [];
this.jumpPath = this.currentlyViewedObjectParentPath();
if (this.multipleRootChildren) {
if(!this.jumpPath) {
this.jumpPath = 'ROOT';
this.ancestors = [];
} else {
this.ancestors = [this.ancestors[0]]
}
} else {
this.ancestors = [];
}
this.jumpToPath(AND_SAVE_PATH);
} else if (justScroll) {
this.scrollTo = this.currentlyViewedObjectId();
this.autoScroll();
}
},
searchValue() {
if (this.searchValue !== '' && !this.activeSearch) {
this.searchActivated();
} else if (this.searchValue === '') {
this.searchDeactivated();
}
},
searchResultItems() {
this.setContainerHeight();
}
},
async mounted() {
let savedPath = this.getSavedNavigatedPath();
this.searchService = this.openmct.$injector.get('searchService');
this.getAllChildren();
window.addEventListener('resize', this.handleWindowResize);
let root = await this.openmct.objects.get('ROOT');
let rootNode = this.buildTreeItem(root);
// if more than one root item, set multipleRootChildren to true and add root to ancestors
if (root.composition && root.composition.length > 1) {
this.ancestors.push(rootNode);
this.multipleRootChildren = true;
} else if (!savedPath) {
// needed if saved path is not set, need to set it to the only root child
savedPath = root.composition[0].key;
}
if (savedPath) {
let scrollIfApplicable = () => {
if (this.currentPathIsActivePath()) {
this.scrollTo = this.currentlyViewedObjectId();
}
};
this.jumpPath = savedPath;
this.afterJump = scrollIfApplicable;
}
this.getAllChildren(rootNode);
},
destroyed() {
window.removeEventListener('resize', this.handleWindowResize);
},
methods: {
getAllChildren() {
this.isLoading = true;
this.openmct.objects.get('ROOT')
.then(root => {
return this.openmct.composition.get(root).load()
})
.then(children => {
this.isLoading = false;
this.allTreeItems = children.map(c => {
return {
id: this.openmct.objects.makeKeyString(c.identifier),
object: c,
objectPath: [c],
navigateToParent: '/browse'
};
});
updatevisibleItems() {
if (this.updatingView) {
return;
}
this.updatingView = true;
requestAnimationFrame(()=> {
let start = 0;
let end = this.pageThreshold;
let allItemsCount = this.focusedItems.length;
if (allItemsCount < this.pageThreshold) {
end = allItemsCount;
} else {
let firstVisible = this.calculateFirstVisibleItem();
let lastVisible = this.calculateLastVisibleItem();
let totalVisible = lastVisible - firstVisible;
let numberOffscreen = this.pageThreshold - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(this.pageThreshold, allItemsCount);
} else if (end >= allItemsCount) {
end = allItemsCount;
start = end - this.pageThreshold + 1;
}
}
this.itemOffset = start;
this.visibleItems = this.focusedItems.slice(start, end);
this.updatingView = false;
});
},
getFilteredChildren() {
this.searchService.query(this.searchValue).then(children => {
this.filteredTreeItems = children.hits.map(child => {
async setContainerHeight() {
await this.$nextTick();
let mainTree = this.$refs.mainTree;
let mainTreeHeight = mainTree.clientHeight;
let context = child.object.getCapability('context'),
object = child.object.useCapability('adapter'),
objectPath = [],
navigateToParent;
if (mainTreeHeight !== 0) {
this.calculateChildHeight(() => {
let ancestorsHeight = this.calculateAncestorHeight();
let allChildrenHeight = this.calculateChildrenHeight();
if (this.activeSearch) {
ancestorsHeight = 0;
}
this.availableContainerHeight = mainTreeHeight - ancestorsHeight;
if (allChildrenHeight > this.availableContainerHeight) {
this.setPageThreshold();
this.noScroll = false;
} else {
this.noScroll = true;
}
this.updatevisibleItems();
});
} else {
window.setTimeout(this.setContainerHeight, RECHECK_DELAY);
}
},
calculateFirstVisibleItem() {
let scrollTop = this.$refs.scrollable.scrollTop;
return Math.floor(scrollTop / this.itemHeight);
},
calculateLastVisibleItem() {
let scrollBottom = this.$refs.scrollable.scrollTop + this.$refs.scrollable.offsetHeight;
return Math.ceil(scrollBottom / this.itemHeight);
},
calculateChildrenHeight() {
let mainTreeTopMargin = this.getElementStyleValue(this.$refs.mainTree, 'marginTop');
let childrenCount = this.focusedItems.length;
return (this.itemHeight * childrenCount) - mainTreeTopMargin; // 5px margin
},
setChildrenHeight() {
this.childrenHeight = this.calculateChildrenHeight();
},
calculateAncestorHeight() {
let ancestorCount = this.ancestors.length;
return this.itemHeight * ancestorCount;
},
calculateChildHeight(callback) {
if (callback) {
this.afterChildHeight = callback;
}
if (!this.activeSearch) {
this.getChildHeight = true;
} else if (this.afterChildHeight) {
// keep the height from before
this.afterChildHeight();
delete this.afterChildHeight;
}
},
async setChildHeight(item) {
if (!this.getChildHeight || this.settingChildrenHeight) {
return;
}
this.settingChildrenHeight = true;
if (this.isMobile) {
item = item.children[0];
}
await this.$nextTick();
let topMargin = this.getElementStyleValue(item, 'marginTop');
let bottomMargin = this.getElementStyleValue(item, 'marginBottom');
let totalVerticalMargin = topMargin + bottomMargin;
this.itemHeight = item.clientHeight + totalVerticalMargin;
this.setChildrenHeight();
if (this.afterChildHeight) {
this.afterChildHeight();
delete this.afterChildHeight;
}
this.getChildHeight = false;
this.settingChildrenHeight = false;
},
setPageThreshold() {
let threshold = Math.ceil(this.availableContainerHeight / this.itemHeight) + ITEM_BUFFER;
// all items haven't loaded yet (nextTick not working for this)
if (threshold === ITEM_BUFFER) {
window.setTimeout(this.setPageThreshold, RECHECK_DELAY);
} else {
this.pageThreshold = threshold;
}
},
handleWindowResize() {
if (!windowResizing) {
windowResizing = true;
window.clearTimeout(windowResizeId);
windowResizeId = window.setTimeout(() => {
this.setContainerHeight();
windowResizing = false;
}, RESIZE_FIRE_DELAY_MS);
}
},
async getAllChildren(node) {
this.isLoading = true;
if (this.composition) {
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
delete this.composition;
}
this.allTreeItems = [];
this.composition = this.openmct.composition.get(node.object);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
await this.composition.load();
this.finishLoading();
},
buildTreeItem(domainObject) {
let navToParent = ROOT_PATH + this.currentNavigatedPath;
if (navToParent === ROOT_PATH) {
navToParent = navToParent.slice(0, -1);
}
return {
id: this.openmct.objects.makeKeyString(domainObject.identifier),
object: domainObject,
objectPath: [domainObject].concat(this.currentObjectPath),
navigateToParent: navToParent
};
},
addChild(child) {
let item = this.buildTreeItem(child);
this.allTreeItems.push(item);
if (!this.isLoading) {
this.setContainerHeight();
}
},
removeChild(identifier) {
let removeId = this.openmct.objects.makeKeyString(identifier);
this.allTreeItems = this.allTreeItems
.filter(c => c.id !== removeId);
this.setContainerHeight();
},
async finishLoading() {
if (this.jumpPath) {
this.jumpToPath();
}
this.autoScroll();
this.isLoading = false;
},
async jumpToPath(saveExpandedPath = false) {
let nodes = this.jumpPath.split('/');
for(let i = 0; i < nodes.length; i++) {
let currentNode = await this.openmct.objects.get(nodes[i]);
let newParent = this.buildTreeItem(currentNode);
this.ancestors.push(newParent);
if (i === nodes.length - 1) {
this.jumpPath = '';
this.getAllChildren(newParent);
if (this.afterJump) {
await this.$nextTick()
this.afterJump();
delete this.afterJump;
}
if (saveExpandedPath) {
this.setCurrentNavigatedPath();
}
}
}
},
async autoScroll() {
if (!this.scrollTo) {
return;
}
if (this.$refs.scrollable) {
let indexOfScroll = this.indexOfItemById(this.scrollTo);
let scrollTopAmount = indexOfScroll * this.itemHeight;
await this.$nextTick();
this.$refs.scrollable.scrollTop = scrollTopAmount;
// race condition check
if(scrollTopAmount > 0 && this.$refs.scrollable.scrollTop === 0) {
window.setTimeout(this.autoScroll, RECHECK_DELAY);
return;
}
this.scrollTo = undefined;
} else {
window.setTimeout(this.autoScroll, RECHECK_DELAY);
}
},
indexOfItemById(id) {
for(let i = 0; i < this.allTreeItems.length; i++) {
if (this.allTreeItems[i].id === id) {
return i;
}
}
},
async getSearchResults() {
let results = await this.searchService.query(this.searchValue);
this.searchResultItems = results.hits.map(result => {
let context = result.object.getCapability('context');
let object = result.object.useCapability('adapter');
let objectPath = [];
let navigateToParent;
if (context) {
objectPath = context.getPath().slice(1)
.map(oldObject => oldObject.useCapability('adapter'))
.reverse();
navigateToParent = '/browse/' + objectPath.slice(1)
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier))
.join('/');
navigateToParent = objectPath.slice(1)
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier));
navigateToParent = ROOT_PATH + navigateToParent.reverse().join('/');
}
return {
@ -120,14 +496,95 @@ export default {
navigateToParent
}
});
});
},
searchTree(value) {
this.searchValue = value;
if (this.searchValue !== '') {
this.getFilteredChildren();
this.getSearchResults();
}
},
searchActivated() {
this.activeSearch = true;
this.$refs.scrollable.scrollTop = 0;
},
searchDeactivated() {
this.activeSearch = false;
this.$refs.scrollable.scrollTop = 0;
this.setContainerHeight();
},
handleReset(node) {
this.childrenSlideClass = 'slide-right';
this.ancestors.splice(this.ancestors.indexOf(node) + 1);
this.getAllChildren(node);
this.setCurrentNavigatedPath();
},
handleExpanded(node) {
if (this.activeSearch) {
return;
}
this.childrenSlideClass = 'slide-left';
let newParent = this.buildTreeItem(node);
this.ancestors.push(newParent);
this.getAllChildren(newParent);
this.setCurrentNavigatedPath();
},
getSavedNavigatedPath() {
return JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED));
},
setCurrentNavigatedPath() {
if (!this.searchValue) {
localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(this.currentNavigatedPath));
}
},
currentPathIsActivePath() {
return this.getSavedNavigatedPath() === this.currentlyViewedObjectParentPath();
},
currentlyViewedObjectId() {
let currentPath = this.openmct.router.currentLocation.path;
if (currentPath) {
currentPath = currentPath.split(ROOT_PATH)[1];
return currentPath.split('/').pop();
}
},
currentlyViewedObjectParentPath() {
let currentPath = this.openmct.router.currentLocation.path;
if (currentPath) {
currentPath = currentPath.split(ROOT_PATH)[1];
currentPath = currentPath.split('/');
currentPath.pop();
return currentPath.join('/');
}
},
scrollItems(event) {
if (!windowResizing) {
this.updatevisibleItems();
}
},
childrenListStyles() {
return { position: 'relative' };
},
scrollableStyles() {
return {
height: this.availableContainerHeight + 'px',
overflow: this.noScroll ? 'hidden' : 'scroll'
}
},
emptyStyles() {
let offset = ((this.ancestors.length + 1) * 10);
return {
paddingLeft: offset + 'px'
};
},
childrenIn(el, done) {
// still needing this timeout for some reason
window.setTimeout(this.setContainerHeight, RECHECK_DELAY);
done();
},
getElementStyleValue(el, style) {
let styleString = window.getComputedStyle(el)[style];
let index = styleString.indexOf('px');
return Number(styleString.slice(0, index));
}
}
}

View File

@ -40,6 +40,10 @@
display: flex;
align-items: center;
@include desktop() { margin-bottom: $interiorMargin; }
[class*="button"] {
color: $colorBtnMajorBg;
}
}
&--reacts {
@ -128,12 +132,23 @@
@include userSelectNone();
color: $splitterBtnLabelColorFg;
display: block;
pointer-events: none;
text-transform: uppercase;
transform-origin: top left;
flex: 1 1 auto;
}
[class*="expand-button"] {
display: none; // Hidden by default
background: $splitterCollapsedBtnColorBg;
color: $splitterCollapsedBtnColorFg;
font-size: 0.9em;
&:hover {
background: $splitterCollapsedBtnColorBgHov;
color: inherit;
transition: $transIn;
}
}
&--resizing {
// User is dragging the handle and resizing a pane
@include userSelectNone();
@ -160,23 +175,12 @@
display: none;
}
.l-pane__header {
&:hover {
color: $splitterCollapsedBtnColorFgHov;
.l-pane__label {
color: inherit;
}
.l-pane__collapse-button {
background: $splitterCollapsedBtnColorBgHov;
color: inherit;
transition: $transIn;
}
}
[class*="collapse-button"] {
display: none;
}
.l-pane__collapse-button {
background: $splitterCollapsedBtnColorBg;
color: $splitterCollapsedBtnColorFg;
[class*="expand-button"] {
display: block;
}
}
@ -198,36 +202,26 @@
.l-pane__collapse-button {
&:before {
content: $glyph-icon-arrow-right-equilateral;
content: $glyph-icon-line-horz;
}
}
&[class*="--collapsed"] {
/************************ COLLAPSED HORIZONTAL SPLITTER, EITHER DIRECTION */
[class*="__header"] {
@include abs();
margin: 0;
display: none;
}
[class*="label"] {
position: absolute;
transform: translate($interiorMarginLg + 1, 18px) rotate(90deg);
left: 3px;
top: 0;
z-index: 1;
}
.l-pane__collapse-button {
border-top-left-radius: 0;
border-bottom-left-radius: 0; // Only have to do this once, because of scaleX(-1) below.
[class*="expand-button"] {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
height: auto; width: 100%;
padding: 0;
padding: $interiorMargin 2px;
&:before {
position: absolute;
top: 5px;
[class*="label"] {
text-orientation: mixed;
text-transform: uppercase;
writing-mode: vertical-lr;
}
}
}
@ -243,10 +237,9 @@
transform: translateX(floor($splitterHandleD / -2)); // Center over the pane edge
}
&[class*="--collapsed"] {
.l-pane__collapse-button {
transform: scaleX(-1);
}
[class*="expand-button"] {
border-top-left-radius: $controlCr;
border-bottom-left-radius: $controlCr;
}
}
@ -261,10 +254,9 @@
transform: translateX(floor($splitterHandleD / 2));
}
&:not([class*="--collapsed"]) {
.l-pane__collapse-button {
transform: scaleX(-1);
}
[class*="expand-button"] {
border-top-right-radius: $controlCr;
border-bottom-right-radius: $controlCr;
}
}
}

View File

@ -20,12 +20,19 @@
<span v-if="label"
class="l-pane__label"
>{{ label }}</span>
<slot name="controls"></slot>
<button
v-if="collapsable"
class="l-pane__collapse-button c-button"
class="l-pane__collapse-button c-icon-button"
@click="toggleCollapse"
></button>
</div>
<button
class="l-pane__expand-button"
@click="toggleCollapse"
>
<span class="l-pane__expand-button__label">{{ label }}</span>
</button>
<div class="l-pane__contents">
<slot></slot>
</div>

View File

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

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