Compare commits

...

75 Commits

Author SHA1 Message Date
f1d7c7843c Scroll to bottom after resize, if auto-scroll enabled 2019-04-28 16:21:20 -07:00
d35c656ce3 Merge branch 'topic-core-refactor' into tables-performance 2019-04-28 12:41:43 -07:00
1b83631e43 Remove deprecation warnings (#2391) 2019-04-28 12:30:30 -07:00
547d4e82db [Display Layout] Disallow moving objects beyond top or left edges of the edit area (#2390)
* Disallow moving objects beyond top or left edges of the edit area.

* Disallow line also to move beyond top or left edges of the edit area.
2019-04-28 12:30:10 -07:00
3377ad5e0d Reimplemented Go To Original Action (#2383)
* complete working go to original action, todo (css class for action)

* Fix layout when a menu item does not have an icon

* Removed superfluous keystring conversion
2019-04-28 12:29:16 -07:00
627b59e91d Table rendering performance tweaks
Throttled add, remove, and scroll
2019-04-28 12:05:32 -07:00
1c0df60f05 Misc Fixes 3 (#2389)
* Misc Fixes 3

- Fix Chrome 73 bug in overlay __contents-main element;
- Fixed messages by including erroneously missing _legacy-messages.scss
file;
- Better layout for messages in notification overlay list;

* Misc Fixes 3

- Fix about screen for better compatibility with VISTA;
- Better logo sizing in splash element;
2019-04-26 14:43:13 -07:00
138067dca9 [Migration] convert telemetry points to overlay plot (#2388)
* Replace telemetry point objects with overlay plot when migrating display layouts.

* Persist plot object
2019-04-26 11:19:07 -07:00
844280eaa5 Memory leak fixes (#2387)
* Clean up listeners

* Fix uses of 'destroy' instead of 'destroyed'
2019-04-26 10:34:24 -07:00
d2e2d55caf Bring across fixes for #1468 and #2277 into TCR (#2386) 2019-04-24 16:01:45 -07:00
f01d4071a1 Merge pull request #2385 from nasa/misc-fixes-2
Misc Fixes 2
2019-04-24 16:00:28 -07:00
06524ce967 Misc Fixes 2
- Hide nav-to-parent button when editing
2019-04-18 14:47:32 -07:00
1ec529f360 Misc Fixes 2
- Remove explicit height;
2019-04-17 00:20:51 -07:00
cf6458c69d Misc Fixes 2
- Better approach to title in layout frames;
- Removed unneeded !important attribs;
2019-04-16 23:31:59 -07:00
3316500774 Misc Fixes 2
- Restore erroneous delete;
2019-04-16 22:03:30 -07:00
0f780587c0 Misc Fixes 2
- Fix alignment issue in c-so-view__headers;
2019-04-16 21:59:41 -07:00
ea69508e22 Misc Fixes 2
- Fix table resizing issue in Flex Layouts;
2019-04-16 14:45:08 -07:00
4274d8cc0b Misc Fixes 1 (#2382)
- Fix color issue for mobile menu icon;
- Fixed Chrome 73 overflow issue in main folder view;
- Better fixes for Chrome 73 overflow bug;
- Code cleanup;
2019-04-16 10:42:17 -07:00
1a2048332f Request latest data from alphanumerics (#2377) 2019-04-11 11:00:15 -07:00
38a875395f Markup changes to support VISTA About Dialog (#2375) 2019-04-11 10:19:25 -07:00
f601ab03e7 Add unsynced status class to legacy views (#2374) 2019-04-11 10:18:23 -07:00
ee1d92d4a9 Implements selection and reorder in stacked plots (#2371)
* working selection in stacked plots

* reorder in stacked plots works

* tabs code cleanup
2019-04-10 16:03:18 -07:00
548286bacd fixed filter field issue, and prevent elements pool from updating when selection has not changed (#2372) 2019-04-10 15:56:08 -07:00
9c9006d415 conditionally enable notebook button in preview (#2373) 2019-04-10 15:45:02 -07:00
3219a64d09 Add root object to object path for legacy context menu actions (#2369) 2019-04-10 11:42:49 -07:00
570aa2c02a Resolve object paths properly for search results (#2370) 2019-04-10 10:17:50 -07:00
c577d2e231 Only switch into edit mode if view is editable (#2367) 2019-04-10 10:13:54 -07:00
6bf4b3aba8 Fixes some issues relating to removal of objects (#2366)
* Leave edit mode on navigation after removal

* Only leave edit mode if removing navigated item, or parent of

* Do not emit mutation from filters with out of date object model
2019-04-09 10:45:56 -07:00
b659f205f7 Reset selection on cancel (#2363) 2019-04-09 10:23:53 -07:00
40d54df567 Summary widget unsubscribe (#2364)
* Delete subscription handle to prevent double unsubscribe error

* Do not attempt to render undefined telemetry datum
2019-04-08 22:02:40 -07:00
b7fa5c7ba8 Avoid using the same separator object when adding separators to toolbar (#2362) 2019-04-06 17:04:52 -07:00
3b0605d17b Properties section for single select non-domain object (#2361)
* Display a message when a single non-domain object is selected.

* Reviewer's requested change
2019-04-06 14:55:46 -07:00
d93e7bfd1a Merge pull request #2360 from nasa/flex-layout-selection
Toolbar issues in Flexible layout
2019-04-06 13:57:21 -07:00
104cd0ed29 Merge branch 'topic-core-refactor' into flex-layout-selection 2019-04-05 20:09:36 -07:00
7fb3d86d06 Update toolbar to get value and mutate object if toolbar item doesn't have applicableSelectedItems. Also, modify flexibale layout to select parent by clicking the element instead of using selection's private methods. 2019-04-05 20:03:31 -07:00
dbb42e9bb6 Do not use Object.create() with Vueified objects (#2359) 2019-04-05 18:20:30 -07:00
d1baa1f98b Merge pull request #2357 from nasa/misc-ui-8
Misc UI 8
2019-04-05 17:31:07 -07:00
5ab68c0586 Merge branch 'topic-core-refactor' into misc-ui-8 2019-04-05 17:30:42 -07:00
3cf78f509d Significant enhancements to limits (#2352)
- Icons added for red and yellow limits without upr/lwr classes;
- When is-limit--upr and is-limit--lwr present, those icons trump the
red/yellow icons;
- Styles for table tr's, and everything else;
- Unit tested in telem tables, LAD tables and plot legend;
2019-04-05 17:22:49 -07:00
c6053e234a Implement multi selection (#2351)
* Modify Selection API to support multi-select via shift click.

* Add support for shift + click to add and remove the selection.

* Display message in Location and Properties for multi-select.

* Define applicableSelectedItems for toolbar items. Move toolbar  control definitions to functions.

* Hide positioning inputs if multi-select. Show a 'non-specific' icon when a discrete setting can't be shown in a mixed setting."

* Add toolbar controls in groups per layout item type. Add nonSpecific property to toolbar items to be used by toolbar controls to show non-specific icon.

* Modify toolbar button to react to nonSpecific flag. Get form value by checking value of applicable selected items.

* Support deleting multiple selected objects.

* Do not disable controls when selected items have mixed setting.

* Revert default color to original value.

* Changes to snap-to-grid

* Remove timeout for updating toolbar after mutation. Do not copy toolbar item when iterating the structure.

* Implement move to top and move to bottom for multi-select

* Implement move up and move down for multi-select.

* Markup and CSS changes for mixed settings in toolbar

- Toggle, color-picker buttons;
- TODO: check other themes and sync;

* Mixed settings styling complete

- Refined and synced theme constants;
- Styling for all toolbar components;
- Text size menu handling;
- Inspector messaging;

* Fix selection path

* Mixed settings styling refinements

- Normalized button styling for mixed style context;
- Better theme constant naming;
- Refined swatch styling, better theme constants;

* First cut at getting the bounding rectangle working for multi-select.

* Set pointer-events to none on c-edit-frame to prevent marquee reacting to click events.

* Delete capturing before calling select.

* Remove EditMarquee from ITEM_TYPE_VIEW_MAP

* Pass selected layout items as a prop to edit marquee instead of selection so that x, y, w, h are updated.

* Multi-select c-frame-edit visual fixes

- WIP

* Add complexContent class for a single selected item whose type is subobject-view.

* Move 'c-frame-edit-move' div to layout frame.

* Saving work - multi-move WIP

* Fixes issue with selection happening at end of drag

* Styles fixed for new markup organization

- Marquee, frame styles;
- $editMarqueeBorder style added to theme constants;

* Significant functionality for .c-frame-edit__move element

- Added .is-multi-selected class to .l-layout when > 1 items selected;
- __move element now handles multi-select and complex content (CC)
objects:
-- 0 to 1 items selected, displays as hover bar with grippy on all CC
 objects,
-- > 1 items selected, __move covers all of the frame of all selected CC
items and doesn't allow sub-object selection, and only displays as hover
bar on non-selected CC objects;
- Added better styling for selected objects while editing;
- Code cleanup and consolidation;
- Left translucent green style applied to __move element to temporarily
aid development;
- TODO: fix line drawing object;

* - Fix an issue where shift click did not remove the selected item from the selection after move.
- Modify telemetry and subobject views to emit move and endMove events.
- Clone selectedLayoutItems to get initial positions instead of selection so subsequent moves start from the current position.

* Fix cursor for __move, code comment refinements

* Code cleanup, line view markup changes

- line view markup brought into line with structure in LayoutFrame.vue;

* Implement multi-resize

* Simplify edit marquee code. Revert image and text views' default position to the original values.

* Fix resize for single selection when snap to grid is disabled

* Hide edit marquee if single line is selected, and show c-frame-edit in line-view instead.

* Fix for LineView handles

* Remove snap to grid toggle button and modify the migration script to convert elements with pixel coordinates to grid.

* Fix resizing single line

* Calculate width and height differently for line to position marquee correctly.

* Fix moving single selected line

* Calculate the height and width for line before comparing them with max height and width to correct the marquee position.

* Change the logic for showing frame edit for lines to check for item id.

* Allow multi-move with line in the mix.

* Implement multi-resize when grabbing SW corner.

* Removed temp green tint from __move element

* Fix object undefined error.

* Implement multi-resize for all items except line (take 2).

* Misc UI 7

- CSS selectors to properly display edit marquee, don't show in browse
mode;

* Fix multi-resize for lines.
Make sure line's height and width is minimum 1.

* Disable inspector views when multiple objects are selected.

* Restored layout grid display on sub-layout selection

* Clean up code

* Fixes

- Edit marquee display fixes;

* More code clean up

* SIGNIFICANT fixes and rewriting in LayoutFrame.vue

- Styles for .c-frame-edit__move element for selection and hovering;
- local controls;
- view large button;
- Theme constants updated;

* Get selected item's index from layoutItems.

* Address review feedback.

* Merge topic-core-refactor

* Reset keyString to empty string after setting original path when domainObject is undefined.
Add proper check for selection.
2019-04-05 14:22:10 -07:00
964c326535 Cancel editing bug (#2355)
* WIP

* Refresh view with object from persistence
2019-04-05 11:25:46 -07:00
baf410a364 Retain scrolltop on resize (#2358) 2019-04-05 09:56:31 -07:00
517a40a32b Tree Performance Fixes (#2353)
* Disable disclosure triangle transition

* Reduce number of times navigation path is calculated
2019-04-05 09:44:38 -07:00
8b275b206b Remove selection fix (#2348)
* add a function to change selection of deleted item in remove action, and update flexlayouts

* resize when item is deleted

* fix for resize handles not showing after object is dropped

* fix isAlias logic for folder views

* remove uneccesary console log

* move selection logic to flexible layouts

* only update inspector views if selection has changed to a new context

* force a digest in the plot options controller once the series are added

* conditionally show snapshot button only if notebook is installed
2019-04-05 09:34:55 -07:00
a40a31aa4c Remove wait spinners when error occurs in tables (#2356) 2019-04-05 09:34:03 -07:00
6c0c1df010 Added a mutation listener to CompositionCollection (#2354) 2019-04-05 09:32:58 -07:00
c552afff17 Overlay preview scroll fix and styling
- Preview now handles overflow properly;
- Refined preview styles;
2019-04-04 23:29:50 -07:00
0837129ad5 Styles for td.is-selectable
- Required for VISTA `channel-list-selection`;
- Added new theme constants for editUIColorBg, Fg;
2019-04-04 23:08:09 -07:00
6f3e2a8fbb Misc UI 7 (#2349)
* Misc UI 7

- Better approach to hide/show in Tabs view;

* Misc UI 7

- Fix Chrome 73 bug for Folders in Tabs and Flex Layouts views;

* Misc UI 7

- Fixed look of text inputs in Snow;
- Added description for Tabs View;

* Misc UI 7

- Resizeable table column headers now clip properly;
- Cleaned up and consolidated related CSS;

* Misc UI 7

- Remove undesired top margin in Flex Layouts;
- Fix Chrome 73 overflow bug in ObjectFrame;

* Misc UI 7

- Remove undesired top margin in Flex Layouts;
- Fix Chrome 73 overflow bug in ObjectFrame;
- Enhanced View Large button so now displays in objects with
frames hidden;
- Changed behavior for frame move bar such that it only displays for
selected items;
- Fixed bug where telem table columns can't be resized in new tables;
- Added overflow handling to telem table column headers;

* Misc UI 7

- Remove undesired top margin in Flex Layouts;
- Fix Chrome 73 overflow bug in ObjectFrame;
- Enhanced View Large button so now displays in objects with
frames hidden, and is only shown for objects that get
.has-complex-content applied;
- Changed behavior for frame move bar such that it only displays for
selected items;
- Fixed bug where telem table columns can't be resized in new tables;
- Added overflow handling to telem table column headers;
- Fix for clipped color palette in Summary Widgets, and better flex
layout in sizing in edit interface;
- Added timer and hyperlink to SIMPLE_CONTENT_TYPES list;

* Misc UI 7

- Accessibility: add name of object as title attribute to Layout frames;
- Moved c-frame base styling into c-so-view;

* remove title from layoutFrame
2019-04-04 10:45:17 -07:00
4189a05758 event emitter uses keystring instead of key, to avoid broadcasting to all domainObjects that share the same key' (#2350) 2019-04-04 10:29:42 -07:00
97ccaa58c7 show notifications error for rejected telemetry requests (#2334)
* show notification error for rejected telemetry requests

* change notification message details
2019-03-29 15:52:44 -07:00
08ef932926 Use instead of to avoid double digest issue (#2346) 2019-03-29 15:38:39 -07:00
1d2ed0398c Default routing (#2342)
* Working version of default navigation to last child

* Implemented as separate route to clean up code a bit
2019-03-29 14:36:15 -07:00
5a00e0c549 Fix for Chrome 73 overflow bug effecting Telem Tables in Flex Layouts (#2341)
* Voodoo fix for Chrome 73 overflow bug effecting Telem Tables in Flex
Layouts;

* Update table.vue
2019-03-29 13:34:16 -07:00
ebcf47733f Merge pull request #2345 from nasa/revert-2344-properties-dialog-digest
LGTM. Revert "Force digest on compilation of overlay template"
2019-03-29 13:29:52 -07:00
381d7e7615 Revert "Force digest on compilation of overlay template (#2344)"
This reverts commit 8246b47668.
2019-03-29 13:21:45 -07:00
8246b47668 Force digest on compilation of overlay template (#2344) 2019-03-29 10:38:56 -07:00
bc5e300ba9 Optionally provide list of object types to show as views rather than telemetry in Display Layouts (#2339)
* Optionally provide list of object types to show as views rather than alpha numerics to display layouts

* Only make table view available for objects that have telemetry to show
2019-03-28 22:21:27 -07:00
57efef3160 Digest after telemetry returned. (#2343) 2019-03-28 22:19:22 -07:00
dfc5a9f040 Tabs view fixes plus elements pool (#2340)
* use reorder api

* fix regression where elements pool doesnt update on remove

* fix issue where tabs was not updating when changes are discarded
2019-03-28 17:45:07 -07:00
57443d227d Misc UI 6 (#2338)
* Sanding and shimming on loading CSS

- Wait spinners in the tree;
- Move spinner and loading CSS from legacy to global.scss;

* Misc UI 6
- Better approach to Time Conductor overflow;
- Fixed main page left/right clipping issue;
- Fixed table header bg styling that had issues in legacy tables;
- Fixed Time Conductor datetime picker clipping issue;
- New .c-message--simple for use in Summary Widgets;
- Better styling for header in empty Tabs view;
- Fixed Chrome 73 scrolling bug in Summary Widgets;
- Fixed problem in Inspector <li> elements from new wait spinner;
- Fixed color of <a> tags in tables to be more visible;
styling;

* Misc UI 6

- Fix VISTA session selector not scrolling (Chrome 73 overflow bug);

* Misc UI 6

- Fix VISTA session selector not scrolling (Chrome 73 overflow bug);
2019-03-28 16:29:39 -07:00
d36441db73 Persistence dialog fix (#2337)
* discard persistence error dialog

* update tests
2019-03-28 13:55:39 -07:00
327782835e save object before triggering mutate (#2336) 2019-03-28 13:54:27 -07:00
994f6be535 do not return null unsubscribe function (#2335) 2019-03-27 18:09:13 -07:00
72fc8a24a5 Fixed for flex-related overflow scrolling issues in Chrome 73/Firefox (#2333) 2019-03-26 22:57:41 -07:00
07002c12eb set default theme to snow for VISTA deployments 2019-03-26 14:50:25 -07:00
c688d19e15 Fix bug introduced into .c-table 2019-03-26 11:48:03 -07:00
c0ce448dc3 Misc UI 5 (#2332)
* Style fixes for Inspector and location elements

- Code cleanup;
- Remove legacy styles;

* Tab styling WIP, for VISTA Venue dialog

* Add new c-tabs styles, replaces c-compact-button

- Remove c-compact-button and mixin;
- Refactor to use c-tabs in Tabs View;
- New notched look for tabs;

* Tweaks to c-tabs

* Misc various

- Increased mouse wheel zoom and changed to use const;
- Fixed squishy grippys in Elements pool;
- Fixed Time Conductor to prevent overrunning right pane when main pane
is very small;
- Changed message text when leaving Layout editing;
- Fixed z-index problem with splitter bars and VISTA Indicator hover
bubbles;
- Restored support for legacy `l-input-lg` to allow large text input
fields in form generation;
- Modded styles in Properties dialog to fix issue with label column
colliding with inputs when the label text is long;

* Restore hover hide/show to local controls in Summary Widgets

- Also fixed rotation transition for disclosure controls;

* Refinement to overflow hidden for Time Conductor UI

* Fix Time Conductor layout in mobile

* Fix Filter tree items in Inspector

* Move .selector-list out from within .form .form-row to allow more
flexible usage;

* Significant theme updates; table layout and Summary styling added

- Reorganized status constants;
- Added base styles for selected and active styles;
- Added styling for selected and active buttons;
- c-table changed from absolute pos to relative;
- Added c-table-and-summary styling;

* Tweaks to Location component
2019-03-26 11:38:38 -07:00
6c479d6d59 Change tree wait spinner from span to li 2019-03-26 10:59:31 -07:00
76ba487261 Remove action works on missing objects (#2330) 2019-03-26 09:35:52 -07:00
e3f4da19f9 Table migration (#2327)
* Added table migration code

* First working version

* Fixed issues with objects missing from composition
2019-03-25 22:19:33 -07:00
c7ffcbf7e0 Fix path routing issue that prevented object navigation in different deployment paths (#2331) 2019-03-25 22:17:59 -07:00
a27b3737f1 Fixes testathon 3/21 (#2328)
* fix error in location.vue because of drawing objects in selection

* add conditional to check if view is editable before forcing edit after create

* show original location only in inspector, add original location for drawing objects

* fix document title

* set document title in browse.js

* sort items in create menu

* sort children in tree by name

* remove ordering from tree items

* add loading spinner

* fix minor bug
2019-03-25 18:26:39 -07:00
78dccf1e0a Persist table sort options (#2329) 2019-03-25 11:35:47 -07:00
9cb7e09aef Better drag reordering affordance in Elements pool (#2326)
* Better drag reordering affordance in Elements pool

* add isDragging

* Better drag reordering affordance in Elements pool

* Only add dragend handler after drag starts
2019-03-23 10:01:38 -07:00
122 changed files with 3491 additions and 2352 deletions

View File

@ -54,6 +54,9 @@
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
}));
openmct.install(openmct.plugins.DisplayLayout({
showAsView: ['summary-widget', 'example.imagery']
}));
openmct.install(openmct.plugins.Conductor({
menuOptions: [
{
@ -83,6 +86,7 @@
openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.GoToOriginalAction());
openmct.start();
</script>
</html>

View File

@ -31,7 +31,6 @@ define([
"./src/navigation/NavigateAction",
"./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction",
"./src/windowing/WindowTitler",
"./res/templates/browse.html",
"./res/templates/browse-object.html",
"./res/templates/browse/object-header.html",
@ -52,7 +51,6 @@ define([
NavigateAction,
OrphanNavigationHandler,
NewTabAction,
WindowTitler,
browseTemplate,
browseObjectTemplate,
objectHeaderTemplate,
@ -226,14 +224,6 @@ define([
}
],
"runs": [
{
"implementation": WindowTitler,
"depends": [
"navigationService",
"$rootScope",
"$document"
]
},
{
"implementation": OrphanNavigationHandler,
"depends": [

View File

@ -1,78 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/**
* WindowTitlerSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../../src/windowing/WindowTitler"],
function (WindowTitler) {
describe("The window titler", function () {
var mockNavigationService,
mockRootScope,
mockDocument,
mockDomainObject,
titler; // eslint-disable-line
beforeEach(function () {
mockNavigationService = jasmine.createSpyObj(
'navigationService',
['getNavigation']
);
mockRootScope = jasmine.createSpyObj(
'$rootScope',
['$watch']
);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getModel']
);
mockDocument = [{}];
mockDomainObject.getModel.and.returnValue({ name: 'Test name' });
mockNavigationService.getNavigation.and.returnValue(mockDomainObject);
titler = new WindowTitler(
mockNavigationService,
mockRootScope,
mockDocument
);
});
it("listens for changes to the name of the navigated object", function () {
expect(mockRootScope.$watch).toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
expect(mockRootScope.$watch.calls.mostRecent().args[0]())
.toEqual('Test name');
});
it("sets the title to the name of the navigated object", function () {
mockRootScope.$watch.calls.mostRecent().args[1]("Some name");
expect(mockDocument[0].title).toEqual("Some name");
});
});
}
);

View File

@ -65,7 +65,8 @@ define([
"depends": [
"$document",
"$compile",
"$rootScope"
"$rootScope",
"$timeout"
]
}
],

View File

@ -9,17 +9,17 @@
ng-model="ngModel"
ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div>
<div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options"
</div>
<div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options"
class="c-button"
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</button>
<button class="c-button c-button--major"
{{dialogOption.label}}
</button>
<button class="c-button c-button--major"
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</button>
</div>
{{ngModel.primaryOption.label}}
</button>
</div>
</div>

View File

@ -44,8 +44,9 @@ define(
* @memberof platform/commonUI/dialog
* @constructor
*/
function OverlayService($document, $compile, $rootScope) {
function OverlayService($document, $compile, $rootScope, $timeout) {
this.$compile = $compile;
this.$timeout = $timeout;
// Don't include $document and $rootScope directly;
// avoids https://docs.angularjs.org/error/ng/cpws
@ -93,12 +94,14 @@ define(
scope.key = key;
scope.typeClass = typeClass || 't-dialog';
// Create the overlay element and add it to the document's body
element = this.$compile(TEMPLATE)(scope);
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
// multiple overlays with the same z-index are active.
this.findBody().append(element);
this.$timeout(() => {
// Create the overlay element and add it to the document's body
element = this.$compile(TEMPLATE)(scope);
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
// multiple overlays with the same z-index are active.
this.findBody().append(element);
});
return {
dismiss: dismiss

View File

@ -35,16 +35,20 @@ define(
mockTemplate,
mockElement,
mockScope,
mockTimeout,
overlayService;
beforeEach(function () {
mockDocument = jasmine.createSpyObj("$document", ["find"]);
mockCompile = jasmine.createSpy("$compile");
mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]);
mockBody = jasmine.createSpyObj("body", ["prepend"]);
mockBody = jasmine.createSpyObj("body", ["append"]);
mockTemplate = jasmine.createSpy("template");
mockElement = jasmine.createSpyObj("element", ["remove"]);
mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
mockTimeout = function (callback) {
callback();
}
mockDocument.find.and.returnValue(mockBody);
mockCompile.and.returnValue(mockTemplate);
@ -54,7 +58,8 @@ define(
overlayService = new OverlayService(
mockDocument,
mockCompile,
mockRootScope
mockRootScope,
mockTimeout
);
});
@ -67,7 +72,7 @@ define(
it("adds the templated element to the body", function () {
overlayService.createOverlay("test", {});
expect(mockBody.prepend).toHaveBeenCalledWith(mockElement);
expect(mockBody.append).toHaveBeenCalledWith(mockElement);
});
it("places the provided model/key in its template's scope", function () {

View File

@ -49,7 +49,7 @@ define(
name: "Properties",
rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition
var row = Object.create(property.getDefinition());
var row = JSON.parse(JSON.stringify(property.getDefinition()));
row.key = index;
return row;
}).filter(function (row) {

View File

@ -162,9 +162,6 @@ function (
function saveAfterClone(clonedObject) {
return this.openmct.editor.save().then(() => {
// Force mutation for search indexing
clonedObject.useCapability('mutation', (model) => {
return model;
});
return clonedObject;
})
}
@ -173,6 +170,14 @@ function (
return fetchObject(clonedObject.getId())
}
function indexForSearch(savedObject) {
savedObject.useCapability('mutation', (model) => {
return model;
});
return savedObject;
}
function onSuccess(object) {
self.notificationService.info("Save Succeeded");
return object;
@ -194,6 +199,7 @@ function (
.then(undirtyOriginals)
.then(saveAfterClone)
.then(finishEditing)
.then(indexForSearch)
.then(hideBlockingDialog)
.then(onSuccess)
.catch(onFailure);

View File

@ -64,7 +64,6 @@ define(
* @returns boolean
*/
EditorCapability.prototype.inEditContext = function () {
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};
@ -74,7 +73,6 @@ define(
* @returns {*}
*/
EditorCapability.prototype.isEditContextRoot = function () {
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing();
};

View File

@ -71,6 +71,12 @@ define(
openmct.editor.cancel();
}
function isFirstViewEditable(domainObject) {
let firstView = openmct.objectViews.get(domainObject)[0];
return firstView && firstView.canEdit && firstView.canEdit(domainObject);
}
function navigateAndEdit(object) {
let objectPath = object.getCapability('context').getPath(),
url = '#/browse/' + objectPath
@ -82,7 +88,9 @@ define(
window.location.href = url;
openmct.editor.edit();
if (isFirstViewEditable(object.useCapability('adapter'))) {
openmct.editor.edit();
}
}
newModel.type = this.type.getKey();

View File

@ -66,7 +66,7 @@ define(
name: "Properties",
rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition
var row = Object.create(property.getDefinition());
var row = JSON.parse(JSON.stringify(property.getDefinition()));
// Use index as the key into the formValue;
// this correlates to the indexing provided by

View File

@ -77,14 +77,19 @@ define([], function () {
return promiseFn().then(nextFn);
};
}
if (!this.isScheduled(id)) {
this.clearTransactionFns[id] =
this.transactionService.addToTransaction(
chain(onCommit, release),
chain(onCancel, release)
);
/**
* Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence
* call is executed. This should prevent stale objects being persisted and overwriting fresh ones.
*/
if (this.isScheduled(id)) {
this.clearTransactionsFor(id);
}
this.clearTransactionFns[id] =
this.transactionService.addToTransaction(
chain(onCommit, release),
chain(onCancel, release)
);
};
/**

View File

@ -93,24 +93,33 @@ define(
expect(mockOnCancel).toHaveBeenCalled();
});
it("ignores subsequent calls for the same object", function () {
manager.addToTransaction(
testId,
jasmine.createSpy(),
jasmine.createSpy()
);
expect(mockTransactionService.addToTransaction.calls.count())
.toEqual(1);
});
describe("Adds callbacks to transaction", function () {
beforeEach(function () {
spyOn(manager, 'clearTransactionsFor');
manager.clearTransactionsFor.and.callThrough();
});
it("accepts subsequent calls for other objects", function () {
manager.addToTransaction(
'other-id',
jasmine.createSpy(),
jasmine.createSpy()
);
expect(mockTransactionService.addToTransaction.calls.count())
.toEqual(2);
it("and clears pending calls if same object", function () {
manager.addToTransaction(
testId,
jasmine.createSpy(),
jasmine.createSpy()
);
expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId);
});
it("and does not clear pending calls if different object", function () {
manager.addToTransaction(
'other-id',
jasmine.createSpy(),
jasmine.createSpy()
);
expect(manager.clearTransactionsFor).not.toHaveBeenCalled();
});
afterEach(function () {
expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2);
});
});
it("does not remove callbacks from the transaction", function () {

View File

@ -54,6 +54,7 @@ define(
if (isDestroyed) {
return;
}
var removeSelectable = openmct.selection.selectable(
element[0],
scope.$eval(attrs.mctSelectable),

View File

@ -43,23 +43,10 @@ define([], function () {
var mutationTopic = topic('mutation');
mutationTopic.listen(function (domainObject) {
var persistence = domainObject.getCapability('persistence');
var wasActive = transactionService.isActive();
cacheService.put(domainObject.getId(), domainObject.getModel());
if (hasChanged(domainObject)) {
if (!wasActive) {
transactionService.startTransaction();
}
transactionService.addToTransaction(
persistence.persist.bind(persistence),
persistence.refresh.bind(persistence)
);
if (!wasActive) {
transactionService.commit();
}
persistence.persist();
}
});
}

View File

@ -24,22 +24,27 @@ define(
["../../src/runs/TransactingMutationListener"],
function (TransactingMutationListener) {
xdescribe("TransactingMutationListener", function () {
describe("TransactingMutationListener", function () {
var mockTopic,
mockMutationTopic,
mockCacheService,
mockTransactionService,
mockDomainObject,
mockModel,
mockPersistence;
beforeEach(function () {
mockTopic = jasmine.createSpy('topic');
mockMutationTopic =
jasmine.createSpyObj('mutation', ['listen']);
mockCacheService =
jasmine.createSpyObj('cacheService', [
'put'
]);
mockTransactionService =
jasmine.createSpyObj('transactionService', [
'isActive',
'startTransaction',
'addToTransaction',
'commit'
]);
mockDomainObject = jasmine.createSpyObj(
@ -52,18 +57,24 @@ define(
);
mockTopic.and.callFake(function (t) {
return (t === 'mutation') && mockMutationTopic;
expect(t).toBe('mutation');
return mockMutationTopic;
});
mockDomainObject.getId.and.returnValue('mockId');
mockDomainObject.getCapability.and.callFake(function (c) {
return (c === 'persistence') && mockPersistence;
expect(c).toBe('persistence');
return mockPersistence;
});
mockModel = {};
mockDomainObject.getModel.and.returnValue(mockModel);
mockPersistence.persisted.and.returnValue(true);
return new TransactingMutationListener(
mockTopic,
mockTransactionService
mockTransactionService,
mockCacheService
);
});
@ -72,48 +83,27 @@ define(
.toHaveBeenCalledWith(jasmine.any(Function));
});
[false, true].forEach(function (isActive) {
var verb = isActive ? "is" : "isn't";
it("calls persist if the model has changed", function () {
mockModel.persisted = Date.now();
function onlyWhenInactive(expectation) {
return isActive ? expectation.not : expectation;
}
//Mark the model dirty by setting the mutated date later than the last persisted date.
mockModel.modified = mockModel.persisted + 1;
describe("when a transaction " + verb + " active", function () {
var innerVerb = isActive ? "does" : "doesn't";
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
beforeEach(function () {
mockTransactionService.isActive.and.returnValue(isActive);
});
expect(mockPersistence.persist).toHaveBeenCalled();
});
describe("and mutation occurs", function () {
beforeEach(function () {
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
});
it("does not call persist if the model has not changed", function () {
mockModel.persisted = Date.now();
mockModel.modified = mockModel.persisted;
it(innerVerb + " start a new transaction", function () {
onlyWhenInactive(
expect(mockTransactionService.startTransaction)
).toHaveBeenCalled();
});
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
it("adds to the active transaction", function () {
expect(mockTransactionService.addToTransaction)
.toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
});
it(innerVerb + " immediately commit", function () {
onlyWhenInactive(
expect(mockTransactionService.commit)
).toHaveBeenCalled();
});
});
});
expect(mockPersistence.persist).not.toHaveBeenCalled();
});
});
}

View File

@ -24,7 +24,6 @@ define([
"./src/actions/MoveAction",
"./src/actions/CopyAction",
"./src/actions/LinkAction",
"./src/actions/GoToOriginalAction",
"./src/actions/SetPrimaryLocationAction",
"./src/services/LocatingCreationDecorator",
"./src/services/LocatingObjectDecorator",
@ -41,7 +40,6 @@ define([
MoveAction,
CopyAction,
LinkAction,
GoToOriginalAction,
SetPrimaryLocationAction,
LocatingCreationDecorator,
LocatingObjectDecorator,
@ -104,14 +102,6 @@ define([
"linkService"
]
},
{
"key": "follow",
"name": "Go To Original",
"description": "Go to the original, un-linked instance of this object.",
"cssClass": "",
"category": "contextual",
"implementation": GoToOriginalAction
},
{
"key": "locate",
"name": "Set Primary Location",

View File

@ -1,60 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
function () {
/**
* Implements the "Go To Original" action, which follows a link back
* to an original instance of an object.
*
* @implements {Action}
* @constructor
* @private
* @memberof platform/entanglement
* @param {ActionContext} context the context in which the action
* will be performed
*/
function GoToOriginalAction(context) {
this.domainObject = context.domainObject;
}
GoToOriginalAction.prototype.perform = function () {
return this.domainObject.getCapability("location").getOriginal()
.then(function (originalObject) {
var actionCapability =
originalObject.getCapability("action");
return actionCapability &&
actionCapability.perform("navigate");
});
};
GoToOriginalAction.appliesTo = function (context) {
var domainObject = context.domainObject;
return domainObject && domainObject.hasCapability("location") &&
domainObject.getCapability("location").isLink();
};
return GoToOriginalAction;
}
);

View File

@ -1,93 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
[
'../../src/actions/GoToOriginalAction',
'../DomainObjectFactory',
'../ControlledPromise'
],
function (GoToOriginalAction, domainObjectFactory, ControlledPromise) {
describe("The 'go to original' action", function () {
var testContext,
originalDomainObject,
mockLocationCapability,
mockOriginalActionCapability,
originalPromise,
action;
beforeEach(function () {
mockLocationCapability = jasmine.createSpyObj(
'location',
['isLink', 'isOriginal', 'getOriginal']
);
mockOriginalActionCapability = jasmine.createSpyObj(
'action',
['perform', 'getActions']
);
originalPromise = new ControlledPromise();
mockLocationCapability.getOriginal.and.returnValue(originalPromise);
mockLocationCapability.isLink.and.returnValue(true);
mockLocationCapability.isOriginal.and.callFake(function () {
return !mockLocationCapability.isLink();
});
testContext = {
domainObject: domainObjectFactory({
capabilities: {
location: mockLocationCapability
}
})
};
originalDomainObject = domainObjectFactory({
capabilities: {
action: mockOriginalActionCapability
}
});
action = new GoToOriginalAction(testContext);
});
it("is applicable to links", function () {
expect(GoToOriginalAction.appliesTo(testContext))
.toBeTruthy();
});
it("is not applicable to originals", function () {
mockLocationCapability.isLink.and.returnValue(false);
expect(GoToOriginalAction.appliesTo(testContext))
.toBeFalsy();
});
it("navigates to original objects when performed", function () {
expect(mockOriginalActionCapability.perform)
.not.toHaveBeenCalled();
action.perform();
originalPromise.resolve(originalDomainObject);
expect(mockOriginalActionCapability.perform)
.toHaveBeenCalledWith('navigate');
});
});
}
);

View File

@ -37,75 +37,17 @@ define(
}
/**
* Handle persistence failures by providing the user with a
* dialog summarizing these failures, and giving the option
* to overwrite/cancel as appropriate.
* Discard failures
* @param {Array} failures persistence failures, as prepared
* by PersistenceQueueHandler
* @memberof platform/persistence/queue.PersistenceFailureHandler#
*/
PersistenceFailureHandler.prototype.handle = function handleFailures(failures) {
// Prepare dialog for display
var dialogModel = new PersistenceFailureDialog(failures),
revisionErrors = dialogModel.model.revised,
$q = this.$q;
// Refresh revision information for the domain object associated
// with this persistence failure
function refresh(failure) {
// Refresh the domain object to the latest from persistence
return failure.persistence.refresh();
}
// Issue a new persist call for the domain object associated with
// this failure.
function persist(failure) {
// Note that we reissue the persist request here, but don't
// return it, to avoid a circular wait. We trust that the
// PersistenceQueue will behave correctly on the next round
// of flushing.
failure.requeue();
}
// Retry persistence (overwrite) for this set of failed attempts
function retry(failuresToRetry) {
var models = {};
// Cache a copy of the model
function cacheModel(failure) {
// Clone...
models[failure.id] = JSON.parse(JSON.stringify(
failure.domainObject.getModel()
));
}
// Mutate a domain object to restore its model
function remutate(failure) {
var model = models[failure.id];
return failure.domainObject.useCapability(
"mutation",
function () {
return model;
},
model.modified
);
}
// Cache the object models we might want to save
failuresToRetry.forEach(cacheModel);
// Strategy here:
// * Cache all of the models we might want to save (above)
// * Refresh all domain objects (so they are latest versions)
// * Re-insert the cached domain object models
// * Invoke persistence again
return $q.all(failuresToRetry.map(refresh)).then(function () {
return $q.all(failuresToRetry.map(remutate));
}).then(function () {
return $q.all(failuresToRetry.map(persist));
});
}
// Discard changes for a failed refresh
function discard(failure) {
var persistence =
@ -118,19 +60,7 @@ define(
return $q.all(failuresToDiscard.map(discard));
}
// Handle user input (did they choose to overwrite?)
function handleChoice(key) {
// If so, try again
if (key === PersistenceFailureConstants.OVERWRITE_KEY) {
return retry(revisionErrors);
} else {
return discardAll(revisionErrors);
}
}
// Prompt for user input, the overwrite if they said so.
return this.dialogService.getUserChoice(dialogModel)
.then(handleChoice, handleChoice);
return discardAll(revisionErrors);
};
return PersistenceFailureHandler;

View File

@ -74,43 +74,14 @@ define(
handler = new PersistenceFailureHandler(mockQ, mockDialogService);
});
it("shows a dialog to handle failures", function () {
it("discards on handle", function () {
handler.handle(mockFailures);
expect(mockDialogService.getUserChoice).toHaveBeenCalled();
});
it("overwrites on request", function () {
mockQ.all.and.returnValue(asPromise([]));
handler.handle(mockFailures);
// User chooses overwrite
mockPromise.then.calls.mostRecent().args[0](Constants.OVERWRITE_KEY);
// Should refresh, remutate, and requeue all objects
mockFailures.forEach(function (mockFailure, i) {
expect(mockFailure.persistence.refresh).toHaveBeenCalled();
expect(mockFailure.requeue).toHaveBeenCalled();
expect(mockFailure.domainObject.useCapability).toHaveBeenCalledWith(
'mutation',
jasmine.any(Function),
i // timestamp
);
expect(mockFailure.domainObject.useCapability.calls.mostRecent().args[1]())
.toEqual({ id: mockFailure.id, modified: i });
});
});
it("discards on request", function () {
mockQ.all.and.returnValue(asPromise([]));
handler.handle(mockFailures);
// User chooses overwrite
mockPromise.then.calls.mostRecent().args[0](false);
// Should refresh, but not remutate, and requeue all objects
mockFailures.forEach(function (mockFailure) {
expect(mockFailure.persistence.refresh).toHaveBeenCalled();
expect(mockFailure.requeue).not.toHaveBeenCalled();
expect(mockFailure.domainObject.useCapability).not.toHaveBeenCalled();
});
});
});
}
);

View File

@ -248,7 +248,6 @@ define([
this.legacyRegistry = defaultRegistry;
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable());
this.install(this.plugins.DisplayLayout());
this.install(PreviewPlugin.default());
this.install(LegacyIndicatorsPlugin());
this.install(LicensesPlugin.default());
@ -257,7 +256,6 @@ define([
if (typeof BUILD_CONSTANTS !== 'undefined') {
this.install(buildInfoPlugin(BUILD_CONSTANTS));
}
}
MCT.prototype = Object.create(EventEmitter.prototype);
@ -328,6 +326,12 @@ define([
* MCT; if undefined, MCT will be run in the body of the document
*/
MCT.prototype.start = function (domElement) {
if (!this.plugins.DisplayLayout._installed) {
this.install(this.plugins.DisplayLayout({
showAsView: ['summary-widget']
}));
}
if (!domElement) {
domElement = document.body;
}
@ -352,7 +356,7 @@ define([
legacyRegistry.enable('adapter');
this.router.route(/^\/$/, () => {
this.router.setPath('/browse/mine');
this.router.setPath('/browse/');
});
/**

View File

@ -33,20 +33,25 @@ export default class LegacyContextMenuAction {
}
invoke(objectPath) {
let context = {
category: 'contextual',
domainObject: this.openmct.legacyObject(objectPath)
}
let legacyAction = new this.LegacyAction(context);
this.openmct.objects.getRoot().then((root) => {
let pathWithRoot = objectPath.slice();
pathWithRoot.push(root);
if (!legacyAction.getMetadata) {
let metadata = Object.create(this.LegacyAction.definition);
metadata.context = context;
legacyAction.getMetadata = function () {
return metadata;
}.bind(legacyAction);
}
legacyAction.perform();
let context = {
category: 'contextual',
domainObject: this.openmct.legacyObject(pathWithRoot)
}
let legacyAction = new this.LegacyAction(context);
if (!legacyAction.getMetadata) {
let metadata = Object.create(this.LegacyAction.definition);
metadata.context = context;
legacyAction.getMetadata = function () {
return metadata;
}.bind(legacyAction);
}
legacyAction.perform();
});
}
appliesTo(objectPath) {

View File

@ -137,8 +137,7 @@ define([
function callbackWrapper(series) {
callback(createDatum(domainObject, metadata, series, series.getPointCount() - 1));
}
return capability.subscribe(callbackWrapper, request);
return capability.subscribe(callbackWrapper, request) || function () {};
};
LegacyTelemetryProvider.prototype.supportsLimits = function (domainObject) {

View File

@ -57,8 +57,10 @@ define([
}.bind(this);
handleLegacyMutation = function (legacyObject) {
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
this.eventEmitter.emit(newStyleObject.identifier.key + ":*", newStyleObject);
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()),
keystring = utils.makeKeyString(newStyleObject.identifier);
this.eventEmitter.emit(keystring + ":*", newStyleObject);
this.eventEmitter.emit('mutation', newStyleObject);
}.bind(this);

View File

@ -45,15 +45,30 @@ define([
view: function (domainObject) {
let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new();
let scope = $rootScope.$new(true);
let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false;
let unlistenToStatus;
let element;
scope.domainObject = legacyObject;
scope.model = legacyObject.getModel();
let child;
let parent;
return {
show: function (container) {
parent = container;
child = document.createElement('div');
parent.appendChild(child);
let statusCapability = legacyObject.getCapability('status');
unlistenToStatus = statusCapability.listen((newStatus) => {
child.classList.remove('s-status-timeconductor-unsynced');
if (newStatus.includes('timeconductor-unsynced')) {
child.classList.add('s-status-timeconductor-unsynced');
}
});
// TODO: implement "gestures" support ?
let uses = legacyView.uses || [];
let promises = [];
@ -74,12 +89,13 @@ define([
uses.forEach(function (key, i) {
scope[key] = results[i];
});
element = openmct.$angular.element(child);
templateLinker.link(
scope,
openmct.$angular.element(container),
element,
legacyView
);
container.classList.add('u-contents');
child.classList.add('u-contents');
}
if (promises.length) {
@ -93,7 +109,12 @@ define([
}
},
destroy: function () {
element.off();
element.remove();
scope.$destroy();
element = null;
scope = null;
unlistenToStatus();
}
}
},

View File

@ -25,25 +25,34 @@ define([
cssClass: representation.cssClass,
description: representation.description,
canView: function (selection) {
if (!selection[0] || !selection[0].context.item) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
}
let domainObject = selection[0].context.item;
return domainObject.type === typeDefinition.key;
let selectionContext = selection[0][0].context;
if (!selectionContext.item) {
return false;
}
return selectionContext.item.type === typeDefinition.key;
},
view: function (selection) {
let domainObject = selection[0].context.item;
let domainObject = selection[0][0].context.item;
let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new();
let scope = $rootScope.$new(true);
let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false;
let element;
scope.domainObject = legacyObject;
scope.model = legacyObject.getModel();
return {
show: function (container) {
let child = document.createElement('div');
container.appendChild(child);
// TODO: implement "gestures" support ?
let uses = representation.uses || [];
let promises = [];
@ -64,9 +73,10 @@ define([
uses.forEach(function (key, i) {
scope[key] = results[i];
});
element = openmct.$angular.element(child)
templateLinker.link(
scope,
openmct.$angular.element(container),
element,
representation
);
container.style.height = '100%';
@ -83,7 +93,11 @@ define([
}
},
destroy: function () {
element.off();
element.remove();
scope.$destroy();
element = null;
scope = null;
}
}
}

View File

@ -28,11 +28,6 @@ export default class Editor extends EventEmitter {
super();
this.editing = false;
this.openmct = openmct;
document.addEventListener('drop', (event) => {
if (!this.isEditing()) {
this.edit();
}
}, {capture: true});
}
/**
@ -79,9 +74,11 @@ export default class Editor extends EventEmitter {
* @private
*/
cancel() {
this.getTransactionService().cancel();
let cancelPromise = this.getTransactionService().cancel();
this.editing = false;
this.emit('isEditing', false);
return cancelPromise;
}
/**

View File

@ -25,7 +25,6 @@ define([
], function (
_
) {
/**
* A CompositionCollection represents the list of domain objects contained
* by another domain object. It provides methods for loading this
@ -63,7 +62,6 @@ define([
this.onProviderRemove = this.onProviderRemove.bind(this);
}
/**
* Listen for changes to this composition. Supports 'add', 'remove', and
* 'load' events.
@ -76,7 +74,11 @@ define([
if (!this.listeners[event]) {
throw new Error('Event not supported by composition: ' + event);
}
if (!this.mutationListener) {
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
this.domainObject = newDomainObject;
})
}
if (this.provider.on && this.provider.off) {
if (event === 'add') {
this.provider.on(
@ -132,6 +134,10 @@ define([
this.listeners[event].splice(index, 1);
if (this.listeners[event].length === 0) {
if (this.mutationListener) {
this.mutationListener();
delete this.mutationListener;
}
// Remove provider listener if this is the last callback to
// be removed.
if (this.provider.off && this.provider.on) {

View File

@ -21,8 +21,10 @@
*****************************************************************************/
define([
'./object-utils.js',
'lodash'
], function (
utils,
_
) {
var ANY_OBJECT_EVENT = "mutation";
@ -41,7 +43,9 @@ define([
}
function qualifiedEventName(object, eventName) {
return [object.identifier.key, eventName].join(':');
var keystring = utils.makeKeyString(object.identifier);
return [keystring, eventName].join(':');
}
MutableObject.prototype.stopListening = function () {

View File

@ -36,7 +36,7 @@
.c-message {
display: flex;
align-items: flex-start;
align-items: center;
> * + * {
margin-left: $interiorMarginLg;
@ -66,6 +66,17 @@
font-size: 1.2em; // TEMP
}
&--simple {
// Icon and text elements only
&:before {
font-size: 30px !important;
}
[class*='__text'] {
font-size: 1.25em;
}
}
/************************** LEGACY */
&.message-severity-info:before {
@include legacyMessage();

View File

@ -92,6 +92,7 @@
display: flex;
flex-direction: column;
flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
padding-right: $interiorMargin; // fend off scroll bar
}

View File

@ -280,7 +280,11 @@ define([
if (!provider) {
return Promise.reject('No provider found');
}
return provider.request.apply(provider, arguments);
return provider.request.apply(provider, arguments).catch((rejected) => {
this.openmct.notifications.error('Error requesting telemetry data, see console for details');
console.error(rejected);
return Promise.reject(rejected);
});
};
/**

View File

@ -28,11 +28,17 @@ define([], function () {
key: "layout",
description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) {
// Apply the layout toolbar if the selected object
// is inside a layout, or the main layout is selected.
return (selection &&
((selection[1] && selection[1].context.item && selection[1].context.item.type === 'layout') ||
(selection[0].context.item && selection[0].context.item.type === 'layout')));
if (!selection || selection.length === 0) {
return false;
}
let selectionPath = selection[0];
let selectedObject = selectionPath[0];
let selectedParent = selectionPath[1];
// Apply the layout toolbar if the selected object is inside a layout, or the main layout is selected.
return (selectedParent && selectedParent.context.item && selectedParent.context.item.type === 'layout') ||
(selectedObject.context.item && selectedObject.context.item.type === 'layout');
},
toolbar: function (selection) {
const DIALOG_FORM = {
@ -73,190 +79,72 @@ define([], function () {
return openmct.$injector.get('dialogService').getUserInput(form, {});
}
function getPath() {
return `configuration.items[${selection[0].context.index}]`;
function getPath(selectionPath) {
return `configuration.items[${selectionPath[0].context.index}]`;
}
let selectedParent = selection[1] && selection[1].context.item,
selectedObject = selection[0].context.item,
layoutItem = selection[0].context.layoutItem,
toolbar = [];
if (selectedObject && selectedObject.type === 'layout') {
toolbar.push({
control: "menu",
domainObject: selectedObject,
method: function (option) {
let name = option.name.toLowerCase();
let form = DIALOG_FORM[name];
if (form) {
getUserInput(form)
.then(element => selection[0].context.addElement(name, element));
} else {
selection[0].context.addElement(name);
}
},
key: "add",
icon: "icon-plus",
label: "Add",
options: [
{
"name": "Box",
"class": "icon-box-round-corners"
},
{
"name": "Line",
"class": "icon-line-horz"
},
{
"name": "Text",
"class": "icon-font"
},
{
"name": "Image",
"class": "icon-image"
}
]
function getAllTypes(selection) {
return selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view' ||
type === 'subobject-view';
});
}
if (!layoutItem) {
return toolbar;
}
let separator = {
control: "separator"
};
let remove = {
control: "button",
domainObject: selectedParent,
icon: "icon-trash",
title: "Delete the selected object",
method: function () {
let removeItem = selection[1].context.removeItem;
let prompt = openmct.overlays.dialog({
iconClass: 'alert',
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
buttons: [
function getAddButton(selection, selectionPath) {
if (selection.length === 1) {
selectionPath = selectionPath || selection[0];
return {
control: "menu",
domainObject: selectionPath[0].context.item,
method: function (option) {
let name = option.name.toLowerCase();
let form = DIALOG_FORM[name];
if (form) {
getUserInput(form)
.then(element => selectionPath[0].context.addElement(name, element));
} else {
selectionPath[0].context.addElement(name);
}
},
key: "add",
icon: "icon-plus",
label: "Add",
options: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
removeItem(layoutItem, selection[0].context.index);
prompt.dismiss();
}
"name": "Box",
"class": "icon-box-round-corners"
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
"name": "Line",
"class": "icon-line-horz"
},
{
"name": "Text",
"class": "icon-font"
},
{
"name": "Image",
"class": "icon-image"
}
]
});
};
}
};
let stackOrder = {
control: "menu",
domainObject: selectedParent,
icon: "icon-layers",
title: "Move the selected object above or below other objects",
options: [
{
name: "Move to Top",
value: "top",
class: "icon-arrow-double-up"
},
{
name: "Move Up",
value: "up",
class: "icon-arrow-up"
},
{
name: "Move Down",
value: "down",
class: "icon-arrow-down"
},
{
name: "Move to Bottom",
value: "bottom",
class: "icon-arrow-double-down"
}
],
method: function (option) {
selection[1].context.orderItem(option.value, selection[0].context.index);
}
};
let useGrid = {
control: "toggle-button",
domainObject: selectedParent,
property: function () {
return getPath() + ".useGrid";
},
options: [
{
value: false,
icon: "icon-grid-snap-to",
title: "Grid snapping enabled"
},
{
value: true,
icon: "icon-grid-snap-no",
title: "Grid snapping disabled"
}
]
};
let x = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".x";
},
label: "X:",
title: "X position"
},
y = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".y";
},
label: "Y:",
title: "Y position",
},
width = {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: function () {
return getPath() + ".width";
},
label: 'W:',
title: 'Resize object width'
},
height = {
control: 'input',
type: 'number',
domainObject: selectedParent,
property: function () {
return getPath() + ".height";
},
label: 'H:',
title: 'Resize object height'
};
}
if (layoutItem.type === 'subobject-view') {
if (toolbar.length > 0) {
toolbar.push(separator);
}
toolbar.push({
function getToggleFrameButton(selectedParent, selection) {
return {
control: "toggle-button",
domainObject: selectedParent,
property: function () {
return getPath() + ".hasFrame";
applicableSelectedItems: selection.filter(selectionPath =>
selectionPath[0].context.layoutItem.type === 'subobject-view'
),
property: function (selectionPath) {
return getPath(selectionPath) + ".hasFrame";
},
options: [
{
@ -270,52 +158,186 @@ define([], function () {
title: "Frame hidden"
}
]
});
toolbar.push(separator);
toolbar.push(stackOrder);
toolbar.push(x);
toolbar.push(y);
toolbar.push(width);
toolbar.push(height);
toolbar.push(useGrid);
toolbar.push(separator);
toolbar.push(remove);
} else {
};
}
function getRemoveButton(selectedParent, selectionPath, selection) {
return {
control: "button",
domainObject: selectedParent,
icon: "icon-trash",
title: "Delete the selected object",
method: function () {
let removeItem = selectionPath[1].context.removeItem;
let prompt = openmct.overlays.dialog({
iconClass: 'alert',
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
emphasis: 'true',
callback: function () {
removeItem(getAllTypes(selection));
prompt.dismiss();
}
},
{
label: 'Cancel',
callback: function () {
prompt.dismiss();
}
}
]
});
}
};
}
function getStackOrder(selectedParent, selectionPath) {
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-layers",
title: "Move the selected object above or below other objects",
options: [
{
name: "Move to Top",
value: "top",
class: "icon-arrow-double-up"
},
{
name: "Move Up",
value: "up",
class: "icon-arrow-up"
},
{
name: "Move Down",
value: "down",
class: "icon-arrow-down"
},
{
name: "Move to Bottom",
value: "bottom",
class: "icon-arrow-double-down"
}
],
method: function (option) {
selectionPath[1].context.orderItem(option.value, getAllTypes(selection));
}
};
}
function getXInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".x";
},
label: "X:",
title: "X position"
};
}
}
function getYInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".y";
},
label: "Y:",
title: "Y position",
};
}
}
function getWidthInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: 'input',
type: 'number',
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".width";
},
label: 'W:',
title: 'Resize object width'
};
}
}
function getHeightInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: 'input',
type: 'number',
domainObject: selectedParent,
applicableSelectedItems: getAllTypes(selection),
property: function (selectionPath) {
return getPath(selectionPath) + ".height";
},
label: 'H:',
title: 'Resize object height'
};
}
}
function getX2Input(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".x2";
},
label: "X2:",
title: "X2 position"
};
}
}
function getY2Input(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".y2";
},
label: "Y2:",
title: "Y2 position",
};
}
}
function getTextSizeMenu(selectedParent, selection) {
const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128];
let fill = {
control: "color-picker",
domainObject: selectedParent,
property: function () {
return getPath() + ".fill";
},
icon: "icon-paint-bucket",
title: "Set fill color"
},
stroke = {
control: "color-picker",
domainObject: selectedParent,
property: function () {
return getPath() + ".stroke";
},
icon: "icon-line-horz",
title: "Set border color"
},
color = {
control: "color-picker",
domainObject: selectedParent,
property: function () {
return getPath() + ".color";
},
icon: "icon-font",
mandatory: true,
title: "Set text color",
preventNone: true
},
size = {
return {
control: "select-menu",
domainObject: selectedParent,
property: function () {
return getPath() + ".size";
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' || type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".size";
},
title: "Set text size",
options: TEXT_SIZE.map(size => {
@ -324,13 +346,128 @@ define([], function () {
};
})
};
}
if (layoutItem.type === 'telemetry-view') {
let displayMode = {
function getFillMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".fill";
},
icon: "icon-paint-bucket",
title: "Set fill color"
};
}
function getStrokeMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".stroke";
},
icon: "icon-line-horz",
title: "Set border color"
};
}
function getTextColorMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' || type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".color";
},
icon: "icon-font",
mandatory: true,
title: "Set text color",
preventNone: true
};
}
function getURLButton(selectedParent, selection) {
return {
control: "button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'image-view';
}),
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-image",
title: "Edit image properties",
dialog: DIALOG_FORM['image']
};
}
function getTextButton(selectedParent, selection) {
return {
control: "button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'text-view';
}),
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-gear",
title: "Edit text properties",
dialog: DIALOG_FORM['text']
};
}
function getTelemetryValueMenu(selectionPath, selection) {
if (selection.length === 1) {
return {
control: "select-menu",
domainObject: selectionPath[1].context.item,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".value";
},
title: "Set value",
options: openmct.telemetry.getMetadata(selectionPath[0].context.item).values().map(value => {
return {
name: value.name,
value: value.key
}
})
};
}
}
function getDisplayModeMenu(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "select-menu",
domainObject: selectedParent,
property: function () {
return getPath() + ".displayMode";
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".displayMode";
},
title: "Set display mode",
options: [
@ -347,146 +484,196 @@ define([], function () {
value: "value"
}
]
},
value = {
control: "select-menu",
domainObject: selectedParent,
property: function () {
return getPath() + ".value";
},
title: "Set value",
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
return {
name: value.name,
value: value.key
}
})
};
toolbar = [
displayMode,
separator,
value,
separator,
fill,
stroke,
color,
separator,
size,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
remove
];
} else if (layoutItem.type === 'text-view') {
let text = {
control: "button",
domainObject: selectedParent,
property: function () {
return getPath();
},
icon: "icon-gear",
title: "Edit text properties",
dialog: DIALOG_FORM['text']
};
toolbar = [
fill,
stroke,
separator,
color,
size,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
text,
separator,
remove
];
} else if (layoutItem.type === 'box-view') {
toolbar = [
fill,
stroke,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
remove
];
} else if (layoutItem.type === 'image-view') {
let url = {
control: "button",
domainObject: selectedParent,
property: function () {
return getPath();
},
icon: "icon-image",
title: "Edit image properties",
dialog: DIALOG_FORM['image']
};
toolbar = [
stroke,
separator,
stackOrder,
x,
y,
height,
width,
useGrid,
separator,
url,
separator,
remove
];
} else if (layoutItem.type === 'line-view') {
let x2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".x2";
},
label: "X2:",
title: "X2 position"
},
y2 = {
control: "input",
type: "number",
domainObject: selectedParent,
property: function () {
return getPath() + ".y2";
},
label: "Y2:",
title: "Y2 position",
};
toolbar = [
stroke,
separator,
stackOrder,
x,
y,
x2,
y2,
useGrid,
separator,
remove
];
}
}
return toolbar;
function getSeparator() {
return {
control: "separator"
};
}
function isMainLayoutSelected(selectionPath) {
let selectedObject = selectionPath[0].context.item;
return selectedObject && selectedObject.type === 'layout' &&
!selectionPath[0].context.layoutItem;
}
if (isMainLayoutSelected(selection[0])) {
return [getAddButton(selection)];
}
let toolbar = {
'add-menu': [],
'toggle-frame': [],
'display-mode': [],
'telemetry-value': [],
'style': [],
'text-style': [],
'position': [],
'text': [],
'url': [],
'remove': [],
};
selection.forEach(selectionPath => {
let selectedParent = selectionPath[1].context.item;
let layoutItem = selectionPath[0].context.layoutItem;
if (layoutItem.type === 'subobject-view') {
if (toolbar['add-menu'].length === 0 && selectionPath[0].context.item.type === 'layout') {
toolbar['add-menu'] = [getAddButton(selection, selectionPath)];
}
if (toolbar['toggle-frame'].length === 0) {
toolbar['toggle-frame'] = [getToggleFrameButton(selectedParent, selection)];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
} else if (layoutItem.type === 'telemetry-view') {
if (toolbar['display-mode'].length === 0) {
toolbar['display-mode'] = [getDisplayModeMenu(selectedParent, selection)];
}
if (toolbar['telemetry-value'].length === 0) {
toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selection)];
}
if (toolbar['style'].length < 2) {
toolbar['style'] = [
getFillMenu(selectedParent, selection),
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selection),
getTextSizeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
} else if (layoutItem.type === 'text-view') {
if (toolbar['style'].length < 2) {
toolbar['style'] = [
getFillMenu(selectedParent, selection),
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selection),
getTextSizeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['text'].length === 0) {
toolbar['text'] = [getTextButton(selectedParent, selection)];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
} else if (layoutItem.type === 'box-view') {
if (toolbar['style'].length < 2) {
toolbar['style'] = [
getFillMenu(selectedParent, selection),
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
} else if (layoutItem.type === 'image-view') {
if (toolbar['style'].length === 0) {
toolbar['style'] = [
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getHeightInput(selectedParent, selection),
getWidthInput(selectedParent, selection)
];
}
if (toolbar['url'].length === 0) {
toolbar['url'] = [getURLButton(selectedParent, selection)];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
} else if (layoutItem.type === 'line-view') {
if (toolbar['style'].length === 0) {
toolbar['style'] = [
getStrokeMenu(selectedParent, selection)
];
}
if (toolbar['position'].length === 0) {
toolbar['position'] = [
getStackOrder(selectedParent, selectionPath),
getXInput(selectedParent, selection),
getYInput(selectedParent, selection),
getX2Input(selectedParent, selection),
getY2Input(selectedParent, selection)
];
}
if (toolbar['remove'].length === 0) {
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
}
}
});
let toolbarArray = Object.values(toolbar);
return _.flatten(toolbarArray.reduce((accumulator, group, index) => {
group = group.filter(control => control !== undefined);
if (group.length > 0) {
accumulator.push(group);
if (index < toolbarArray.length - 1) {
accumulator.push(getSeparator());
}
}
return accumulator;
}, []));
}
}
}

View File

@ -95,7 +95,7 @@ define(
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
*/
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(
@ -109,6 +109,16 @@ define(
};
};
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(
this.rawPosition.position,
multiply(gridDelta, this.posFactor)
), [0, 0])
};
};
return LayoutDrag;
}

View File

@ -23,7 +23,8 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-box-view"
:style="style">
</div>
@ -54,8 +55,7 @@
x: 1,
y: 1,
width: 10,
height: 5,
useGrid: true
height: 5
};
},
inject: ['openmct'],

View File

@ -23,8 +23,11 @@
<template>
<div class="l-layout"
@dragover="handleDragOver"
@click="bypassSelection"
@drop="handleDrop">
@click.capture="bypassSelection"
@drop="handleDrop"
:class="{
'is-multi-selected': selectedLayoutItems.length > 1
}">
<!-- Background grid -->
<div class="l-layout__grid-holder c-grid">
<div class="c-grid__x l-grid l-grid-x"
@ -39,18 +42,38 @@
:is="item.type"
:item="item"
:key="item.id"
:gridSize="item.useGrid ? gridSize : [1, 1]"
:gridSize="gridSize"
:initSelect="initSelectIndex === index"
:index="index"
@endDrag="endDrag"
>
:multiSelect="selectedLayoutItems.length > 1"
@move="move"
@endMove="endMove"
@endLineResize='endLineResize'>
</component>
<edit-marquee v-if='showMarquee'
:gridSize="gridSize"
:selectedLayoutItems="selectedLayoutItems"
@endResize="endResize">
</edit-marquee>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
@mixin displayMarquee($c) {
> .c-frame-edit {
// All other frames
//@include test($c, 0.4);
display: block;
}
> .c-frame > .c-frame-edit {
// Line object frame
//@include test($c, 0.4);
display: block;
}
}
.l-layout {
@include abs();
display: flex;
@ -70,7 +93,7 @@
.l-shell__main-container {
&[s-selected],
&[s-selected-parent] {
// Display grid in main layout holder when editing
// Display grid and allow edit marquee to display in main layout holder when editing
> .l-layout {
background: $editUIGridColorBg;
@ -84,7 +107,7 @@
.l-layout__frame {
&[s-selected],
&[s-selected-parent] {
// Display grid in nested layouts when editing
// Display grid and allow edit marquee to display in nested layouts when editing
> * > * > .l-layout {
background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
@ -95,10 +118,21 @@
}
}
}
/*********************** EDIT MARQUEE CONTROL */
*[s-selected-parent] {
> .l-layout {
// When main shell layout is the parent
@include displayMarquee(deeppink);
}
> * > * > * {
// When a sub-layout is the parent
@include displayMarquee(blue);
}
}
}
</style>
<script>
import uuid from 'uuid';
@ -108,6 +142,7 @@
import TextView from './TextView.vue'
import LineView from './LineView.vue'
import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue'
const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView,
@ -123,9 +158,11 @@
down: -1,
bottom: Number.NEGATIVE_INFINITY
};
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee;
function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
@ -141,7 +178,8 @@
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return {
internalDomainObject: domainObject,
initSelectIndex: undefined
initSelectIndex: undefined,
selection: []
};
},
computed: {
@ -150,82 +188,145 @@
},
layoutItems() {
return this.internalDomainObject.configuration.items;
},
selectedLayoutItems() {
return this.layoutItems.filter(item => {
return this.itemIsInCurrentSelection(item);
});
},
showMarquee() {
let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 &&
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view';
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
}
},
inject: ['openmct'],
inject: ['openmct', 'options'],
props: ['domainObject'],
components: ITEM_TYPE_VIEW_MAP,
components: components,
methods: {
addElement(itemType, element) {
this.addItem(itemType + '-view', element);
},
setSelection(selection) {
if (selection.length === 0) {
return;
}
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
let itemIndex = selection[0].context.index;
if (itemIndex !== undefined) {
this.attachSelectionListener(itemIndex);
}
this.selection = selection;
},
attachSelectionListener(index) {
let path = `configuration.items[${index}].useGrid`;
this.removeSelectionListener = this.openmct.objects.observe(this.internalDomainObject, path, function (value) {
let item = this.layoutItems[index];
if (value) {
item.x = Math.round(item.x / this.gridSize[0]);
item.y = Math.round(item.y / this.gridSize[1]);
item.width = Math.round(item.width / this.gridSize[0]);
item.height = Math.round(item.height / this.gridSize[1]);
if (item.x2) {
item.x2 = Math.round(item.x2 / this.gridSize[0]);
}
if (item.y2) {
item.y2 = Math.round(item.y2 / this.gridSize[1]);
}
} else {
item.x = this.gridSize[0] * item.x;
item.y = this.gridSize[1] * item.y;
item.width = this.gridSize[0] * item.width;
item.height = this.gridSize[1] * item.height;
if (item.x2) {
item.x2 = this.gridSize[0] * item.x2;
}
if (item.y2) {
item.y2 = this.gridSize[1] * item.y2;
}
}
item.useGrid = value;
this.mutate(`configuration.items[${index}]`, item);
}.bind(this));
itemIsInCurrentSelection(item) {
return this.selection.some(selectionPath =>
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id);
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopImmediatePropagation();
}
this.dragInProgress = false;
return;
}
},
endDrag(item, updates) {
endLineResize(item, updates) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
let index = this.layoutItems.indexOf(item);
Object.assign(item, updates);
this.mutate(`configuration.items[${index}]`, item);
},
endResize(scaleWidth, scaleHeight, marqueeStart, marqueeOffset) {
this.dragInProgress = true;
this.layoutItems.forEach(item => {
if (this.itemIsInCurrentSelection(item)) {
let itemXInMarqueeSpace = item.x - marqueeStart.x;
let itemXInMarqueeSpaceAfterScale = Math.round(itemXInMarqueeSpace * scaleWidth);
item.x = itemXInMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
let itemYInMarqueeSpace = item.y - marqueeStart.y;
let itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight);
item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
if (item.x2) {
let itemX2InMarqueeSpace = item.x2 - marqueeStart.x;
let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth);
item.x2 = itemX2InMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
} else {
item.width = Math.round(item.width * scaleWidth);
}
if (item.y2) {
let itemY2InMarqueeSpace = item.y2 - marqueeStart.y;
let itemY2InMarqueeSpaceAfterScale = Math.round(itemY2InMarqueeSpace * scaleHeight);
item.y2 = itemY2InMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
} else {
item.height = Math.round(item.height * scaleHeight);
}
}
});
this.mutate("configuration.items", this.layoutItems);
},
move(gridDelta) {
this.dragInProgress = true;
if (!this.initialPositions) {
this.initialPositions = {};
_.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => {
if (selectedItem.type === 'line-view') {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2];
this.startingMinX2 = this.startingMinX2 !== undefined ? Math.min(this.startingMinX2, selectedItem.x2) : selectedItem.x2;
this.startingMinY2 = this.startingMinY2 !== undefined ? Math.min(this.startingMinY2, selectedItem.y2) : selectedItem.y2;
} else {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y];
}
this.startingMinX = this.startingMinX !== undefined ? Math.min(this.startingMinX, selectedItem.x) : selectedItem.x;
this.startingMinY = this.startingMinY !== undefined ? Math.min(this.startingMinY, selectedItem.y) : selectedItem.y;
});
}
let layoutItems = this.layoutItems.map(item => {
if (this.initialPositions[item.id]) {
this.updateItemPosition(item, gridDelta);
}
return item;
});
},
updateItemPosition(item, gridDelta) {
let startingPosition = this.initialPositions[item.id];
let [startingX, startingY, startingX2, startingY2] = startingPosition;
if (this.startingMinX + gridDelta[0] >= 0) {
if (item.x2 !== undefined) {
if (this.startingMinX2 + gridDelta[0] >= 0) {
item.x = startingX + gridDelta[0];
}
} else {
item.x = startingX + gridDelta[0];
}
}
if (this.startingMinY + gridDelta[1] >= 0) {
if (item.y2 !== undefined) {
if (this.startingMinY2 + gridDelta[1] >= 0) {
item.y = startingY + gridDelta[1];
}
} else {
item.y = startingY + gridDelta[1];
}
}
if (item.x2 !== undefined && this.startingMinX2 + gridDelta[0] >= 0 && this.startingMinX + gridDelta[0] >= 0) {
item.x2 = startingX2 + gridDelta[0];
}
if (item.y2 !== undefined && this.startingMinY2 + gridDelta[1] >= 0 && this.startingMinY + gridDelta[1] >= 0) {
item.y2 = startingY2 + gridDelta[1];
}
},
endMove() {
this.mutate('configuration.items', this.layoutItems);
this.initialPositions = undefined;
this.startingMinX = undefined;
this.startingMinY = undefined;
this.startingMinX2 = undefined;
this.startingMinY2 = undefined;
},
mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value);
},
@ -283,9 +384,8 @@
}
},
isTelemetry(domainObject) {
if (this.openmct.telemetry.isTelemetryObject(domainObject)
&& domainObject.type !== 'summary-widget'
&& domainObject.type !== 'example.imagery') {
if (this.openmct.telemetry.isTelemetryObject(domainObject) &&
!this.options.showAsView.includes(domainObject.type)) {
return true;
} else {
return false;
@ -314,11 +414,15 @@
this.objectViewMap[keyString] = true;
}
},
removeItem(item, index) {
removeItem(selectedItems) {
let indices = [];
this.initSelectIndex = -1;
this.layoutItems.splice(index, 1);
selectedItems.forEach(selectedItem => {
indices.push(selectedItem[0].context.index);
this.untrackItem(selectedItem[0].context.layoutItem);
});
_.pullAt(this.layoutItems, indices);
this.mutate("configuration.items", this.layoutItems);
this.untrackItem(item);
this.$el.click();
},
untrackItem(item) {
@ -384,20 +488,74 @@
this.mutate("configuration.items", layoutItems);
this.$el.click();
},
orderItem(position, index) {
orderItem(position, selectedItems) {
let delta = ORDERS[position];
let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0);
let item = this.layoutItems[index];
let indices = [];
let newIndex = -1;
let items = [];
if (newIndex !== index) {
this.layoutItems.splice(index, 1);
this.layoutItems.splice(newIndex, 0, item);
this.mutate('configuration.items', this.layoutItems);
Object.assign(items, this.layoutItems);
this.selectedLayoutItems.forEach(selectedItem => {
indices.push(this.layoutItems.indexOf(selectedItem));
});
indices.sort((a, b) => a - b);
if (this.removeSelectionListener) {
this.removeSelectionListener();
this.attachSelectionListener(newIndex);
if (position === 'top' || position === 'up') {
indices.reverse();
}
if (position === 'top' || position === 'bottom') {
this.moveToTopOrBottom(position, indices, items, delta);
} else {
this.moveUpOrDown(position, indices, items, delta);
}
this.mutate('configuration.items', this.layoutItems);
},
moveUpOrDown(position, indices, items, delta) {
let previousItemIndex = -1;
let newIndex = -1;
indices.forEach((itemIndex, index) => {
let isAdjacentItemSelected = position === 'up' ?
itemIndex + 1 === previousItemIndex :
itemIndex - 1 === previousItemIndex;
if (index > 0 && isAdjacentItemSelected) {
if (position === 'up') {
newIndex -= 1;
} else {
newIndex += 1;
}
} else {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
}
previousItemIndex = itemIndex;
this.updateItemOrder(newIndex, itemIndex, items);
});
},
moveToTopOrBottom(position, indices, items, delta) {
let newIndex = -1;
indices.forEach((itemIndex, index) => {
if (index === 0) {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
} else {
if (position === 'top') {
newIndex -= 1;
} else {
newIndex += 1;
}
}
this.updateItemOrder(newIndex, itemIndex, items);
});
},
updateItemOrder(newIndex, itemIndex, items) {
if (newIndex !== itemIndex) {
this.layoutItems.splice(itemIndex, 1);
this.layoutItems.splice(newIndex, 0, items[itemIndex]);
}
}
},
@ -413,14 +571,10 @@
this.composition.load();
},
destroyed: function () {
this.openmct.off('change', this.setSelection);
this.openmct.selection.off('change', this.setSelection);
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.unlisten();
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
}
}
</script>

View File

@ -0,0 +1,233 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<!-- Resize handles -->
<div class="c-frame-edit" :style="style">
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startResize([1,1], [-1,-1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startResize([0,1], [1,-1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startResize([1,0], [-1,1], $event)"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startResize([0,0], [1,1], $event)"></div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-frame-edit {
// In Layouts, this is the editing rect and handles
display: none; // Set to display: block in DisplayLayout.vue
pointer-events: none;
@include abs();
border: $editMarqueeBorder;
&__handle {
$d: 6px;
$o: floor($d * -0.5);
background: $editFrameColorHandleFg;
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
pointer-events: all;
position: absolute;
width: $d; height: $d;
top: auto; right: auto; bottom: auto; left: auto;
&:before {
// Extended hit area
@include abs(-10px);
content: '';
display: block;
z-index: 0;
}
&:hover {
background: $editUIColor;
}
&--nwse {
cursor: nwse-resize;
}
&--nw {
cursor: nw-resize;
left: $o; top: $o;
}
&--ne {
cursor: ne-resize;
right: $o; top: $o;
}
&--se {
cursor: se-resize;
right: $o; bottom: $o;
}
&--sw {
cursor: sw-resize;
left: $o; bottom: $o;
}
}
}
</style>
<script>
import LayoutDrag from './../LayoutDrag'
export default {
inject: ['openmct'],
props: {
selectedLayoutItems: Array,
gridSize: Array
},
data() {
return {
dragPosition: undefined
}
},
computed: {
style() {
let x = Number.POSITIVE_INFINITY;
let y = Number.POSITIVE_INFINITY;
let width = Number.NEGATIVE_INFINITY;
let height = Number.NEGATIVE_INFINITY;
this.selectedLayoutItems.forEach(item => {
if (item.x2 !== undefined) {
let lineWidth = Math.abs(item.x - item.x2);
let lineMinX = Math.min(item.x, item.x2);
x = Math.min(lineMinX, x);
width = Math.max(lineWidth + lineMinX, width);
} else {
x = Math.min(item.x, x);
width = Math.max(item.width + item.x, width);
}
if (item.y2 !== undefined) {
let lineHeight = Math.abs(item.y - item.y2);
let lineMinY = Math.min(item.y, item.y2);
y = Math.min(lineMinY, y);
height = Math.max(lineHeight + lineMinY, height);
} else {
y = Math.min(item.y, y);
height = Math.max(item.height + item.y, height);
}
});
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
} else {
width = width - x;
height = height - y;
}
this.marqueePosition = {
x: x,
y: y,
width: width,
height: height
}
return this.getMarqueeStyle(x, y, width, height);
}
},
methods: {
getMarqueeStyle(x, y, width, height) {
return {
left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px',
width: (this.gridSize[0] * width) + 'px',
height: (this.gridSize[1] * height) + 'px'
};
},
updatePosition(event) {
let currentPosition = [event.pageX, event.pageY];
this.initialPosition = this.initialPosition || currentPosition;
this.delta = currentPosition.map(function (value, index) {
return value - this.initialPosition[index];
}.bind(this));
},
startResize(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueResize);
document.body.addEventListener('mouseup', this.endResize);
this.marqueeStartPosition = {
position: [this.marqueePosition.x, this.marqueePosition.y],
dimensions: [this.marqueePosition.width, this.marqueePosition.height]
};
this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.marqueeStartPosition, posFactor, dimFactor, this.gridSize);
event.preventDefault();
},
continueResize(event) {
event.preventDefault();
this.updatePosition(event);
this.dragPosition = this.activeDrag.getAdjustedPositionAndDimensions(this.delta);
},
endResize(event) {
document.body.removeEventListener('mousemove', this.continueResize);
document.body.removeEventListener('mouseup', this.endResize);
this.continueResize(event);
let marqueeStartWidth = this.marqueeStartPosition.dimensions[0];
let marqueeStartHeight = this.marqueeStartPosition.dimensions[1];
let marqueeStartX = this.marqueeStartPosition.position[0];
let marqueeStartY = this.marqueeStartPosition.position[1];
let marqueeEndX = this.dragPosition.position[0];
let marqueeEndY = this.dragPosition.position[1];
let marqueeEndWidth = this.dragPosition.dimensions[0];
let marqueeEndHeight = this.dragPosition.dimensions[1];
let scaleWidth = marqueeEndWidth / marqueeStartWidth;
let scaleHeight = marqueeEndHeight / marqueeStartHeight;
let marqueeStart = {
x: marqueeStartX,
y: marqueeStartY,
height: marqueeStartWidth,
width: marqueeStartHeight
};
let marqueeEnd = {
x: marqueeEndX,
y: marqueeEndY,
width: marqueeEndWidth,
height: marqueeEndHeight
};
let marqueeOffset = {
x: marqueeEnd.x - marqueeStart.x,
y: marqueeEnd.y - marqueeStart.y
};
this.$emit('endResize', scaleWidth, scaleHeight, marqueeStart, marqueeOffset);
this.dragPosition = undefined;
this.initialPosition = undefined;
this.marqueeStartPosition = undefined;
this.delta = undefined;
event.preventDefault();
}
}
}
</script>

View File

@ -23,7 +23,8 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-image-view"
:style="style">
</div>
@ -56,8 +57,7 @@
y: 1,
width: 10,
height: 5,
url: element.url,
useGrid: true
url: element.url
};
},
inject: ['openmct'],

View File

@ -24,25 +24,14 @@
<div class="l-layout__frame c-frame"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable,
'is-resizing': isResizing
'u-inspectable': inspectable
}"
:style="style">
<slot></slot>
<!-- Drag handles -->
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag([1,1], [0,0], $event, 'move')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startDrag([1,1], [-1,-1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startDrag([0,1], [1,-1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startDrag([1,0], [-1,1], $event, 'resize')"></div>
<div class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startDrag([0,0], [1,1], $event, 'resize')"></div>
<div class="c-frame-edit__move"
@mousedown="startMove([1,1], [0,0], $event)">
</div>
</div>
</template>
@ -50,7 +39,7 @@
<style lang="scss">
@import "~styles/sass-base";
/******************************* FRAME */
/******************* FRAME */
.c-frame {
display: flex;
flex-direction: column;
@ -59,124 +48,15 @@
> *:first-child {
flex: 1 1 auto;
}
&:not(.no-frame) {
background: $colorBodyBg;
border: $browseFrameBorder;
padding: $interiorMargin;
}
}
.c-frame-edit {
// In Layouts, this is the editing rect and handles
// In Fixed Position, this is a wrapper element
@include abs();
.c-frame-edit__move {
display: none;
&__move {
@include abs();
cursor: move;
}
&__handle {
$d: 6px;
$o: floor($d * -0.5);
background: $editFrameColorHandleFg;
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
display: none; // Set to block via s-selected selector
position: absolute;
width: $d; height: $d;
top: auto; right: auto; bottom: auto; left: auto;
&:before {
// Extended hit area
@include abs(-10px);
content: '';
display: block;
z-index: 0;
}
&:hover {
background: $editUIColor;
}
&--nwse {
cursor: nwse-resize;
}
&--nw {
cursor: nw-resize;
left: $o; top: $o;
}
&--ne {
cursor: ne-resize;
right: $o; top: $o;
}
&--se {
cursor: se-resize;
right: $o; bottom: $o;
}
&--sw {
cursor: sw-resize;
left: $o; bottom: $o;
}
}
}
.c-so-view.has-complex-content + .c-frame-edit {
// Target frames that hold domain objects that include header elements, as opposed to drawing and alpha objects
// Make the __move element a more affordable drag UI element
.c-frame-edit__move {
@include userSelectNone();
background: $editFrameMovebarColorBg;
box-shadow: rgba(black, 0.2) 0 1px;
bottom: auto;
height: 0; // Height is set on hover on s-selected.c-frame
opacity: 0.8;
max-height: 100%;
overflow: hidden;
text-align: center;
&:before {
// Grippy
$h: 4px;
$tbOffset: ($editFrameMovebarH - $h) / 2;
$lrOffset: 25%;
@include grippy($editFrameMovebarColorFg);
content: '';
display: block;
position: absolute;
top: $tbOffset; right: $lrOffset; bottom: $tbOffset; left: $lrOffset;
}
&:hover {
background: $editFrameHovMovebarColorBg;
&:before { @include grippy($editFrameHovMovebarColorFg); }
}
}
}
.is-editing {
/******************* STYLES FOR C-FRAME WHILE EDITING */
.c-frame {
$moveBarOutDelay: 500ms;
&.no-frame {
border: $editFrameBorder; // Base border style for a frame element while editing.
}
&-edit {
display: contents;
}
&-edit__move,
.c-so-view {
transition: $transOut;
transition-delay: $moveBarOutDelay;
}
&:not([s-selected]) {
&:hover {
border: $editFrameBorderHov;
@ -188,37 +68,110 @@
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw;
> .c-frame-edit {
[class*='__handle'] {
.c-frame-edit__move {
cursor: move;
}
}
}
/******************* DEFAULT STYLES FOR -EDIT__MOVE */
// All object types
.c-frame-edit__move {
@include abs();
display: block;
}
// Has-complex-content objects
.c-so-view.has-complex-content {
transition: $transOut;
transition-delay: $moveBarOutDelay;
> .c-so-view__local-controls {
transition: transform 250ms ease-in-out;
transition-delay: $moveBarOutDelay;
}
+ .c-frame-edit__move {
display: none;
}
}
.l-layout {
/******************* 0 - 1 ITEM SELECTED */
&:not(.is-multi-selected) {
> .l-layout__frame[s-selected] {
> .c-so-view.has-complex-content {
> .c-so-view__local-controls {
transition: transform $transOutTime ease-in-out;
transition-delay: $moveBarOutDelay;
}
+ .c-frame-edit__move {
transition: $transOut;
transition-delay: $moveBarOutDelay;
@include userSelectNone();
background: $editFrameMovebarColorBg;
box-shadow: rgba(black, 0.2) 0 1px;
bottom: auto;
display: block;
height: 0; // Height is set on hover below
opacity: 0.8;
max-height: 100%;
overflow: hidden;
text-align: center;
&:before {
// Grippy
$h: 4px;
$tbOffset: ($editFrameMovebarH - $h) / 2;
$lrOffset: 25%;
@include grippy($editFrameMovebarColorFg);
content: '';
display: block;
position: absolute;
top: $tbOffset;
right: $lrOffset;
bottom: $tbOffset;
left: $lrOffset;
}
}
}
&:hover {
> .c-so-view.has-complex-content {
transition: $transIn;
transition-delay: 0s;
padding-top: $editFrameMovebarH + $interiorMarginSm;
> .c-so-view__local-controls {
transform: translateY($editFrameMovebarH);
transition: transform $transInTime ease-in-out;
transition-delay: 0s;
}
+ .c-frame-edit__move {
transition: $transIn;
transition-delay: 0s;
height: $editFrameMovebarH;
}
}
}
}
}
/******************* > 1 ITEMS SELECTED */
&.is-multi-selected {
.l-layout__frame[s-selected] {
> .c-so-view.has-complex-content + .c-frame-edit__move {
display: block;
}
}
}
}
.l-layout__frame:not(.is-resizing) {
// Show and animate the __move bar for sub-object views with complex content
&:hover > .c-so-view.has-complex-content {
// Move content down so the __move bar doesn't cover it.
padding-top: $editFrameMovebarH;
transition: $transIn;
&.c-so-view--no-frame {
// Move content down with a bit more space
padding-top: $editFrameMovebarH + $interiorMarginSm;
}
// Show the move bar
+ .c-frame-edit .c-frame-edit__move {
height: $editFrameMovebarH;
transition: $transIn;
}
}
}
}
</style>
<script>
import LayoutDrag from './../LayoutDrag'
@ -228,21 +181,9 @@
item: Object,
gridSize: Array
},
data() {
return {
dragPosition: undefined,
isResizing: undefined
}
},
computed: {
style() {
let {x, y, width, height} = this.item;
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
}
return {
left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px',
@ -264,36 +205,40 @@
return value - this.initialPosition[index];
}.bind(this));
},
startDrag(posFactor, dimFactor, event, type) {
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
startMove(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueMove);
document.body.addEventListener('mouseup', this.endMove);
this.dragPosition = {
position: [this.item.x, this.item.y],
dimensions: [this.item.width, this.item.height]
position: [this.item.x, this.item.y]
};
this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
this.isResizing = type === 'resize';
event.preventDefault();
},
continueDrag(event) {
continueMove(event) {
event.preventDefault();
this.updatePosition(event);
this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
let newPosition = this.activeDrag.getAdjustedPosition(this.delta);
if (!_.isEqual(newPosition, this.dragPosition)) {
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(this.delta));
}
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
this.continueDrag(event);
let [x, y] = this.dragPosition.position;
let [width, height] = this.dragPosition.dimensions;
this.$emit('endDrag', this.item, {x, y, width, height});
endMove(event) {
document.body.removeEventListener('mousemove', this.continueMove);
document.body.removeEventListener('mouseup', this.endMove);
this.continueMove(event);
this.$emit('endMove');
this.dragPosition = undefined;
this.initialPosition = undefined;
this.delta = undefined;
this.isResizing = undefined;
event.preventDefault();
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
}
}
}

View File

@ -30,9 +30,9 @@
</line>
</svg>
<div class="c-frame-edit">
<div class="c-frame-edit__move"
@mousedown="startDrag($event)"></div>
<div class="c-frame-edit__move"
@mousedown="startDrag($event)"></div>
<div class="c-frame-edit" v-if="showFrameEdit">
<div class="c-frame-edit__handle"
:class="startHandleClass"
@mousedown="startDrag($event, 'start')"></div>
@ -66,8 +66,7 @@
y: 10,
x2: 10,
y2: 5,
stroke: '#717171',
useGrid: true
stroke: '#717171'
};
},
inject: ['openmct'],
@ -76,24 +75,31 @@
gridSize: Array,
initSelect: Boolean,
index: Number,
multiSelect: Boolean
},
data() {
return {
dragPosition: undefined
dragPosition: undefined,
dragging: undefined,
selection: []
};
},
computed: {
showFrameEdit() {
let layoutItem = this.selection.length > 0 && this.selection[0][0].context.layoutItem;
return !this.multiSelect && layoutItem && layoutItem.id === this.item.id;
},
position() {
let {x, y, x2, y2} = this.item;
if (this.dragPosition) {
if (this.dragging && this.dragPosition) {
({x, y, x2, y2} = this.dragPosition);
}
return {x, y, x2, y2};
},
style() {
let {x, y, x2, y2} = this.position;
let width = this.gridSize[0] * Math.abs(x - x2);
let height = this.gridSize[1] * Math.abs(y - y2);
let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1);
let height = Math.max(this.gridSize[1] * Math.abs(y - y2), 1);
let left = this.gridSize[0] * Math.min(x, x2);
let top = this.gridSize[1] * Math.min(y, y2);
return {
@ -175,13 +181,27 @@
event.preventDefault();
let pxDeltaX = this.startPosition[0] - event.pageX;
let pxDeltaY = this.startPosition[1] - event.pageY;
this.dragPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
let newPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
if (!this.dragging) {
if (!_.isEqual(newPosition, this.dragPosition)) {
let gridDelta = [event.pageX - this.startPosition[0], event.pageY - this.startPosition[1]];
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(gridDelta));
}
} else {
this.dragPosition = newPosition;
}
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
let {x, y, x2, y2} = this.dragPosition;
this.$emit('endDrag', this.item, {x, y, x2, y2});
if (!this.dragging) {
this.$emit('endMove');
} else {
this.$emit('endLineResize', this.item, {x, y, x2, y2});
}
this.dragPosition = undefined;
this.dragging = undefined;
event.preventDefault();
@ -191,6 +211,7 @@
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
let {x, y, x2, y2} = this.item;
let dragPosition = {x, y, x2, y2};
if (this.dragging === 'start') {
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
@ -205,6 +226,14 @@
dragPosition.y2 -= gridDeltaY;
}
return dragPosition;
},
setSelection(selection) {
this.selection = selection;
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
}
},
watch: {
@ -217,6 +246,7 @@
}
},
mounted() {
this.openmct.selection.on('change', this.setSelection);
this.context = {
layoutItem: this.item,
index: this.index
@ -228,6 +258,7 @@
if (this.removeSelectable) {
this.removeSelectable();
}
this.openmct.selection.off('change', this.setSelection);
}
}
</script>

View File

@ -22,7 +22,9 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
:title="domainObject && domainObject.name"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<object-frame v-if="domainObject"
:domain-object="domainObject"
:object-path="objectPath"
@ -66,8 +68,7 @@
x: position[0],
y: position[1],
identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type),
useGrid: true
hasFrame: hasFrameByDefault(domainObject.type)
};
},
inject: ['openmct'],

View File

@ -23,7 +23,8 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-telemetry-view"
:style="styleObject"
v-if="domainObject">
@ -96,10 +97,9 @@
displayMode: 'all',
value: metadata.getDefaultDisplayValue(),
stroke: "transparent",
fill: "",
fill: "transparent",
color: "",
size: "13px",
useGrid: true
size: "13px"
};
},
inject: ['openmct'],
@ -176,7 +176,8 @@
let options = {
start: bounds.start,
end: bounds.end,
size: 1
size: 1,
strategy: 'latest'
};
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {

View File

@ -23,7 +23,8 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-text-view"
:style="style">
{{ item.text }}
@ -59,8 +60,7 @@
y: 1,
width: 10,
height: 5,
text: element.text,
useGrid: true
text: element.text
};
},
inject: ['openmct'],

View File

@ -25,8 +25,7 @@ import Vue from 'vue'
import objectUtils from '../../api/objects/object-utils.js'
import DisplayLayoutType from './DisplayLayoutType.js'
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
export default function () {
export default function DisplayLayoutPlugin(options) {
return function (openmct) {
openmct.objectViews.addProvider({
key: 'layout.view',
@ -47,7 +46,8 @@ export default function () {
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>',
provide: {
openmct,
objectUtils
objectUtils,
options
},
el: container,
data () {
@ -60,6 +60,7 @@ export default function () {
getSelectionContext() {
return {
item: domainObject,
supportsMultiSelect: true,
addElement: component && component.$refs.displayLayout.addElement,
removeItem: component && component.$refs.displayLayout.removeItem,
orderItem: component && component.$refs.displayLayout.orderItem
@ -83,5 +84,6 @@ export default function () {
return true;
}
});
DisplayLayoutPlugin._installed = true;
}
}

View File

@ -42,7 +42,10 @@
<!-- Checkbox list, NOT editing -->
<template v-if="filter.possibleValues && !isEditing">
<span>{{persistedFilters[filter.comparator].join(', ')}}</span>
<span
v-if="persistedFilters[filter.comparator]">
{{persistedFilters[filter.comparator].join(', ')}}
</span>
</template>
</div>
</li>

View File

@ -5,11 +5,11 @@
<span class="c-disclosure-triangle is-enabled flex-elem"
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
<div class="c-tree__item__label">
<div class="t-object-label l-flex-row flex-elem grows">
<div class="t-item-icon flex-elem"
<div class="c-object-label">
<div class="c-object-label__type-icon"
:class="objectCssClass">
</div>
<div class="t-title-label flex-elem grows">{{ filterObject.name }}</div>
<div class="c-object-label__name flex-elem grows">{{ filterObject.name }}</div>
</div>
</div>
</div>
@ -69,16 +69,17 @@ export default {
}
} else {
if (!this.updatedFilters[key]) {
this.updatedFilters[key] = {};
this.$set(this.updatedFilters, key, {});
}
this.updatedFilters[key][comparator] = [value ? valueName : undefined];
this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]);
}
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
updateTextFilter(key, comparator, value) {
if (!this.updatedFilters[key]) {
this.updatedFilters[key] = {};
this.$set(this.updatedFilters, key, {});
this.$set(this.updatedFilters[key], comparator, '');
}
this.updatedFilters[key][comparator] = value;
this.$emit('updateFilters', this.keyString, this.updatedFilters);

View File

@ -23,17 +23,18 @@ export default {
FilterObject
},
inject: [
'openmct',
'providedObject'
'openmct'
],
data() {
let providedObject = this.openmct.selection.get()[0][0].context.item;
let persistedFilters = {};
if (this.providedObject.configuration && this.providedObject.configuration.filters) {
persistedFilters = this.providedObject.configuration.filters;
if (providedObject.configuration && providedObject.configuration.filters) {
persistedFilters = providedObject.configuration.filters;
}
return {
providedObject,
persistedFilters,
children: {}
}
@ -73,13 +74,14 @@ export default {
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
},
beforeDestroy() {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
this.unobserve();
this.unobserveAllMutation();
}
}
</script>

View File

@ -33,23 +33,20 @@ define([
key: 'filters-inspector',
name: 'Filters Inspector View',
canView: function (selection) {
if (selection.length === 0) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
}
let object = selection[0].context.item;
let object = selection[0][0].context.item;
return object && supportedObjectTypesArray.some(type => object.type === type);
},
view: function (selection) {
let component;
let providedObject = selection[0].context.item;
return {
show: function (element) {
component = new Vue({
provide: {
openmct,
providedObject
openmct
},
components: {
FiltersView: FiltersView.default
@ -59,8 +56,10 @@ define([
});
},
destroy: function () {
component.$destroy();
component = undefined;
if (component) {
component.$destroy();
component = undefined;
}
}
}
},

View File

@ -106,9 +106,6 @@
.c-fl {
@include abs();
display: flex;
flex-direction: column; // TEMP: only needed to support temp-toolbar element
> * + * { margin-top: $interiorMargin; }
.temp-toolbar {
flex: 0 0 auto;
@ -116,7 +113,8 @@
&__container-holder {
display: flex;
flex: 1 1 100%; // Must needs to be 100% to work
flex: 1 1 100%; // Must be 100% to work
overflow: auto;
// Columns by default
flex-direction: row;
@ -292,11 +290,6 @@
margin-bottom: $interiorMargin;
}
&__object-view {
flex: 1 1 auto;
overflow: auto;
}
&__size-indicator {
$size: 35px;
@ -422,6 +415,7 @@ import Container from '../utils/container';
import Frame from '../utils/frame';
import ResizeHandle from './resizeHandle.vue';
import DropHint from './dropHint.vue';
import RemoveAction from '../../remove/RemoveAction.js';
const MIN_CONTAINER_SIZE = 5;
@ -513,7 +507,7 @@ export default {
remove associated domainObjects from composition
*/
container.frames.forEach(f => {
this.composition.remove({identifier: f.domainObjectIdentifier});
this.removeFromComposition(f.domainObjectIdentifier);
});
this.containers.splice(containerIndex, 1);
@ -528,6 +522,7 @@ export default {
}
sizeToFill(this.containers);
this.setSelectionToParent();
this.persist();
},
moveFrame(toContainerIndex, toFrameIndex, frameId, fromContainerIndex) {
@ -561,20 +556,23 @@ export default {
deleteFrame(frameId) {
let container = this.containers
.filter(c => c.frames.some(f => f.id === frameId))[0];
let containerIndex = this.containers.indexOf(container);
let frame = container
.frames
.filter((f => f.id === frameId))[0];
let frameIndex = container.frames.indexOf(frame);
/*
remove associated domainObject from composition
*/
this.composition.remove({identifier: frame.domainObjectIdentifier});
container.frames.splice(frameIndex, 1);
sizeToFill(container.frames);
this.persist(containerIndex);
this.removeFromComposition(frame.domainObjectIdentifier)
.then(() => {
sizeToFill(container.frames)
this.setSelectionToParent();
});
},
removeFromComposition(identifier) {
return this.openmct.objects.get(identifier).then((childDomainObject) => {
this.RemoveAction.removeFromComposition(this.domainObject, childDomainObject);
});
},
setSelectionToParent() {
this.$el.click();
},
allowContainerDrop(event, index) {
if (!event.dataTransfer.types.includes('containerid')) {
@ -665,6 +663,8 @@ export default {
this.composition.on('remove', this.removeChildObject);
this.composition.on('add', this.addFrame);
this.RemoveAction = new RemoveAction(this.openmct);
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
},
beforeDestroy() {

View File

@ -79,12 +79,14 @@ export default {
},
setSelection() {
this.$nextTick(function () {
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = this.domainObject;
childContext.type = 'frame';
childContext.frameId = this.frame.id;
this.unsubscribeSelection = this.openmct.selection.selectable(
this.$refs.frame, childContext, false);
if (this.$refs && this.$refs.objectFrame) {
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = this.domainObject;
childContext.type = 'frame';
childContext.frameId = this.frame.id;
this.unsubscribeSelection = this.openmct.selection.selectable(
this.$refs.frame, childContext, false);
}
});
},
initDrag(event) {

View File

@ -79,10 +79,12 @@ export default {
mounted() {
document.addEventListener('dragstart', this.setDragging);
document.addEventListener('dragend', this.unsetDragging);
document.addEventListener('drop', this.unsetDragging);
},
destroyed() {
document.removeEventListener('dragstart', this.setDragging);
document.removeEventListener('dragend', this.unsetDragging);
document.removeEventListener('drop', this.unsetDragging);
}
}
</script>

View File

@ -27,28 +27,22 @@ function ToolbarProvider(openmct) {
key: "flex-layout",
description: "A toolbar for objects inside a Flexible Layout.",
forSelection: function (selection) {
let context = selection[0].context;
let context = selection[0][0].context;
return (context && context.type &&
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
},
toolbar: function (selection) {
let primary = selection[0],
secondary = selection[1],
tertiary = selection[2],
let selectionPath = selection[0],
primary = selectionPath[0],
secondary = selectionPath[1],
tertiary = selectionPath[2],
deleteFrame,
toggleContainer,
deleteContainer,
addContainer,
toggleFrame,
separator;
separator = {
control: "separator",
domainObject: selection[0].context.item,
key: "separator"
};
toggleFrame;
toggleContainer = {
control: 'toggle-button',
@ -69,6 +63,12 @@ function ToolbarProvider(openmct) {
]
};
function getSeparator() {
return {
control: "separator"
};
}
if (primary.context.type === 'frame') {
let frameId = primary.context.frameId;
let layoutObject = tertiary.context.item;
@ -77,11 +77,11 @@ function ToolbarProvider(openmct) {
.containers;
let container = containers
.filter(c => c.frames.some(f => f.id === frameId))[0];
let frame = container
let containerIndex = containers.indexOf(container);
let frame = container && container
.frames
.filter((f => f.id === frameId))[0];
let containerIndex = containers.indexOf(container);
let frameIndex = container.frames.indexOf(frame);
let frameIndex = container && container.frames.indexOf(frame);
deleteFrame = {
control: "button",
@ -202,9 +202,9 @@ function ToolbarProvider(openmct) {
let toolbar = [
toggleContainer,
addContainer,
toggleFrame ? separator: undefined,
toggleFrame ? getSeparator() : undefined,
toggleFrame,
deleteFrame || deleteContainer ? separator : undefined,
deleteFrame || deleteContainer ? getSeparator() : undefined,
deleteFrame,
deleteContainer
];

View File

@ -14,6 +14,7 @@ export default {
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
if (!this.composition) {
return;
}
@ -34,7 +35,7 @@ export default {
this.items.push({
model: child,
type: type.definition,
isAlias: this.domainObject.identifier.key !== child.location,
isAlias: this.keystring !== child.location,
objectPath: [child].concat(this.openmct.router.path),
objectKeyString: this.openmct.objects.makeKeyString(child.identifier)
});

View File

@ -0,0 +1,53 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default class GoToOriginalAction {
constructor(openmct) {
this.name = 'Go To Original';
this.description = 'Go to the original unlinked instance of this object';
this._openmct = openmct;
}
invoke(objectPath) {
this._openmct.objects.getOriginalPath(objectPath[0].identifier)
.then((originalPath) => {
let url = '#/browse/' + originalPath
.map(function (o) {
return o && this._openmct.objects.makeKeyString(o.identifier);
}.bind(this))
.reverse()
.slice(1)
.join('/');
window.location.href = url;
});
}
appliesTo(objectPath) {
let parentKeystring = objectPath[1] && this._openmct.objects.makeKeyString(objectPath[1].identifier);
if (!parentKeystring) {
return false;
}
return (parentKeystring !== objectPath[0].location);
}
}

View File

@ -19,33 +19,10 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import GoToOriginalAction from './goToOriginalAction';
define(
[],
function () {
/**
* Updates the title of the current window to reflect the name
* of the currently navigated-to domain object.
* @memberof platform/commonUI/browse
* @constructor
*/
function WindowTitler(navigationService, $rootScope, $document) {
// Look up name of the navigated domain object...
function getNavigatedObjectName() {
var navigatedObject = navigationService.getNavigation();
return navigatedObject && navigatedObject.getModel().name;
}
// Set the window title...
function setTitle(name) {
$document[0].title = name;
}
// Watch the former, and invoke the latter
$rootScope.$watch(getNavigatedObjectName, setTitle);
}
return WindowTitler;
}
);
export default function () {
return function (openmct) {
openmct.contextMenu.registerAction(new GoToOriginalAction(openmct));
};
}

View File

@ -25,176 +25,235 @@ define([
], function (
uuid
) {
function isTelemetry(domainObject) {
if (openmct.telemetry.isTelemetryObject(domainObject)
&& domainObject.type !== 'summary-widget'
&& domainObject.type !== 'example.imagery') {
return true;
} else {
return false;
}
}
function migrateDisplayLayout(domainObject, childObjects) {
const DEFAULT_GRID_SIZE = [32, 32];
let migratedObject = Object.assign({}, domainObject);
let panels = migratedObject.configuration.layout.panels;
let items = [];
Object.keys(panels).forEach(key => {
let panel = panels[key];
let domainObject = childObjects[key];
if (isTelemetry(domainObject)) {
items.push({
width: panel.dimensions[0],
height: panel.dimensions[1],
x: panel.position[0],
y: panel.position[1],
useGrid: true,
identifier: domainObject.identifier,
id: uuid(),
type: 'telemetry-view',
displayMode: 'all',
value: openmct.telemetry.getMetadata(domainObject).getDefaultDisplayValue(),
stroke: "transparent",
fill: "",
color: "",
size: "13px"
return function Migrations(openmct) {
function getColumnNameKeyMap(domainObject) {
let composition = openmct.composition.get(domainObject);
if (composition) {
return composition.load().then(composees => {
return composees.reduce((nameKeyMap, composee) => {
let metadata = openmct.telemetry.getMetadata(composee);
if (metadata !== undefined) {
metadata.values().forEach(value => {
nameKeyMap[value.name] = value.key;
});
}
return nameKeyMap;
}, {});
});
} else {
return Promise.resolve([]);
}
}
function isTelemetry(domainObject) {
if (openmct.telemetry.isTelemetryObject(domainObject)
&& domainObject.type !== 'summary-widget'
&& domainObject.type !== 'example.imagery') {
return true;
} else {
return false;
}
}
function migrateDisplayLayout(domainObject, childObjects) {
const DEFAULT_GRID_SIZE = [32, 32];
let migratedObject = Object.assign({}, domainObject);
let panels = migratedObject.configuration.layout.panels;
let items = [];
Object.keys(panels).forEach(key => {
let panel = panels[key];
let domainObject = childObjects[key];
let identifier = undefined;
if (isTelemetry(domainObject)) {
// If object is a telemetry point, convert it to a plot and
// replace the object in migratedObject composition with the plot.
identifier = {
key: uuid(),
namespace: migratedObject.identifier.namespace
};
let plotObject = {
identifier: identifier,
location: domainObject.location,
name: domainObject.name,
type: "telemetry.plot.overlay"
};
let plotType = openmct.types.get('telemetry.plot.overlay');
plotType.definition.initialize(plotObject);
plotObject.composition.push(domainObject.identifier);
openmct.objects.mutate(plotObject, 'persisted', Date.now());
let keyString = openmct.objects.makeKeyString(domainObject.identifier);
let clonedComposition = Object.assign([], migratedObject.composition);
clonedComposition.forEach((identifier, index) => {
if (openmct.objects.makeKeyString(identifier) === keyString) {
migratedObject.composition[index] = plotObject.identifier;
}
});
}
items.push({
width: panel.dimensions[0],
height: panel.dimensions[1],
x: panel.position[0],
y: panel.position[1],
useGrid: true,
identifier: domainObject.identifier,
identifier: identifier || domainObject.identifier,
id: uuid(),
type: 'subobject-view',
hasFrame: panel.hasFrame
});
}
});
});
migratedObject.configuration.items = items;
migratedObject.configuration.layoutGrid = migratedObject.layoutGrid || DEFAULT_GRID_SIZE;
delete migratedObject.layoutGrid;
delete migratedObject.configuration.layout;
return migratedObject;
}
function migrateFixedPositionConfiguration(elements, telemetryObjects) {
const DEFAULT_STROKE = "transparent";
const DEFAULT_SIZE = "13px";
const DEFAULT_COLOR = "";
const DEFAULT_FILL = "";
let items = [];
elements.forEach(element => {
let item = {
x: element.x,
y: element.y,
width: element.width,
height: element.height,
useGrid: element.useGrid,
id: uuid()
};
if (element.type === "fixed.telemetry") {
item.type = "telemetry-view";
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
item.identifier = telemetryObjects[element.id].identifier;
item.displayMode = element.titled ? 'all' : 'value';
item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue();
} else if (element.type === 'fixed.box') {
item.type = "box-view";
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
} else if (element.type === 'fixed.line') {
item.type = "line-view";
item.x2 = element.x2;
item.y2 = element.y2;
item.stroke = element.stroke || DEFAULT_STROKE;
delete item.height;
delete item.width;
} else if (element.type === 'fixed.text') {
item.type = "text-view";
item.text = element.text;
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
} else if (element.type === 'fixed.image') {
item.type = "image-view";
item.url =element.url;
item.stroke = element.stroke || DEFAULT_STROKE;
}
items.push(item);
});
return items;
}
return [
{
check(domainObject) {
return domainObject.type === 'layout' && domainObject.configuration.layout;
},
migrate(domainObject) {
let childObjects = {};
let promises = Object.keys(domainObject.configuration.layout.panels).map(key => {
return openmct.objects.get(key)
.then(object => {
childObjects[key] = object;
});
});
return Promise.all(promises)
.then(function () {
return migrateDisplayLayout(domainObject, childObjects);
});
},
},
{
check(domainObject) {
return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display'];
},
migrate(domainObject) {
const DEFAULT_GRID_SIZE = [64, 16];
let newLayoutObject = {
identifier: domainObject.identifier,
location: domainObject.location,
name: domainObject.name,
type: "layout"
};
let layoutType = openmct.types.get('layout');
layoutType.definition.initialize(newLayoutObject);
newLayoutObject.composition = domainObject.composition;
newLayoutObject.configuration.layoutGrid = domainObject.layoutGrid || DEFAULT_GRID_SIZE;
let elements = domainObject.configuration['fixed-display'].elements;
let telemetryObjects = {};
let promises = elements.map(element => {
if (element.id) {
return openmct.objects.get(element.id)
.then(object => {
telemetryObjects[element.id] = object;
});
}
});
return Promise.all(promises)
.then(function () {
newLayoutObject.configuration.items =
migrateFixedPositionConfiguration(elements, telemetryObjects);
return newLayoutObject;
});
}
migratedObject.configuration.items = items;
migratedObject.configuration.layoutGrid = migratedObject.layoutGrid || DEFAULT_GRID_SIZE;
delete migratedObject.layoutGrid;
delete migratedObject.configuration.layout;
return migratedObject;
}
];
function migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize) {
const DEFAULT_STROKE = "transparent";
const DEFAULT_SIZE = "13px";
const DEFAULT_COLOR = "";
const DEFAULT_FILL = "";
let items = [];
elements.forEach(element => {
let item = {
x: element.x,
y: element.y,
width: element.width,
height: element.height,
id: uuid()
};
if (!element.useGrid) {
item.x = Math.round(item.x / gridSize[0]);
item.y = Math.round(item.y / gridSize[1]);
item.width = Math.round(item.width / gridSize[0]);
item.height = Math.round(item.height / gridSize[1]);
}
if (element.type === "fixed.telemetry") {
item.type = "telemetry-view";
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
item.identifier = telemetryObjects[element.id].identifier;
item.displayMode = element.titled ? 'all' : 'value';
item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue();
} else if (element.type === 'fixed.box') {
item.type = "box-view";
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
} else if (element.type === 'fixed.line') {
item.type = "line-view";
item.x2 = element.x2;
item.y2 = element.y2;
item.stroke = element.stroke || DEFAULT_STROKE;
delete item.height;
delete item.width;
} else if (element.type === 'fixed.text') {
item.type = "text-view";
item.text = element.text;
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
} else if (element.type === 'fixed.image') {
item.type = "image-view";
item.url =element.url;
item.stroke = element.stroke || DEFAULT_STROKE;
}
items.push(item);
});
return items;
}
return [
{
check(domainObject) {
return domainObject.type === 'layout' && domainObject.configuration.layout;
},
migrate(domainObject) {
let childObjects = {};
let promises = Object.keys(domainObject.configuration.layout.panels).map(key => {
return openmct.objects.get(key)
.then(object => {
childObjects[key] = object;
});
});
return Promise.all(promises)
.then(function () {
return migrateDisplayLayout(domainObject, childObjects);
});
}
},
{
check(domainObject) {
return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display'];
},
migrate(domainObject) {
const DEFAULT_GRID_SIZE = [64, 16];
let newLayoutObject = {
identifier: domainObject.identifier,
location: domainObject.location,
name: domainObject.name,
type: "layout"
};
let gridSize = domainObject.layoutGrid || DEFAULT_GRID_SIZE;
let layoutType = openmct.types.get('layout');
layoutType.definition.initialize(newLayoutObject);
newLayoutObject.composition = domainObject.composition;
newLayoutObject.configuration.layoutGrid = gridSize;
let elements = domainObject.configuration['fixed-display'].elements;
let telemetryObjects = {};
let promises = elements.map(element => {
if (element.id) {
return openmct.objects.get(element.id)
.then(object => {
telemetryObjects[element.id] = object;
});
}
});
return Promise.all(promises)
.then(function () {
newLayoutObject.configuration.items =
migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize);
return newLayoutObject;
});
}
},
{
check(domainObject) {
return domainObject.type === 'table' &&
domainObject.configuration.table;
},
migrate(domainObject) {
let currentTableConfiguration = domainObject.configuration.table || {};
let currentColumnConfiguration = currentTableConfiguration.columns || {};
return getColumnNameKeyMap(domainObject).then(nameKeyMap => {
let hiddenColumns = Object.keys(currentColumnConfiguration).filter(columnName => {
return currentColumnConfiguration[columnName] === false;
}).reduce((hiddenColumnsMap, hiddenColumnName) => {
let key = nameKeyMap[hiddenColumnName];
hiddenColumnsMap[key] = true;
return hiddenColumnsMap;
}, {});
domainObject.configuration.hiddenColumns = hiddenColumns;
delete domainObject.configuration.table;
return domainObject;
});
}
}
];
}
});

View File

@ -20,19 +20,21 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import migrations from './Migrations.js'
import Migrations from './Migrations.js'
export default function () {
function needsMigration(domainObject) {
return migrations.some(m => m.check(domainObject));
}
function migrateObject(domainObject) {
return migrations.filter(m => m.check(domainObject))[0]
.migrate(domainObject);
}
return function (openmct) {
let migrations = Migrations(openmct);
function needsMigration(domainObject) {
return migrations.some(m => m.check(domainObject));
}
function migrateObject(domainObject) {
return migrations.filter(m => m.check(domainObject))[0]
.migrate(domainObject);
}
let wrappedFunction = openmct.objects.get;
openmct.objects.get = function migrate(identifier) {
return wrappedFunction.apply(openmct.objects, [identifier])
@ -46,6 +48,6 @@ export default function () {
}
return object;
});
}
}
};
}
}

View File

@ -43,12 +43,16 @@
<div class="l-view-section">
<div class="c-loading--overlay loading"
ng-show="!!currentRequest.pending"></div>
<div class="gl-plot child-frame"
<div class="gl-plot child-frame u-inspectable"
ng-repeat="telemetryObject in telemetryObjects"
ng-class="{
's-status-timeconductor-unsynced': telemetryObject
.getCapability('status')
.get('timeconductor-unsynced')
}"
mct-selectable="{
item: telemetryObject.useCapability('adapter'),
oldItem: telemetryObject
}">
<mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot>
</div>

View File

@ -70,13 +70,15 @@ define([
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.listenTo(config.series, 'add', this.addSeries, this);
this.listenTo(config.series, 'remove', this.resetAllSeries, this);
config.series.forEach(this.addSeries, this);
};
PlotOptionsController.prototype.addSeries = function (series, index) {
this.$scope.plotSeries[index] = series;
series.locateOldObject(this.$scope.domainObject);
this.$timeout(function () {
this.$scope.plotSeries[index] = series;
series.locateOldObject(this.$scope.domainObject);
}.bind(this));
};
PlotOptionsController.prototype.resetAllSeries = function (series, index) {

View File

@ -315,6 +315,7 @@ define([
};
MCTPlotController.prototype.wheelZoom = function (event) {
const ZOOM_AMT = 0.1;
event.preventDefault();
if (!this.positionOverPlot) {
@ -349,24 +350,24 @@ define([
if (event.wheelDelta < 0) {
this.$scope.xAxis.set('displayRange', {
min: xDisplayRange.min + ((xAxisDist * 0.01) * xAxisMinDist),
max: xDisplayRange.max - ((xAxisDist * 0.01) * xAxisMaxDist)
min: xDisplayRange.min + ((xAxisDist * ZOOM_AMT) * xAxisMinDist),
max: xDisplayRange.max - ((xAxisDist * ZOOM_AMT) * xAxisMaxDist)
});
this.$scope.yAxis.set('displayRange', {
min: yDisplayRange.min + ((yAxisDist * 0.01) * yAxisMinDist),
max: yDisplayRange.max - ((yAxisDist * 0.01) * yAxisMaxDist)
min: yDisplayRange.min + ((yAxisDist * ZOOM_AMT) * yAxisMinDist),
max: yDisplayRange.max - ((yAxisDist * ZOOM_AMT) * yAxisMaxDist)
});
} else if (event.wheelDelta >= 0) {
this.$scope.xAxis.set('displayRange', {
min: xDisplayRange.min - ((xAxisDist * 0.01) * xAxisMinDist),
max: xDisplayRange.max + ((xAxisDist * 0.01) * xAxisMaxDist)
min: xDisplayRange.min - ((xAxisDist * ZOOM_AMT) * xAxisMinDist),
max: xDisplayRange.max + ((xAxisDist * ZOOM_AMT) * xAxisMaxDist)
});
this.$scope.yAxis.set('displayRange', {
min: yDisplayRange.min - ((yAxisDist * 0.01) * yAxisMinDist),
max: yDisplayRange.max + ((yAxisDist * 0.01) * yAxisMaxDist)
min: yDisplayRange.min - ((yAxisDist * ZOOM_AMT) * yAxisMinDist),
max: yDisplayRange.max + ((yAxisDist * ZOOM_AMT) * yAxisMaxDist)
});
}

View File

@ -219,6 +219,7 @@ define([
PlotController.prototype.stopLoading = function () {
this.$scope.pending -= 1;
this.$scope.$digest();
};
/**

View File

@ -79,6 +79,15 @@ define([
$scope.$broadcast('plot:tickWidth', _.max(tickWidthMap));
}
}
function compositionReorder(reorderPlan) {
let oldComposition = telemetryObjects.slice();
reorderPlan.forEach((reorder) => {
telemetryObjects[reorder.newIndex] = oldComposition[reorder.oldIndex];
});
}
thisRequest.pending += 1;
openmct.objects.get(domainObject.getId())
.then(function (obj) {
@ -89,10 +98,12 @@ define([
composition = openmct.composition.get(obj);
composition.on('add', addChild);
composition.on('remove', removeChild);
composition.on('reorder', compositionReorder);
composition.load();
unlisten = function () {
composition.off('add', addChild);
composition.off('remove', removeChild);
composition.off('reorder', compositionReorder);
};
});
}

View File

@ -41,7 +41,8 @@ define([
'./tabs/plugin',
'./LADTable/plugin',
'./filters/plugin',
'./objectMigration/plugin'
'./objectMigration/plugin',
'./goToOriginalAction/plugin'
], function (
_,
UTCTimeSystem,
@ -63,7 +64,8 @@ define([
Tabs,
LADTable,
Filters,
ObjectMigration
ObjectMigration,
GoToOriginalAction
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
@ -160,6 +162,7 @@ define([
plugins.LADTable = LADTable;
plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default;
return plugins;
});

View File

@ -85,16 +85,17 @@ export default class RemoveAction {
);
this.openmct.objects.mutate(parent, 'composition', composition);
if (this.inNavigationPath(child) && this.openmct.editor.isEditing()) {
this.openmct.editor.save();
}
}
appliesTo(objectPath) {
let object = objectPath[0];
let objectType = object && this.openmct.types.get(object.type);
let parent = objectPath[1];
let parentType = parent && this.openmct.types.get(parent.type);
return objectType.definition.creatable &&
parentType &&
return parentType &&
parentType.definition.creatable &&
Array.isArray(parent.composition);
}

View File

@ -4,7 +4,7 @@
<span class="t-configuration"> </span>
<span class="t-value-inputs"> </span>
</span>
<span class="flex-elem local-control local-controls-hidden l-condition-action-buttons-wrapper">
<span class="flex-elem c-local-controls--show-on-hover l-condition-action-buttons-wrapper">
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this condition"></a>
<a class="s-icon-button icon-trash t-delete" title="Delete this condition"></a>
</span>

View File

@ -1,5 +1,5 @@
<div class="c-sw-rule">
<div class="c-sw-rule__ui l-compact-form has-local-controls l-widget-rule s-widget-rule">
<div class="c-sw-rule__ui l-compact-form l-widget-rule s-widget-rule has-local-controls">
<div class="c-sw-rule__ui__header widget-rule-header">
<div class="c-sw-rule__grippy-wrapper">
<div class="c-sw-rule__grippy t-grippy local-control local-controls-hidden"></div>
@ -11,7 +11,7 @@
</div>
<div class="flex-elem rule-title">Default Title</div>
<div class="flex-elem rule-description grows">Rule description goes here</div>
<div class="flex-elem local-control local-controls-hidden l-rule-action-buttons-wrapper">
<div class="flex-elem c-local-controls--show-on-hover l-rule-action-buttons-wrapper">
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this rule"></a>
<a class="s-icon-button icon-trash t-delete" title="Delete this rule"></a>
</div>

View File

@ -7,7 +7,7 @@
<span class="equal-to hidden"> equal to </span>
<span class="t-value-inputs"></span>
</span>
<span class="flex-elem local-control local-controls-hidden l-widget-test-data-item-action-buttons-wrapper">
<span class="flex-elem c-local-controls--show-on-hover l-widget-test-data-item-action-buttons-wrapper">
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this test value"></a>
<a class="s-icon-button icon-trash t-delete" title="Delete this test value"></a>
</span>

View File

@ -3,11 +3,9 @@
<div id="widgetIcon" class="c-sw__icon js-sw__icon"></div>
<div id="widgetLabel" class="label widget-label c-sw__label js-sw__label">Default Static Name</div>
</a>
<div class="c-summary-widget__message holder flex-elem t-message-inline c-message message-severity-alert t-message-widget-no-data">
<div class="w-message-contents l-message-body-only">
<div class="message-body">
You must add at least one telemetry object to edit this widget.
</div>
<div class="js-summary-widget__message c-summary-widget__message c-message c-message--simple message-severity-alert">
<div class="c-summary-widget__text">
You must add at least one telemetry object to edit this widget.
</div>
</div>
<div class="c-sw-edit__ui holder l-flex-accordion flex-elem grows widget-edit-holder expanded-widget-test-data expanded-widget-rules">

View File

@ -237,6 +237,7 @@ define ([
});
delete this.compositionObjs[objectId];
this.subscriptions[objectId](); //unsubscribe from telemetry source
delete this.subscriptions[objectId];
this.eventEmitter.emit('remove', identifier);
if (_.isEmpty(this.compositionObjs)) {

View File

@ -43,7 +43,7 @@ define([
return evaluator.requestLatest(options)
.then(function (latestDatum) {
this.pool.release(evaluator);
return [latestDatum];
return latestDatum ? [latestDatum] : [];
}.bind(this));
};

View File

@ -52,7 +52,10 @@ define([
strategy: 'latest',
size: 1
}).then(function (results) {
if (this.destroyed || this.hasUpdated || this.renderTracker !== renderTracker) {
if (this.destroyed ||
this.hasUpdated ||
this.renderTracker !== renderTracker ||
results.length === 0) {
return;
}
this.updateState(results[results.length - 1]);

View File

@ -1,17 +1,18 @@
<template>
<div class="c-tabs-view">
<div class="c-tabs-view__tabs-holder c-compact-button-holder"
<div class="c-tabs-view__tabs-holder c-tabs"
:class="{
'is-dragging': isDragging,
'is-mouse-over': allowDrop
}">
<div class="c-drop-hint"
@drop="onDrop"
ref="dropHint">
@dragenter="dragenter"
@dragleave="dragleave">
</div>
<div class="c-tabs-view__empty-message"
v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div>
<button class="c-tabs-view__tab c-compact-button"
<button class="c-tabs-view__tab c-tab"
v-for="(tab,index) in tabsList"
:key="index"
:class="[
@ -25,8 +26,9 @@
<div class="c-tabs-view__object-holder"
v-for="(tab, index) in tabsList"
:key="index"
:class="{'invisible': !isCurrent(tab)}">
<div class="c-tabs-view__object-name l-browse-bar__object-name--w"
:class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}">
<div v-if="currentTab"
class="c-tabs-view__object-name l-browse-bar__object-name--w"
:class="currentTab.type.definition.cssClass">
<div class="l-browse-bar__object-name">
{{currentTab.domainObject.name}}
@ -53,15 +55,28 @@
}
&__tabs-holder {
@include userSelectNone();
flex: 0 0 auto;
min-height: $h;
}
&__tab {
&:before {
margin-right: $interiorMarginSm;
opacity: 0.7;
}
}
&__object-holder {
flex: 1 1 auto;
display: flex;
flex-direction: column;
&--hidden {
height: 1000px;
width: 1000px;
position: absolute;
left: -9999px;
top: -9999px;
}
}
&__object-name {
@ -72,10 +87,14 @@
}
&__object {
display: flex;
flex-flow: column nowrap;
flex: 1 1 auto;
height: 0; // Chrome 73 oveflow bug fix
}
&__empty-message {
background: rgba($colorBodyFg, 0.1);
color: rgba($colorBodyFg, 0.7);
font-style: italic;
text-align: center;
@ -140,6 +159,13 @@ export default {
this.showTab(this.tabsList[this.tabsList.length - 1]);
}
},
onReorder(reorderPlan) {
let oldTabs = this.tabsList.slice();
reorderPlan.forEach(reorderEvent => {
this.$set(this.tabsList, reorderEvent.newIndex, oldTabs[reorderEvent.oldIndex]);
});
},
onDrop(e) {
this.setCurrentTab = true;
},
@ -166,31 +192,20 @@ export default {
if (this.composition) {
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.onReorder);
this.composition.load();
}
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
let dropHint = this.$refs.dropHint;
if (dropHint) {
dropHint.addEventListener('dragenter', this.dragenter);
dropHint.addEventListener('dragleave', this.dragleave);
}
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder);
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
},
beforeDestroy() {
let dropHint = this.$refs.dropHint;
dropHint.removeEventListener('dragenter', this.dragenter);
dropHint.removeEventListener('dragleave', this.dragleave);
}
}
</script>

View File

@ -31,6 +31,7 @@ define([
openmct.types.addType('tabs', {
name: "Tabs View",
description: 'Add multiple objects of any type to this view, and quickly navigate between them with tabs',
creatable: true,
cssClass: 'icon-tabs-view',
initialize(domainObject) {

View File

@ -37,15 +37,15 @@ define([
key: 'table-configuration',
name: 'Telemetry Table Configuration',
canView: function (selection) {
if (selection.length === 0) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
}
let object = selection[0].context.item;
let object = selection[0][0].context.item;
return object && object.type === 'table';
},
view: function (selection) {
let component;
let domainObject = selection[0].context.item;
let domainObject = selection[0][0].context.item;
let tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
return {
show: function (element) {
@ -62,8 +62,11 @@ define([
});
},
destroy: function () {
component.$destroy();
component = undefined;
if (component) {
component.$destroy();
component = undefined;
}
tableConfiguration = undefined;
}
}

View File

@ -59,6 +59,7 @@ define([
this.filterObserver = undefined;
this.createTableRowCollections();
openmct.time.on('bounds', this.refreshData);
openmct.time.on('timeSystem', this.refreshData);
}
@ -74,13 +75,17 @@ define([
createTableRowCollections() {
this.boundedRows = new BoundedTableRowCollection(this.openmct);
//By default, sort by current time system, ascending.
this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
this.filteredRows.sortBy({
//Fetch any persisted default sort
let sortOptions = this.configuration.getConfiguration().sortOptions;
//If no persisted sort order, default to sorting by time system, ascending.
sortOptions = sortOptions || {
key: this.openmct.time.timeSystem().key,
direction: 'asc'
});
};
this.filteredRows.sortBy(sortOptions);
}
loadComposition() {
@ -132,12 +137,17 @@ define([
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
return this.openmct.telemetry.request(telemetryObject, requestOptions)
.then(telemetryData => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects.includes(telemetryObject)) {
return;
}
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
}).finally(() => {
this.decrementOutstandingRequests();
});
}
@ -188,6 +198,10 @@ define([
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects.includes(telemetryObject)) {
return;
}
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
}, subscribeOptions);
}
@ -210,12 +224,22 @@ define([
delete this.subscriptions[keyString];
}
sortBy(sortOptions) {
this.filteredRows.sortBy(sortOptions);
if (this.openmct.editor.isEditing()) {
let configuration = this.configuration.getConfiguration();
configuration.sortOptions = sortOptions;
this.configuration.updateConfiguration(configuration);
}
}
destroy() {
this.boundedRows.destroy();
this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.on('timeSystem', this.refreshData);
this.openmct.time.off('timeSystem', this.refreshData);
if (this.filterObserver) {
this.filterObserver();
}

View File

@ -32,12 +32,20 @@ define([
Vue
) {
function TelemetryTableViewProvider(openmct) {
function hasTelemetry(domainObject) {
if (!domainObject.hasOwnProperty('telemetry')) {
return false;
}
let metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0;
}
return {
key: 'table',
name: 'Telemetry Table',
cssClass: 'icon-tabular-realtime',
canView(domainObject) {
return domainObject.type === 'table' || domainObject.hasOwnProperty('telemetry');
return domainObject.type === 'table' ||
hasTelemetry(domainObject)
},
canEdit(domainObject) {
return domainObject.type === 'table';

View File

@ -34,39 +34,13 @@
isSortable ? 'is-sortable' : '',
isSortable && sortOptions.key === headerKey ? 'is-sorting' : '',
isSortable && sortOptions.direction].join(' ')">
<div v-if="isEditing" class="c-telemetry-table__resize-hotzone c-telemetry-table__resize-hotzone--right"
<div class="c-telemetry-table__resize-hitarea"
@mousedown="resizeColumnStart"
></div>
<slot></slot>
</div>
</th>
</template>
<style lang="scss">
@import "~styles/sass-base";
@import "~styles/table";
$hotzone-size: 6px;
.c-telemetry-table__headers__content {
width: 100%;
}
.c-table.c-telemetry-table {
.c-telemetry-table__resize-hotzone {
display: block;
position: absolute;
height: 100%;
padding: 0;
margin: 0;
width: $hotzone-size;
min-width: $hotzone-size;
cursor: col-resize;
border: none;
right: 0px;
margin-right: -$tabularTdPadLR - 1 - $hotzone-size / 2;
}
}
</style>
<script>
import _ from 'lodash';
const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex';

View File

@ -34,7 +34,7 @@
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
<table class="c-table__headers c-telemetry-table__headers">
<thead>
<tr class="c-telemetry-table__headers__name">
<tr class="c-telemetry-table__headers__labels">
<table-column-header
v-for="(title, key, headerIndex) in headers"
:key="key"
@ -49,7 +49,8 @@
:columnWidth="columnWidths[key]"
:sortOptions="sortOptions"
:isEditing="isEditing"
>{{title}}</table-column-header>
><span class="c-telemetry-table__headers__label">{{title}}</span>
</table-column-header>
</tr>
<tr class="c-telemetry-table__headers__filter">
<table-column-header
@ -77,7 +78,7 @@
<!-- Content table -->
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}">
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div>
<table class="c-table__body c-telemetry-table__body"
<table class="c-table__body c-telemetry-table__body js-telemetry-table__content"
:style="{ height: totalHeight + 'px'}">
<tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
@ -159,6 +160,32 @@
thead {
display: block;
}
&__labels {
// Top row, has labels
.c-telemetry-table__headers__content {
// Holds __label, sort indicator and resize-hitarea
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
}
}
&__headers__label {
overflow: hidden;
flex: 0 1 auto;
}
&__resize-hitarea {
// In table-column-header.vue
@include abs();
display: none; // Set to display: block in .is-editing section below
left: auto; right: -1 * $tabularTdPadLR;
width: $tableResizeColHitareaD;
cursor: col-resize;
transform: translateX(50%); // Move so this element sits over border between columns
}
/******************************* ELEMENTS */
@ -173,6 +200,7 @@
&__body-w {
// Wraps __body table provides scrolling
flex: 1 1 100%;
height: 0; // Fixes Chrome 73 overflow bug
overflow-x: auto;
overflow-y: scroll;
}
@ -220,7 +248,7 @@
/******************************* EDITING */
.is-editing {
.c-telemetry-table__headers__name {
.c-telemetry-table__headers__labels {
th[draggable],
th[draggable] > * {
cursor: move;
@ -232,6 +260,10 @@
> * { background: $b; }
}
}
.c-telemetry-table__resize-hitarea {
display: block;
}
}
/******************************* LEGACY */
@ -252,7 +284,7 @@ import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
const RESIZE_HOT_ZONE = 10;
const MOVE_TRIGGER_WAIT = 500;
const VERTICAL_SCROLL_WIDTH = 30;
@ -332,42 +364,48 @@ export default {
},
methods: {
updateVisibleRows() {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
let start = 0;
let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
let start = 0;
let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
}
this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end);
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
this.updatingView = false;
});
}
this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end);
},
calculateFirstVisibleRow() {
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
let scrollTop = this.scrollable.scrollTop;
return Math.floor(scrollTop / this.rowHeight);
},
calculateLastVisibleRow() {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight);
let scrollBottom = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.ceil(scrollBottom / this.rowHeight);
},
updateHeaders() {
this.headers = this.table.configuration.getVisibleHeaders();
@ -409,31 +447,25 @@ export default {
direction: 'asc'
}
}
this.table.filteredRows.sortBy(this.sortOptions);
this.table.sortBy(this.sortOptions);
},
scroll() {
if (!this.processingScroll) {
this.processingScroll = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.synchronizeScrollX();
scroll () {
this.updateVisibleRows();
this.synchronizeScrollX();
if (this.shouldSnapToBottom()) {
this.autoScroll = true;
} else {
// If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false;
}
this.processingScroll = false;
});
if (this.shouldSnapToBottom()) {
this.autoScroll = true;
} else {
// If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false;
}
},
shouldSnapToBottom() {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
},
scrollToBottom() {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
},
synchronizeScrollX() {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
@ -445,37 +477,40 @@ export default {
this.filters[columnKey] = '';
this.table.filteredRows.setColumnFilter(columnKey, '');
},
rowsAdded(rows) {
rowsAdded (rows) {
this.setHeight();
let sizingRow;
if (Array.isArray(rows)) {
sizingRow = rows[0];
} else {
sizingRow = rows;
}
if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
this.$nextTick().then(this.calculateColumnWidths);
}
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
if (this.autoScroll) {
this.$nextTick().then(this.scrollToBottom);
}
this.updatingView = false;
});
if (this.autoScroll) {
this.scrollToBottom();
}
this.updateVisibleRows();
},
rowsRemoved(rows) {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.updatingView = false;
});
}
rowsRemoved (rows) {
this.setHeight();
this.updateVisibleRows();
},
/**
* Calculates height based on total number of rows, and sets table height.
*/
setHeight() {
let filteredRowsLength = this.table.filteredRows.getRows().length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
// Set element height directly to avoid having to wait for Vue to update DOM
// which causes subsequent scroll to use an out of date height.
this.contentTable.style.height = this.totalHeight + 'px';
},
exportAsCSV() {
const headerKeys = Object.keys(this.headers);
@ -556,13 +591,22 @@ export default {
let el = this.$el;
let width = el.clientWidth;
let height = el.clientHeight;
let scrollTop = this.scrollable.scrollTop;
this.resizePollHandle = setInterval(() => {
if ((el.clientWidth !== width || el.clientHeight !== height) && this.isAutosizeEnabled) {
this.calculateTableSize();
// On some resize events scrollTop is reset to 0. Possibly due to a transition we're using?
// Need to preserve scroll position in this case.
if (this.autoScroll) {
this.scrollToBottom();
} else {
this.scrollable.scrollTop = scrollTop;
}
width = el.clientWidth;
height = el.clientHeight;
}
scrollTop = this.scrollable.scrollTop;
}, RESIZE_POLL_INTERVAL);
},
@ -571,6 +615,9 @@ export default {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
mounted() {
this.rowsAdded = _.throttle(this.rowsAdded, 200);
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
this.scroll = _.throttle(this.scroll, 100);
this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject);
@ -584,6 +631,7 @@ export default {
//Default sort
this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');

View File

@ -23,73 +23,76 @@
<div class="c-conductor"
:class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']">
<form class="u-contents" ref="conductorForm" @submit.prevent="updateTimeFromConductor">
<button class="c-input--submit" type="submit" ref="submitButton"></button>
<ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon>
<div class="c-conductor__time-bounds">
<button class="c-input--submit" type="submit" ref="submitButton"></button>
<ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
v-if="isFixed">
<!-- Fixed start -->
<div class="c-conductor__start-fixed__label">Start</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
ref="startDate"
v-model="formattedBounds.start"
@change="validateAllBounds(); submitForm()" />
<date-picker
v-if="isFixed && isUTCBased"
:default-date-time="formattedBounds.start"
:formatter="timeFormatter"
@date-selected="startDateSelected"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
v-if="!isFixed">
<!-- RT start -->
<div class="c-direction-indicator icon-minus"></div>
<input class="c-input--hrs-min-sec"
type="text" autocorrect="off"
ref="startOffset"
spellcheck="false"
v-model="offsets.start"
@change="validateAllOffsets(); submitForm()">
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
<!-- Fixed end and RT 'last update' display -->
<div class="c-conductor__end-fixed__label">
{{ isFixed ? 'End' : 'Updated' }}
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
v-if="isFixed">
<!-- Fixed start -->
<div class="c-conductor__start-fixed__label">Start</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
ref="startDate"
v-model="formattedBounds.start"
@change="validateAllBounds(); submitForm()" />
<date-picker
v-if="isFixed && isUTCBased"
:default-date-time="formattedBounds.start"
:formatter="timeFormatter"
@date-selected="startDateSelected"></date-picker>
</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
v-model="formattedBounds.end"
:disabled="!isFixed"
ref="endDate"
@change="validateAllBounds(); submitForm()">
<date-picker
v-if="isFixed && isUTCBased"
class="c-ctrl-wrapper--menus-left"
:default-date-time="formattedBounds.end"
:formatter="timeFormatter"
@date-selected="endDateSelected"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
v-if="!isFixed">
<!-- RT end -->
<div class="c-direction-indicator icon-plus"></div>
<input class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
ref="endOffset"
v-model="offsets.end"
@change="validateAllOffsets(); submitForm()">
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
v-if="!isFixed">
<!-- RT start -->
<div class="c-direction-indicator icon-minus"></div>
<input class="c-input--hrs-min-sec"
type="text" autocorrect="off"
ref="startOffset"
spellcheck="false"
v-model="offsets.start"
@change="validateAllOffsets(); submitForm()">
</div>
<conductor-axis
class="c-conductor__ticks"
:bounds="rawBounds"
@panAxis="setViewFromBounds"></conductor-axis>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
<!-- Fixed end and RT 'last update' display -->
<div class="c-conductor__end-fixed__label">
{{ isFixed ? 'End' : 'Updated' }}
</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
v-model="formattedBounds.end"
:disabled="!isFixed"
ref="endDate"
@change="validateAllBounds(); submitForm()">
<date-picker
v-if="isFixed && isUTCBased"
class="c-ctrl-wrapper--menus-left"
:default-date-time="formattedBounds.end"
:formatter="timeFormatter"
@date-selected="endDateSelected"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
v-if="!isFixed">
<!-- RT end -->
<div class="c-direction-indicator icon-plus"></div>
<input class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
ref="endOffset"
v-model="offsets.end"
@change="validateAllOffsets(); submitForm()">
</div>
<conductor-axis
class="c-conductor__ticks"
:bounds="rawBounds"
@panAxis="setViewFromBounds"></conductor-axis>
</div>
<div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider -->
<ConductorMode class="c-conductor__mode-select"></ConductorMode>
@ -113,17 +116,17 @@
/*********************************************** CONDUCTOR LAYOUT */
.c-conductor {
display: grid;
grid-column-gap: $interiorMargin;
grid-row-gap: $interiorMargin;
align-items: center;
&__time-bounds {
display: grid;
grid-column-gap: $interiorMargin;
grid-row-gap: $interiorMargin;
align-items: center;
// Default: fixed mode, desktop
grid-template-rows: 1fr 1fr;
grid-template-columns: 20px auto 1fr auto;
grid-template-areas:
"tc-mode-icon tc-start tc-ticks tc-end"
"tc-controls tc-controls tc-controls tc-controls";
// Default: fixed mode, desktop
grid-template-rows: 1fr;
grid-template-columns: 20px auto 1fr auto;
grid-template-areas: "tc-mode-icon tc-start tc-ticks tc-end";
}
&__mode-icon {
grid-area: tc-mode-icon;
@ -163,10 +166,10 @@
}
&.is-realtime-mode {
grid-template-columns: 20px auto 1fr auto auto;
grid-template-areas:
"tc-mode-icon tc-start tc-ticks tc-updated tc-end"
"tc-controls tc-controls tc-controls tc-controls tc-controls";
.c-conductor__time-bounds {
grid-template-columns: 20px auto 1fr auto auto;
grid-template-areas: "tc-mode-icon tc-start tc-ticks tc-updated tc-end";
}
.c-conductor__end-fixed {
grid-area: tc-updated;
@ -174,9 +177,15 @@
}
body.phone.portrait & {
grid-row-gap: $interiorMargin;
grid-template-rows: auto auto auto;
grid-template-columns: 20px auto auto;
.c-conductor__time-bounds {
grid-row-gap: $interiorMargin;
grid-template-rows: auto auto;
grid-template-columns: 20px auto auto;
}
.c-conductor__controls {
padding-left: 25px; // Line up visually with other controls
}
&__mode-icon {
grid-row: 1;
@ -200,17 +209,19 @@
justify-content: flex-start;
}
grid-template-areas:
.c-conductor__time-bounds {
grid-template-areas:
"tc-mode-icon tc-start tc-start"
"tc-mode-icon tc-end tc-end"
"tc-mode-icon tc-controls tc-controls";
}
}
}
&.is-realtime-mode {
grid-template-areas:
.c-conductor__time-bounds {
grid-template-areas:
"tc-mode-icon tc-start tc-updated"
"tc-mode-icon tc-end tc-end"
"tc-mode-icon tc-controls tc-controls";
"tc-mode-icon tc-end tc-end";
}
.c-conductor__end-fixed {
justify-content: flex-end;

View File

@ -20,7 +20,15 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['EventEmitter'], function (EventEmitter) {
define(
[
'EventEmitter',
'lodash'
],
function (
EventEmitter,
_
) {
/**
* Manages selection state for Open MCT
@ -47,21 +55,87 @@ define(['EventEmitter'], function (EventEmitter) {
* Selects the selectable object and emits the 'change' event.
*
* @param {object} selectable an object with element and context properties
* @param {Boolean} isMultiSelectEvent flag indication shift key is pressed or not
* @private
*/
Selection.prototype.select = function (selectable) {
Selection.prototype.select = function (selectable, isMultiSelectEvent) {
if (!Array.isArray(selectable)) {
selectable = [selectable];
}
if (this.selected[0] && this.selected[0].element) {
this.selected[0].element.removeAttribute('s-selected');
let multiSelect = isMultiSelectEvent &&
this.parentSupportsMultiSelect(selectable) &&
this.isPeer(selectable) &&
!this.selectionContainsParent(selectable);
if (multiSelect) {
this.handleMultiSelect(selectable);
} else {
this.setSelectionStyles(selectable);
this.selected = [selectable];
}
if (this.selected[1] && this.selected[1].element) {
this.selected[1].element.removeAttribute('s-selected-parent');
this.emit('change', this.selected);
};
/**
* @private
*/
Selection.prototype.handleMultiSelect = function (selectable) {
if (this.elementSelected(selectable)) {
this.remove(selectable);
} else {
this.addSelectionAttributes(selectable);
this.selected.push(selectable);
}
};
/**
* @private
*/
Selection.prototype.elementSelected = function (selectable) {
return this.selected.some(selectionPath => _.isEqual(selectionPath, selectable));
};
/**
* @private
*/
Selection.prototype.remove = function (selectable) {
this.selected = this.selected.filter(selectionPath => !_.isEqual(selectionPath, selectable));
if (this.selected.length === 0) {
this.removeSelectionAttributes(selectable);
selectable[1].element.click(); // Select the parent if there is no selection.
} else {
this.removeSelectionAttributes(selectable, true);
}
};
/**
* @private
*/
Selection.prototype.setSelectionStyles = function (selectable) {
this.selected.map(selectionPath => {
this.removeSelectionAttributes(selectionPath);
});
this.addSelectionAttributes(selectable);
};
Selection.prototype.removeSelectionAttributes = function (selectionPath, keepParentStyle) {
if (selectionPath[0] && selectionPath[0].element) {
selectionPath[0].element.removeAttribute('s-selected');
}
if (selectionPath[1] && selectionPath[1].element && !keepParentStyle) {
selectionPath[1].element.removeAttribute('s-selected-parent');
}
};
/*
* Adds selection attributes to the selected element and its parent.
* @private
*/
Selection.prototype.addSelectionAttributes = function (selectable) {
if (selectable[0] && selectable[0].element) {
selectable[0].element.setAttribute('s-selected', "");
}
@ -69,16 +143,36 @@ define(['EventEmitter'], function (EventEmitter) {
if (selectable[1] && selectable[1].element) {
selectable[1].element.setAttribute('s-selected-parent', "");
}
};
this.selected = selectable;
this.emit('change', this.selected);
/**
* @private
*/
Selection.prototype.parentSupportsMultiSelect = function (selectable) {
return selectable[1] && selectable[1].context.supportsMultiSelect;
};
/**
* @private
*/
Selection.prototype.selectionContainsParent = function (selectable) {
return this.selected.some(selectionPath => _.isEqual(selectionPath[0], selectable[1]));
};
/**
* @private
*/
Selection.prototype.isPeer = function (selectable) {
return this.selected.some(selectionPath => _.isEqual(selectionPath[1], selectable[1]));
};
/**
* @private
*/
Selection.prototype.capture = function (selectable) {
if (!this.capturing) {
let capturingContainsSelectable = this.capturing && this.capturing.includes(selectable);
if (!this.capturing || capturingContainsSelectable) {
this.capturing = [];
}
@ -88,13 +182,14 @@ define(['EventEmitter'], function (EventEmitter) {
/**
* @private
*/
Selection.prototype.selectCapture = function (selectable) {
Selection.prototype.selectCapture = function (selectable, event) {
if (!this.capturing) {
return;
}
this.select(this.capturing.reverse());
let reversedCapturing = this.capturing.reverse();
delete this.capturing;
this.select(reversedCapturing, event.shiftKey);
};
/**
@ -112,7 +207,7 @@ define(['EventEmitter'], function (EventEmitter) {
* @public
*/
Selection.prototype.selectable = function (element, context, select) {
var selectable = {
let selectable = {
context: context,
element: element
};

View File

@ -54,10 +54,11 @@
&:after {
// App logo
top: 0;
right: 15%;
bottom: 0;
left: 15%;
$d: 25%;
top: $d;
right: $d;
bottom: $d;
left: $d;
}
}
@ -75,12 +76,20 @@
&__image,
&__text {
height: 50%;
flex: 1 1 0;
flex: 1 1 auto;
}
&__image {
height: 35%;
}
&__text {
height: 65%;
overflow: auto;
> * + * {
border-top: 1px solid $colorInteriorBorder;
margin-top: 1em;
}
}
&--licenses {
@ -107,7 +116,7 @@
h1, h2, h3 {
font-weight: normal;
margin-bottom: 1em;
margin-bottom: .25em;
}
h1 {

View File

@ -79,10 +79,13 @@ $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%);
$colorSelectedFg: pullForward($colorBodyFg, 20%);
// Layout
$shellMainPad: 4px 0;
@ -98,6 +101,20 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de
$colorStatusError: #da0004;
$colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%);
$colorStatusBtnBg: #666; // Where is this used?
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;
$colorWarningHiFg: #FF9594;
$colorWarningLo: #ff9900;
$colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442;
$colorDiagnosticFg: #39461A;
$colorCommand: #3693bd;
$colorCommandFg: #fff;
$colorInfo: #2294a2;
$colorInfoFg: #fff;
$colorOk: #33cc33;
$colorOkFg: #fff;
// States
$colorPausedBg: #ff9900;
@ -126,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
/************************************************** EDITING */
$editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
@ -142,8 +161,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px;
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@ -151,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
// Icons
$colorIconAlias: #4af6f3;
@ -173,6 +193,10 @@ $colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
$colorBtnSelectedFg: $colorSelectedFg;
$colorClickIconButton: $colorKey;
$colorClickIconButtonBgHov: rgba($colorKey, 0.6);
$colorClickIconButtonFgHov: $colorKeyHov;
@ -203,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed
$mixedSettingBgSize: 5px;
// Forms
$colorCheck: $colorKey;
@ -265,26 +291,9 @@ $colorLimitRedBg: #940000;
$colorLimitRedFg: #ffa489;
$colorLimitRedIc: #ff4222;
// Status
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;
$colorWarningHiFg: #FF9594;
$colorWarningLo: #ff9900;
$colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442;
$colorDiagnosticFg: #39461A;
$colorCommand: #3693bd;
$colorCommandFg: #fff;
$colorInfo: #2294a2;
$colorInfoFg: #fff;
$colorOk: #33cc33;
$colorOkFg: #fff;
// Bubble colors
$colorInfoBubbleBg: #dddddd;
$colorInfoBubbleFg: #666;
$colorInfoBubbleFg: #666;
$colorThumbsBubbleFg: pullForward($colorBodyFg, 10%);
$colorThumbsBubbleBg: pullForward($colorBodyBg, 10%);
@ -306,6 +315,9 @@ $colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
$colorTabGroupHeaderFg: pushBack($colorTabHeaderFg, 10%);
$colorSummaryBg: #2c2c2c;
$colorSummaryFg: rgba($colorBodyFg, 0.7);
$colorSummaryFgEm: $colorBodyFg;
// Plot
$colorPlotBg: rgba(black, 0.05);
@ -324,7 +336,7 @@ $colorItemTreeHoverFg: pullForward($colorBodyFg, 20%);
$colorItemTreeIcon: $colorKey; // Used
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: pushBack($colorKey, 15%);
$colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
@ -378,8 +390,10 @@ $colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
$transIn: all 50ms ease-in-out;
$transOut: all 250ms ease-in-out;
$transInTime: 50ms;
$transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@ -87,6 +87,9 @@ $uiColor: #00b2ff; // 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%);
$colorSelectedFg: pullForward($colorBodyFg, 20%);
// Layout
$shellMainPad: 4px 0;
@ -102,6 +105,20 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de
$colorStatusError: #da0004;
$colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%);
$colorStatusBtnBg: #666; // Where is this used?
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;
$colorWarningHiFg: #FF9594;
$colorWarningLo: #ff9900;
$colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442;
$colorDiagnosticFg: #39461A;
$colorCommand: #3693bd;
$colorCommandFg: #fff;
$colorInfo: #2294a2;
$colorInfoFg: #fff;
$colorOk: #33cc33;
$colorOkFg: #fff;
// States
$colorPausedBg: #ff9900;
@ -130,6 +147,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
/************************************************** EDITING */
$editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
@ -146,8 +165,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px;
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@ -155,6 +174,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
// Icons
$colorIconAlias: #4af6f3;
@ -177,6 +197,10 @@ $colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
$colorBtnSelectedFg: $colorSelectedFg;
$colorClickIconButton: $colorKey;
$colorClickIconButtonBgHov: rgba($colorKey, 0.6);
$colorClickIconButtonFgHov: $colorKeyHov;
@ -207,6 +231,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed
$mixedSettingBgSize: 5px;
// Forms
$colorCheck: $colorKey;
@ -269,26 +295,9 @@ $colorLimitRedBg: #940000;
$colorLimitRedFg: #ffa489;
$colorLimitRedIc: #ff4222;
// Status
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;
$colorWarningHiFg: #FF9594;
$colorWarningLo: #ff9900;
$colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442;
$colorDiagnosticFg: #39461A;
$colorCommand: #3693bd;
$colorCommandFg: #fff;
$colorInfo: #2294a2;
$colorInfoFg: #fff;
$colorOk: #33cc33;
$colorOkFg: #fff;
// Bubble colors
$colorInfoBubbleBg: #dddddd;
$colorInfoBubbleFg: #666;
$colorInfoBubbleFg: #666;
$colorThumbsBubbleFg: pullForward($colorBodyFg, 10%);
$colorThumbsBubbleBg: pullForward($colorBodyBg, 10%);
@ -310,6 +319,9 @@ $colorTabHeaderFg: $colorBodyFg;
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
$colorTabGroupHeaderFg: pushBack($colorTabHeaderFg, 10%);
$colorSummaryBg: #2c2c2c;
$colorSummaryFg: rgba($colorBodyFg, 0.7);
$colorSummaryFgEm: $colorBodyFg;
// Plot
$colorPlotBg: rgba(black, 0.05);
@ -328,7 +340,7 @@ $colorItemTreeHoverFg: pullForward($colorBodyFg, 20%);
$colorItemTreeIcon: $colorKey; // Used
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: pushBack($colorKey, 15%);
$colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
@ -375,15 +387,17 @@ $colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
// About Screen
$colorAboutLink: $colorKeySubtle;
$colorAboutLink: #9bb5ff;
// Loading
$colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
$transIn: all 50ms ease-in-out;
$transOut: all 250ms ease-in-out;
$transInTime: 50ms;
$transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@ -83,6 +83,9 @@ $uiColor: #289fec; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #999;
$colorAHov: $colorKey;
$filterHov: brightness(1.3); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 40%);
$colorSelectedFg: pullForward($colorBodyFg, 10%);
// Layout
$shellMainPad: 4px 0;
@ -98,6 +101,20 @@ $colorStatusAlertFilter: invert(89%) sepia(26%) saturate(5035%) hue-rotate(316de
$colorStatusError: #da0004;
$colorStatusErrorFilter: invert(8%) sepia(96%) saturate(4511%) hue-rotate(352deg) brightness(136%) contrast(114%);
$colorStatusBtnBg: #666; // Where is this used?
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;
$colorWarningHiFg: #FF9594;
$colorWarningLo: #ff9900;
$colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442;
$colorDiagnosticFg: #39461A;
$colorCommand: #3693bd;
$colorCommandFg: #fff;
$colorInfo: #2294a2;
$colorInfoFg: #fff;
$colorOk: #33cc33;
$colorOkFg: #fff;
// States
$colorPausedBg: #ff9900;
@ -126,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
/************************************************** EDITING */
$editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #cae1ff; // Base color, toolbar bg
$editUIBaseColorHov: pushBack($editUIBaseColor, 20%);
@ -142,7 +161,7 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #333; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px;
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 5px 2px;
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
@ -151,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
// Icons
$colorIconAlias: #4af6f3;
@ -173,6 +193,10 @@ $colorBtnMajorFgHov: pushBack($colorBtnMajorFg, 10%);
$colorBtnCautionBg: #f16f6f;
$colorBtnCautionBgHov: #f1504e;
$colorBtnCautionFg: $colorBtnFg;
$colorBtnActiveBg: $colorOk;
$colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorBtnMajorBg;
$colorBtnSelectedFg: $colorBtnMajorFg;
$colorClickIconButton: $colorKey;
$colorClickIconButtonBgHov: rgba($colorKey, 0.2);
$colorClickIconButtonFgHov: $colorKeyHov;
@ -185,7 +209,7 @@ $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7);
$btnStdH: 24px;
$colorCursorGuide: rgba(black, 0.6);
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
$colorLocalControlOvrBg: rgba($colorBodyFg, 0.8);
// Menus
$colorMenuBg: pushBack($colorBodyBg, 10%);
@ -203,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.9)); // Used in .c-click-icon--mixed
$mixedSettingBgSize: 10px;
// Forms
$colorCheck: $colorKey;
@ -221,8 +247,8 @@ $colorInputPlaceholder: pushBack($colorBodyFg, 20%);
$colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%);
$colorFieldHint: pullForward($colorBodyFg, 40%);
$shdwInput: inset rgba(black, 0.7) 0 0 1px;
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px;
$shdwInput: inset rgba(black, 0.5) 0 0 2px;
$shdwInputHov: inset rgba(black, 0.8) 0 0 2px;
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
$formTBPad: $interiorMargin;
$formLRPad: $interiorMargin;
@ -265,22 +291,6 @@ $colorLimitRedBg: #ff0000;
$colorLimitRedFg: #fff;
$colorLimitRedIc: #ffa99a;
// Status
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;
$colorWarningHiFg: #FF9594;
$colorWarningLo: #ff9900;
$colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442;
$colorDiagnosticFg: #39461A;
$colorCommand: #3693bd;
$colorCommandFg: #fff;
$colorInfo: #2294a2;
$colorInfoFg: #fff;
$colorOk: #33cc33;
$colorOkFg: #fff;
// Bubble colors
$colorInfoBubbleBg: $colorMenuBg;
$colorInfoBubbleFg: #666;
@ -305,6 +315,9 @@ $colorTabHeaderFg: pullForward($colorBodyFg, 20%);
$colorTabHeaderBorder: $colorBodyBg;
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
$colorTabGroupHeaderFg: pullForward($colorTabGroupHeaderBg, 40%);
$colorSummaryBg: #999;
$colorSummaryFg: rgba($colorBodyBg, 0.7);
$colorSummaryFgEm: white;
// Plot
$colorPlotBg: rgba(black, 0.05);
@ -323,8 +336,8 @@ $colorItemTreeHoverFg: pullForward($colorBodyFg, 10%);
$colorItemTreeIcon: $colorKey; // Used
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
$colorItemTreeFg: $colorBodyFg;
$colorItemTreeSelectedBg: pushBack($colorKey, 15%);
$colorItemTreeSelectedFg: $colorBodyBg;
$colorItemTreeSelectedBg: $colorSelectedBg;
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
$colorItemTreeEditingFg: $editUIColor;
@ -347,6 +360,7 @@ $scrollbarThumbColorMenuHov: darken($scrollbarThumbColorMenu, 2%);
// Splitter
$splitterHandleD: 2px;
$splitterD: $splitterHandleD;
$splitterHandleHitMargin: 4px;
$colorSplitterBaseBg: $colorBodyBg;
$colorSplitterBg: pullForward($colorSplitterBaseBg, 20%);
@ -376,8 +390,10 @@ $colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
$transIn: all 50ms ease-in-out;
$transOut: all 250ms ease-in-out;
$transInTime: 50ms;
$transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@ -28,6 +28,7 @@ $dirImgs: 'images/';
$controlFadeMs: 100ms;
$browseToEditAnimMs: 400ms;
$editBorderPulseMs: 500ms;
$moveBarOutDelay: 500ms;
/************************** SPATIAL */
$interiorMarginSm: 3px;
@ -80,10 +81,12 @@ $formLabelW: 30%;
$waitSpinnerD: 32px;
$waitSpinnerTreeD: 20px;
$waitSpinnerBorderW: 5px;
$waitSpinnerTreeBorderW: 4px;
$waitSpinnerTreeBorderW: 3px;
/*************** Messages */
$messageIconD: 80px;
$messageListIconD: 32px;
/*************** Tables */
$tableResizeColHitareaD: 6px;
/************************** MOBILE */
$mobileMenuIconD: 24px; // Used

View File

@ -48,10 +48,16 @@ button {
height: $d; width: $d;
}
}
}
.c-compact-button {
@include cCompactButtons($bg: $colorBtnBg, $fg: $colorBtnFg, $bgHov: $colorBtnBgHov);
&.is-active {
background: $colorBtnActiveBg;
color: $colorBtnActiveFg;
}
&.is-selected {
background: $colorBtnSelectedBg;
color: $colorBtnSelectedFg;
}
}
/********* Icon Buttons */
@ -79,6 +85,15 @@ button {
margin-left: $interiorMargin;
}
$c1: nth($mixedSettingBg, 1);
$c2: nth($mixedSettingBg, 2);
$mixedBgD: $mixedSettingBgSize $mixedSettingBgSize;
&--mixed {
// E.g. click-icons in toolbar that apply to multiple selected items with different settings
@include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
}
&--swatched {
// Color control, show swatch element
display: flex;
@ -87,9 +102,9 @@ button {
justify-content: center;
> [class*='swatch'] {
box-shadow: inset rgba(black, 0.2) 0 0 1px;
box-shadow: inset rgba($editUIBaseColorFg, 0.2) 0 0 0 1px;
flex: 0 0 auto;
height: 4px;
height: 5px;
width: 100%;
margin-top: 1px;
}
@ -99,6 +114,13 @@ button {
flex: 1 1 auto;
font-size: 1.1em;
}
&--mixed {
// Styling for swatched buttons when settings are mixed
> [class*='swatch'] {
@include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
}
}
}
}
@ -142,8 +164,6 @@ button {
display: block;
font-family: symbolsfont;
font-size: 1rem * $s;
transform-origin: center;
transition: transform 100ms ease-in-out;
}
}
@ -270,6 +290,63 @@ select {
}
}
/******************************************************** TABS */
.c-tabs {
// Single horizontal strip of tabs, with a bottom divider line
@include userSelectNone();
display: flex;
flex: 0 0 auto;
flex-wrap: wrap;
position: relative; // Required in case this is applied to a <ul>
&:before {
// Separator line at bottom of tabs
content: '';
display: block;
height: 1px;
width: 100%;
background: $colorBtnReverseBg;
position: absolute;
bottom: 0px;
z-index: 1;
}
}
.c-tab {
// Used in Tab View, generic tabs
background: $colorBtnBg;
color: $colorBtnFg;
cursor: pointer;
display: flex;
align-items: center;
flex: 1 1 auto;
margin: 1px 1px 0 0;
padding: $interiorMargin $interiorMarginLg;
white-space: nowrap;
--notchSize: 7px;
clip-path:
polygon(
0% 0%,
calc(100% - var(--notchSize)) 0%,
100% var(--notchSize),
100% calc(100% - var(--notchSize)),
100% 100%,
0% 100%
);
@include hover() {
background: $colorBtnBgHov;
}
&.is-current {
background: $colorBtnReverseBg;
color: $colorBtnReverseFg;
pointer-events: none;
}
}
/******************************************************** HYPERLINKS AND HYPERLINK BUTTONS */
.c-hyperlink {
&--link {
@ -316,6 +393,11 @@ select {
color: $colorMenuIc;
font-size: 1em;
margin-right: $interiorMargin;
min-width: 1em;
}
&:not([class]):before {
content: ''; // Add this element so that menu items without an icon still indent properly
}
}
}
@ -508,6 +590,10 @@ select {
margin-left: 2px;
}
&__button {
}
&__separator {
@include cToolbarSeparator();
}
@ -658,7 +744,7 @@ input[type="range"] {
}
}
/***************************************************** LOCAL CONTROLS */
/******************************************************** LOCAL CONTROLS */
.h-local-controls {
// Holder for local controls
&--horz {

View File

@ -101,7 +101,6 @@
min-width: 120px;
order: 1;
position: relative;
white-space: nowrap;
width: $formLabelW;
}
@ -121,26 +120,35 @@
margin-right: 5px;
}
}
.select {
.l-input-lg { // LEGACY FORM SUPPORT
input[type=text],
input[type=search],
input[type=number] {
width: 100%;
}
}
select {
margin-right: $interiorMargin;
}
}
.hint, .field-hints { color: $colorFieldHint; }
}
}
.selector-list {
// Used in create overlay to display tree view
@include nice-input();
padding: $interiorMargin;
position: relative;
min-height: 150px;
height: 100%;
>.wrapper {
$p: $interiorMargin;
box-sizing: border-box;
overflow: auto;
}
}
.selector-list {
// Displays tree view in dialogs
@include nice-input();
padding: $interiorMargin;
position: relative;
min-height: 0; // Chrome 73 overflow bug fix
height: 100%;
>.wrapper {
$p: $interiorMargin;
box-sizing: border-box;
overflow: auto;
}
}

View File

@ -19,8 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/******************************* RESETS */
/******************************************************** RESETS */
*,
:before,
:after {
@ -31,7 +30,7 @@ div {
position: relative;
}
/******************************* UTILITIES */
/******************************************************** UTILITIES */
.u-contents {
display: contents;
}
@ -45,7 +44,7 @@ div {
}
}
/******************************* BROWSER ELEMENTS */
/******************************************************** BROWSER ELEMENTS */
body.desktop {
::-webkit-scrollbar {
box-sizing: border-box;
@ -75,7 +74,7 @@ body.desktop {
}
}
/************************** HTML ENTITIES */
/******************************************************** HTML ENTITIES */
a {
color: $colorA;
cursor: pointer;
@ -167,7 +166,6 @@ body.desktop .has-local-controls {
/******************************************************** SELECTION AND EDITING */
// Provides supporting styles for Display Layouts and augmented legacy Fixed Position view
.c-grid,
.c-grid__x,
.c-grid__y {
@ -204,169 +202,88 @@ body.desktop .has-local-controls {
}
}
/************************** LEGACY */
mct-container {
/******************************************************** STATES */
@mixin spinner($b: 5px, $c: $colorKey) {
animation-name: rotation-centered;
animation-duration: 0.5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
border-radius: 100%;
box-sizing: border-box;
border-color: rgba($c, 0.25);
border-top-color: rgba($c, 1.0);
border-style: solid;
border-width: $b;
display: block;
}
.abs {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: auto;
width: auto;
left: 50%; top: 50%;
transform-origin: center;
transform: translate(-50%, -50%);
}
.code {
font-family: "Lucida Console", monospace;
font-size: 0.7em;
line-height: 150%;
white-space: pre;
.wait-spinner {
@include spinner($waitSpinnerBorderW, $colorKey);
pointer-events: none;
z-index: 2;
&.inline {
display: inline-block !important;
margin-right: $interiorMargin;
position: relative !important;
vertical-align: middle;
}
}
.codehilite {
@extend .code;
background-color: rgba($colorBodyFg, 0.1);
padding: 1em;
.loading {
// Can be applied to any block element with height and width
pointer-events: none;
&:before,
&:after {
content: '';
}
&:before {
@include spinner($waitSpinnerBorderW, $colorLoadingFg);
height: $waitSpinnerD; width: $waitSpinnerD;
z-index: 10;
}
&:after {
@include abs();
background: $colorLoadingBg;
display: block;
z-index: 9;
}
&.c-tree__item {
$d: $waitSpinnerTreeD;
$spinnerL: 19px + $d/2;
display: flex;
align-items: center;
padding-left: $spinnerL + $d/2 + $interiorMargin;
background: $colorLoadingBg;
min-height: 5px + $d;
.c-tree__item__label {
font-style: italic;
opacity: 0.6;
}
&:before {
height: $d;
width: $d;
border-width: 4px;
left: $spinnerL;
}
&:after {
display: none;
}
}
&.c-loading--overlay {
@include abs();
}
}
*[disabled],
.disabled,
a.disabled {
.disabled {
opacity: $controlDisabledOpacity;
pointer-events: none !important;
cursor: default !important;
}
.s-status-missing {
// Labels. Expects .s-status-missing to be applied to mct-representation that contains
.t-object-label .t-item-icon:before {
content: $glyph-icon-object-unknown;
}
// Item, grid item. Expects .s-status-missing to be applied to mct-representation that contains .item.grid-item
.item .t-item-icon-glyph:before {
content: $glyph-icon-object-unknown;
}
// Object header. Expects .s-status-missing to be applied to mct-representation.object-header
&.object-header {
.type-icon:before {
content: $glyph-icon-object-unknown;
}
}
// Tree item. Expects .s-status-missing to be applied to .tree-item,
// and mct-representation.search-item
&.tree-item,
&.search-item {
> .rep-object-label .t-item-icon:before {
content: $glyph-icon-object-unknown;
}
}
}
.align-right {
text-align: right;
}
.centered {
text-align: center;
}
.no-selection {
// aka selection = "None". Used in palettes and their menu buttons.
$c: red;
$s: 48%;
$e: 52%;
background-image: linear-gradient(-45deg,
transparent $s - 5%,
$c $s,
$c $e,
transparent $e + 5%
);
box-shadow:inset rgba(black, 0.3) 0 0 0 1px;
background-repeat: no-repeat;
background-size: contain;
}
.scrolling,
.scroll {
overflow: auto;
}
.vscroll {
overflow-x: hidden;
overflow-y: auto;
&.scroll-pad {
padding-right: $interiorMargin;
}
}
.vscroll--persist {
overflow-x: hidden;
overflow-y: scroll;
}
.slidable {
cursor: move; // Fallback
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
&.horz {
cursor: col-resize;
}
&.vert {
cursor: row-resize;
}
}
.no-margin {
margin: 0;
}
.ds {
box-shadow: rgba(#000, 0.7) 0 4px 10px 2px;
}
.capitalize {
text-transform: capitalize;
}
.hide,
.hidden,
.t-main-view .hide-in-t-main-view {
display: none !important;
}
.hide-nice {
opacity: 0;
pointer-events: none;
}
.invisible {
display: block;
visibility: hidden;
height: 0;
padding: 0;
border: 0;
margin: 0 !important;
transform: scale(0);
pointer-events: none;
position: absolute;
}
.sep {
color: rgba(#fff, 0.2);
}
.comma-list span {
&:not(:first-child) {
&:before {
content: ', ';
}
}
}

View File

@ -51,6 +51,22 @@ mct-plot {
}
}
.is-editing {
.gl-plot.child-frame {
&:hover {
background: rgba($editUIColorBg, 0.1);
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
}
&[s-selected] {
border: 1px solid $editUIColorFg !important;
color: $editUIColorFg !important;
box-shadow: $editFrameSelectedShdw;
z-index: 2;
}
}
}
.gl-plot {
color: $colorPlotFg;
display: flex;
@ -548,3 +564,10 @@ mct-plot {
top: 0; bottom: 0;
}
}
.s-status-timeconductor-unsynced {
.t-object-alert.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
}

View File

@ -461,7 +461,7 @@ body.mobile.phone {
width: $ruleLabelW;
}
.t-message-widget-no-data {
.js-summary-widget__message {
display: none;
}
@ -482,6 +482,8 @@ body.mobile.phone {
.widget-edit-holder {
display: flex; // Overrides `display: none` during Browse mode
flex: 1 1 auto;
.flex-accordion-holder {
// Needed because otherwise accordion elements "creep" when contents expand and contract
display: block !important;
@ -497,18 +499,12 @@ body.mobile.phone {
// Test data is expanded and rules are collapsed
// Make text data take up all the vertical space
.flex-accordion-holder { display: flex; }
.widget-test-data {
flex-grow: 999999;
}
.w-widget-test-data-items {
max-height: inherit;
}
}
}
&.expanded-widget-rules {
.widget-rules-wrapper {
min-height: 50px;
height: auto;
height: 100%; // Fix for Chrome 73 scrolling bug
opacity: 1;
pointer-events: inherit;
}
@ -520,7 +516,7 @@ body.mobile.phone {
opacity: 0.3;
pointer-events: none;
}
.t-message-widget-no-data {
.js-summary-widget__message {
display: flex;
}
}
@ -804,7 +800,9 @@ mct-indicators mct-include {
}
.label {
// Hover bubbles that appear when hovering on an Indicator
display: inline-block;
a,
button,
s-button,
@ -851,8 +849,6 @@ mct-indicators mct-include {
}
&:not(.no-collapse) {
z-index: 0;
&:before {
margin-right: 0 !important;
}
@ -870,6 +866,8 @@ mct-indicators mct-include {
transform-origin: 10px 100%;
transform: scale(0.0);
white-space: nowrap;
z-index: 50;
&:before {
// Infobubble-style arrow element
content: '';
@ -880,9 +878,9 @@ mct-indicators mct-include {
}
}
&:hover {
@include hover() {
background: $bg;
z-index: 1;
.label {
opacity: 1;
transform: scale(1.0);
@ -1175,79 +1173,6 @@ body.desktop {
}
}
/******************************************************************* WAIT SPINNERS */
@mixin spinner($b: 5px, $c: $colorKey) {
animation-name: rotation-centered;
animation-duration: 0.5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
border-radius: 100%;
box-sizing: border-box;
border-color: rgba($c, 0.25);
border-top-color: rgba($c, 1.0);
border-style: solid;
border-width: $b;
display: block;
position: absolute;
left: 50%; top: 50%;
transform-origin: center;
transform: translate(-50%, -50%);
}
.wait-spinner {
@include spinner($waitSpinnerBorderW, $colorKey);
pointer-events: none;
z-index: 2;
&.inline {
display: inline-block !important;
margin-right: $interiorMargin;
position: relative !important;
vertical-align: middle;
}
}
.loading {
// Can be applied to any block element with height and width
pointer-events: none;
&:before,
&:after {
content: '';
}
&:before {
@include spinner($waitSpinnerBorderW, $colorLoadingFg);
height: $waitSpinnerD; width: $waitSpinnerD;
z-index: 10;
}
&:after {
@include abs();
background: $colorLoadingBg;
display: block;
z-index: 9;
}
&.tree-item.t-wait-node {
$d: $waitSpinnerTreeD;
$spinnerL: $interiorMargin + 19px + $d/2;
padding-left: $spinnerL + $d/2 + $interiorMargin;
.t-title-label {
font-style: italic;
opacity: 0.6;
}
&:before {
height: $d;
width: $d;
border-width: 4px;
left: $spinnerL;
}
&:after {
display: none;
}
}
&.c-loading--overlay {
@include abs();
}
}
/******************************************************************* FLEX STYLES */
.l-flex-row,
.l-flex-col {
@ -1314,7 +1239,7 @@ body.desktop {
.grid-properties {
display: grid;
grid-row-gap: 0;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr 2fr;
}
.grid-span-all,
@ -1512,6 +1437,10 @@ body.desktop {
display: contents;
}
mct-container {
display: block;
}
.overlay {
.outer-holder {
background: $colorMenuBg;
@ -1577,3 +1506,157 @@ body.desktop {
}
}
}
.abs {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: auto;
width: auto;
}
.code {
font-family: "Lucida Console", monospace;
font-size: 0.7em;
line-height: 150%;
white-space: pre;
}
.codehilite {
@extend .code;
background-color: rgba($colorBodyFg, 0.1);
padding: 1em;
}
.s-status-missing {
// Labels. Expects .s-status-missing to be applied to mct-representation that contains
.t-object-label .t-item-icon:before {
content: $glyph-icon-object-unknown;
}
// Item, grid item. Expects .s-status-missing to be applied to mct-representation that contains .item.grid-item
.item .t-item-icon-glyph:before {
content: $glyph-icon-object-unknown;
}
// Object header. Expects .s-status-missing to be applied to mct-representation.object-header
&.object-header {
.type-icon:before {
content: $glyph-icon-object-unknown;
}
}
// Tree item. Expects .s-status-missing to be applied to .tree-item,
// and mct-representation.search-item
&.tree-item,
&.search-item {
> .rep-object-label .t-item-icon:before {
content: $glyph-icon-object-unknown;
}
}
}
.align-right {
text-align: right;
}
.centered {
text-align: center;
}
.no-selection {
// aka selection = "None". Used in palettes and their menu buttons.
$c: red;
$s: 48%;
$e: 52%;
background-image: linear-gradient(-45deg,
transparent $s - 5%,
$c $s,
$c $e,
transparent $e + 5%
);
box-shadow:inset rgba(black, 0.3) 0 0 0 1px;
background-repeat: no-repeat;
background-size: contain;
}
.scrolling,
.scroll {
overflow: auto;
}
.vscroll {
overflow-x: hidden;
overflow-y: auto;
&.scroll-pad {
padding-right: $interiorMargin;
}
}
.vscroll--persist {
overflow-x: hidden;
overflow-y: scroll;
}
.slidable {
cursor: move; // Fallback
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
&.horz {
cursor: col-resize;
}
&.vert {
cursor: row-resize;
}
}
.no-margin {
margin: 0;
}
.ds {
box-shadow: rgba(#000, 0.7) 0 4px 10px 2px;
}
.capitalize {
text-transform: capitalize;
}
.hide,
.hidden,
.t-main-view .hide-in-t-main-view {
display: none !important;
}
.hide-nice {
opacity: 0;
pointer-events: none;
}
.invisible {
display: block;
visibility: hidden;
height: 0;
padding: 0;
border: 0;
margin: 0 !important;
transform: scale(0);
pointer-events: none;
position: absolute;
}
.sep {
color: rgba(#fff, 0.2);
}
.comma-list span {
&:not(:first-child) {
&:before {
content: ', ';
}
}
}

View File

@ -108,7 +108,7 @@
}
@mixin bgStripes($c: yellow, $a: 0.1, $bgsize: 5px, $angle: 90deg) {
background-image: linear-gradient($angle,
background: linear-gradient($angle,
rgba($c, $a) 25%, transparent 25%,
transparent 50%, rgba($c, $a) 50%,
rgba($c, $a) 75%, transparent 75%,
@ -117,6 +117,16 @@
background-size: $bgsize $bgsize;
}
@mixin bgStripes2Color($c1, $c2, $bgSize, $angle: -45deg) {
background: linear-gradient(-45deg,
$c1 0%, $c1 25%,
$c2 25%, $c2 50%,
$c1 50%, $c1 75%,
$c2 75%, $c2 100%
) repeat;
background-size: $bgSize;
}
@mixin disabled() {
opacity: $controlDisabledOpacity;
pointer-events: none !important;
@ -415,12 +425,13 @@
// Background is displayed on hover
// Padding is included to facilitate a bigger hit area
// Make the icon bigger relative to its container
$pLR: 4px;
$pTB: 4px;
@include cControl();
background: none;
box-shadow: none;
cursor: pointer;
$pLR: 4px;
$pTB: 4px;
transition: $transOut;
border-radius: $controlCr;
padding: $pTB $pLR;
@ -467,50 +478,6 @@
}
}
@mixin cCompactButtons($bg, $fg, $bgHov) {
// Used in Tab view
// To be used in indicators popups?
$m: 1px;
$btnM: $m;
&-holder {
background: $colorTabsHolderBg;
border-radius: $controlCr;
padding: $m ($m - $btnM) ($m - $btnM) $m;
display: flex;
flex-flow: row wrap;
justify-content: stretch;
> * {
margin: 0 $btnM $btnM 0;
}
}
background: $colorBtnBg;
color: $colorBtnFg;
flex: 1 1 auto;
padding: $interiorMargin;
&:before {
opacity: 0.5;
}
> * {
margin-left: $interiorMarginSm;
}
@include hover() {
background: $bgHov;
}
&.is-current {
background: $colorBtnReverseBg;
color: $colorBtnReverseFg;
pointer-events: none;
}
}
@mixin cSelect($bg, $fg, $arwClr, $shdw) {
$svgArwClr: str-slice(inspect($arwClr), 2, str-length(inspect($arwClr))); // Remove initial # in color value
background: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3e%3cpath fill='%23#{$svgArwClr}' d='M5 5l5-5H0z'/%3e%3c/svg%3e"), $bg;
@ -608,7 +575,6 @@
@mixin test($c: deeppink, $a: 0.3) {
background: rgba($c, $a) !important;
background-color: rgba($c, $a) !important;
box-shadow: deeppink 0 0 10px 1px !important;
}

View File

@ -20,19 +20,38 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*************************************************** MIXINS */
@mixin statusStyle($bg, $fg, $ic) {
background: $bg !important;
background-color: $bg !important;
color: $fg !important;
@mixin statusStyle($bg, $fg, $imp: false) {
$impStr: null;
@if $imp {
$impStr: !important;
}
background: $bg $impStr;
background-color: $bg $impStr;
color: $fg $impStr;
}
@mixin statusIcon($ic, $glyph: null, $imp: false) {
$impStr: null;
@if $imp {
$impStr: !important;
}
&:before {
color: $ic;
display: inline-block;
font-family: symbolsfont;
font-size: 0.7em;
font-size: 0.8em;
margin-right: $interiorMargin;
@if $glyph != null {
content: $glyph $impStr;
}
}
}
@mixin statusStyleCombined($bg, $fg, $ic) {
@include statusStyle($bg, $fg, $imp: true);
@include statusIcon($ic);
}
@mixin elementStatusColors($c) {
// Sets bg and icon colors for elements
background: rgba($c, 0.5) !important;
@ -47,16 +66,45 @@
}
}
.is-limit--yellow {
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg, $colorLimitYellowIc);
&.is-limit--upr:before { content: $glyph-icon-arrow-up; }
&.is-limit--lwr:before { content: $glyph-icon-arrow-down; }
@mixin andUprLwr {
&.is-limit--upr:before { content: $glyph-icon-arrow-up !important; }
&.is-limit--lwr:before { content: $glyph-icon-arrow-down !important; }
}
.is-limit--red {
@include statusStyle($colorLimitRedBg, $colorLimitRedFg, $colorLimitRedIc);
&.is-limit--upr:before { content: $glyph-icon-arrow-double-up; }
&.is-limit--lwr:before { content: $glyph-icon-arrow-double-down; }
/*************************************************** STYLES */
*:not(tr) {
&.is-limit--yellow {
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg, true);
@include statusIcon($colorLimitYellowIc, $glyph-icon-alert-rect);
@include andUprLwr();
}
&.is-limit--red {
@include statusStyle($colorLimitRedBg, $colorLimitRedFg, true);
@include statusIcon($colorLimitRedIc, $glyph-icon-alert-triangle);
@include andUprLwr();
}
}
tr {
&.is-limit--yellow {
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg);
td:first-child {
@include statusIcon($colorLimitYellowIc, $glyph-icon-alert-rect);
}
td { color: $colorLimitYellowFg; }
}
&.is-limit--red {
@include statusStyle($colorLimitRedBg, $colorLimitRedFg);
td:first-child {
@include statusIcon($colorLimitRedIc, $glyph-icon-alert-triangle);
}
td { color: $colorLimitRedFg; }
}
&.is-limit--upr { td:first-child:before { content: $glyph-icon-arrow-up !important; } }
&.is-limit--lwr { td:first-child:before { content: $glyph-icon-arrow-down !important; } }
}
/*************************************************** STATUS */

View File

@ -19,16 +19,18 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/******************************************************** TABLE */
table {
$minW: 50px;
width: 100%;
thead {
background: $colorTabHeaderBg;
th + th {
border-left: 1px solid $colorTabHeaderBorder;
th {
background: $colorTabHeaderBg;
+ th {
border-left: 1px solid $colorTabHeaderBorder;
}
}
}
@ -47,14 +49,37 @@ table {
td {
vertical-align: top;
}
a { color: $colorBtnMajorBg; }
}
.is-editing {
td.is-selectable {
&:hover {
background: rgba($editUIColorBg, 0.1);
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
}
&[s-selected] {
background: $editUIColorBg !important;
border: 1px solid $editUIColorFg !important;
color: $editUIColorFg !important;
box-shadow: $editFrameSelectedShdw;
z-index: 2;
}
}
}
/******************************************************** C-TABLE */
div.c-table {
// When c-table is used as a wrapper element in more complex table views
height: 100%;
}
.c-table {
// Can be used by any type of table, scrolling, LAD, etc.
$min-w: 50px;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
width: 100%;
&__control-bar,
@ -69,7 +94,7 @@ table {
}
thead tr,
[class*="__header"] {
&.c-table__headers {
background: $colorTabHeaderBg;
th {
@ -117,4 +142,36 @@ table {
th, td {
width: 33%; // Needed to prevent size jumping as values dynamically update
}
}
}
/************************************** TABLE AND SUMMARY VIEWS */
// Displays summary values above a table.
.c-table-and-summary {
height: 100%;
width: 100%;
overflow: auto;
display: flex;
flex-direction: column;
> * + * { margin-top: $interiorMargin; }
&__summary {
display: flex;
justify-items: stretch;
> * + * { margin-left: 1px; }
}
&__summary-item {
background: $colorSummaryBg;
color: $colorSummaryFg;
flex: 1 1 auto;
padding: $interiorMargin $interiorMarginLg;
em {
font-weight: bold;
color: $colorSummaryFgEm;
}
}
}

View File

@ -33,3 +33,4 @@
@import "table";
@import "legacy";
@import "legacy-plots";
@import "legacy-messages";

View File

@ -25,8 +25,8 @@
// Do not include anything that renders to CSS!
@import "constants";
@import "constants-mobile.scss";
@import "constants-espresso"; // TEMP
//@import "constants-snow"; // TEMP
//@import "constants-espresso"; // TEMP
@import "constants-snow"; // TEMP
//@import "constants-maelstrom";
@import "mixins";
@import "animations";

View File

@ -26,21 +26,19 @@
'has-complex-content': complexContent
}">
<div class="c-so-view__header">
<div class="c-so-view__header__start">
<div class="c-so-view__header__name"
:class="cssClass">
{{ domainObject && domainObject.name }}
</div>
<context-menu-drop-down
<div class="c-so-view__header__icon" :class="cssClass"></div>
<div class="c-so-view__header__name">
{{ domainObject && domainObject.name }}
</div>
<context-menu-drop-down
:object-path="objectPath">
</context-menu-drop-down>
</div>
<div class="c-so-view__header__end">
<div class="c-button icon-expand local-controls--hidden"
title="View Large"
@click="expand">
</div>
</div>
</context-menu-drop-down>
</div>
<div class="c-so-view__local-controls c-so-view__view-large h-local-controls c-local-controls--show-on-hover">
<button class="c-button icon-expand"
title="View Large"
@click="expand">
</button>
</div>
<object-view
class="c-so-view__object-view"
@ -65,45 +63,53 @@
align-items: center;
margin-bottom: $interiorMargin;
&__start,
&__end {
display: flex;
flex: 1 1 auto;
}
&__end {
justify-content: flex-end;
}
&__name {
@include headerFont(1em);
display: flex;
&:before {
margin-right: $interiorMarginSm;
}
}
}
&--no-frame > .c-so-view__header {
display: none;
}
&__name {
@include ellipsize();
@include headerFont(1.2em);
flex: 0 1 auto;
&:before {
// Object type icon
&__icon {
flex: 0 0 auto;
margin-right: $interiorMarginSm;
opacity: 0.5;
}
&__name {
@include headerFont(1em);
@include ellipsize();
flex: 0 1 auto;
}
}
&:not(.c-so-view--no-frame) {
background: $colorBodyBg;
border: $browseFrameBorder;
padding: $interiorMargin;
}
&--no-frame {
> .c-so-view__header {
display: none;
}
> .c-so-view__local-controls {
top: $interiorMarginSm; right: $interiorMarginSm;
}
}
&__local-controls {
position: absolute;
top: $interiorMargin; right: $interiorMargin;
z-index: 2;
}
&__view-large {
display: none;
}
&.has-complex-content {
> .c-so-view__view-large { display: block; }
}
/*************************** OBJECT VIEW */
&__object-view {
flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
.c-object-view {
@ -128,7 +134,9 @@
const SIMPLE_CONTENT_TYPES = [
'clock',
'summary-widget'
'timer',
'summary-widget',
'hyperlink'
];
export default {

View File

@ -59,7 +59,8 @@ export default {
default() {
return [];
}
}
},
navigateToPath: String
},
data() {
return {

View File

@ -34,7 +34,10 @@ export default {
this.currentObject = this.object;
this.updateView();
this.$el.addEventListener('dragover', this.onDragOver);
this.$el.addEventListener('drop', this.onDrop);
this.$el.addEventListener('drop', this.addObjectToParent);
this.$el.addEventListener('drop', this.editIfEditable, {
capture: true
});
},
methods: {
clear() {
@ -70,22 +73,18 @@ export default {
this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('c-object-view','u-contents');
this.$el.append(this.viewContainer);
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
let provider = this.getViewProvider();
if (!provider) {
provider = this.openmct.objectViews.get(this.currentObject)[0];
if (!provider) {
return;
}
return;
}
if (provider.edit && this.showEditView) {
if (this.openmct.editor.isEditing()) {
this.currentView = provider.edit(this.currentObject);
} else {
this.currentView = provider.view(this.currentObject, false);
}
this.openmct.editor.on('isEditing', this.toggleEditView);
this.releaseEditModeHandler = () => this.openmct.editor.off('isEditing', this.toggleEditView);
} else {
@ -133,7 +132,7 @@ export default {
event.preventDefault();
}
},
onDrop(event) {
addObjectToParent(event) {
if (this.hasComposableDomainObject(event)) {
let composableDomainObject = this.getComposableDomainObject(event);
this.currentObject.composition.push(composableDomainObject.identifier);
@ -142,6 +141,25 @@ export default {
event.stopPropagation();
}
},
getViewProvider() {
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
if (!provider) {
provider = this.openmct.objectViews.get(this.currentObject)[0];
if (!provider) {
return;
}
}
return provider;
},
editIfEditable(event) {
let provider = this.getViewProvider();
if (provider &&
provider.canEdit(this.currentObject) &&
!this.openmct.editor.isEditing()) {
this.openmct.editor.edit();
}
},
hasComposableDomainObject(event) {
return event.dataTransfer.types.includes('openmct/composable-domain-object')
},

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