Compare commits

...

64 Commits

Author SHA1 Message Date
38c1a981e8 Clock element as a standalone component
- Was intended to replace clock indicator;
- WIP!
2019-06-25 10:10:58 -07:00
50944cfed0 Status bar migration to top of layout, WIP
- Added tracking style to indicator-template;
- Moved click action to button in label of globalClearIndicator;
- Removed unnecessary markup in Indicators.vue;
- Commented out __head collapse button for now in Layout.vue;
2019-06-07 19:04:30 -07:00
27b236ffe4 Status bar migration to top of layout, WIP
- Refine styles and markup for Indicators;
- Better separation of styles for clickable and non-clickable
Indicators;
2019-06-07 18:30:08 -07:00
b7e75bcc22 Status bar migration to top of layout, WIP
- Refine and remove legacy styles for Indicators;
- Significant cleanup in Indicator markup;
- Remove unnecessary wrapper component StatusBar.vue;
- Move collapse-button styles to a more general location in _controls
.scss;
- New hasMenu mixin to allow easier application of disclosure control
styling;
2019-06-07 16:07:50 -07:00
210a23dfe2 styling 2019-06-06 15:29:19 -07:00
84bfb9e8cc global clear works on plots 2019-06-06 15:06:03 -07:00
fd749253d9 first proto of global clear, working on tables 2019-06-06 14:38:41 -07:00
c38d810658 Fix import export (#2407)
* working import/export, need to check with objects that have name-spaces

* use keystrings instead of key
2019-05-24 12:04:40 -07:00
f5c48b7bf6 Fix regression in adding to display layouts (#2408)
* Removed policy preventing duplicate composition, and implemented no-op in composition provider instead

* Change order of edit on drop event listener

* Add mutation listener to CompositionCollection even if nothing listening to collection

* Updated test specs

* Address review comments

* Fix regression

* Removed redundant composition creation
2019-05-24 11:55:16 -07:00
d0e08f1d9a Fix typos that prevent building in linux 2019-05-24 11:24:43 -07:00
72ea7b80fd [Summary Widget] support enum fields (#2406)
* Display a drop down menu if the selected key is of type enum.

* Create normalized dataum when persisting telemerty datum using  metadatum source as key.:

* * Clear config values before creating new inputs.
* Emit ‘change' event with the value of the first option after creating the select element.
* If a value is a number, pass it as a number when emitting ‘change’. Similarly, if the cashed telemetry value is a number, convert it to number before applying the operation and validation.

* Update description.

* Update description in operations.js also.
2019-05-24 09:18:46 -07:00
35d0c02bc5 Discard old telemetry values in tables when date is formatted as a string (#2400)
* Parse date values before comparison in BoundedTableRowCollection

* Reset table size when filter changes
2019-05-23 14:42:37 -07:00
abd7506b45 Plots issues for 4.1.1 (#2397)
* working fix

* prevent wheel zoom when nothing is plotted

* fix bug where chart was not getting rid of plot history

* override remove from series collection to keep changes contained

* don't untrack twice from plot options controller

* make plot controller the life cycle controller for config, destroy when the plot is destroyed. Remove tracking system. Add comments to zoom logic, and simplify remove and keep it in series collection

* add comments to removeTelemetryObject
2019-05-23 09:43:45 -07:00
526b4aa07e Remove duplicate policy (#2399)
* Removed policy preventing duplicate composition, and implemented no-op in composition provider instead

* Change order of edit on drop event listener

* Add mutation listener to CompositionCollection even if nothing listening to collection

* Updated test specs

* Address review comments
2019-05-20 19:14:12 -07:00
b5e23963d4 [Summary Widget] Use installed time system's name... (#2398)
* Added LocalTimeSystem to standard plugins object.

* Use each installed time system's name instead of naming them all 'UTC'.
2019-05-16 10:24:38 -07:00
2c11eb90d4 Add additional check for presence of configuration attribute (#2393) 2019-04-29 19:18:27 -07:00
90e9c79e19 Table rendering performance tweaks (#2392)
* Table rendering performance tweaks

Throttled add, remove, and scroll

* Scroll to bottom after resize, if auto-scroll enabled
2019-04-28 17:43:06 -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
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
130 changed files with 3097 additions and 1651 deletions

View File

@ -1,6 +1,6 @@
<span class="h-indicator" ng-controller="DialogLaunchController"> <span class="h-indicator" ng-controller="DialogLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="ls-indicator icon-box-with-arrow s-status-available"><span class="label"> <div class="c-indicator c-indicator--clickable icon-box-with-arrow s-status-available"><span class="label">
<a ng-click="launchProgress(true)">Known</a> <a ng-click="launchProgress(true)">Known</a>
<a ng-click="launchProgress(false)">Unknown</a> <a ng-click="launchProgress(false)">Unknown</a>
<a ng-click="launchError()">Error</a> <a ng-click="launchError()">Error</a>

View File

@ -1,6 +1,6 @@
<span class="h-indicator" ng-controller="NotificationLaunchController"> <span class="h-indicator" ng-controller="NotificationLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="ls-indicator icon-bell s-status-available"><span class="label"> <div class="c-indicator c-indicator--clickable icon-bell s-status-available"><span class="label">
<a ng-click="newInfo()">Success</a> <a ng-click="newInfo()">Success</a>
<a ng-click="newError()">Error</a> <a ng-click="newError()">Error</a>
<a ng-click="newAlert()">Alert</a> <a ng-click="newAlert()">Alert</a>

View File

@ -86,6 +86,8 @@
openmct.install(openmct.plugins.LADTable()); openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration()); openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.GoToOriginalAction());
openmct.install(openmct.plugins.GlobalClearIndicator());
openmct.start(); openmct.start();
</script> </script>
</html> </html>

View File

@ -9,6 +9,7 @@
ng-model="ngModel" ng-model="ngModel"
ng-show="ngModel.progressPerc !== undefined"></mct-include> ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div> </div>
</div>
<div class="c-overlay__button-bar"> <div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options" <button ng-repeat="dialogOption in ngModel.options"
class="c-button" class="c-button"
@ -21,5 +22,4 @@
{{ngModel.primaryOption.label}} {{ngModel.primaryOption.label}}
</button> </button>
</div> </div>
</div>
</div> </div>

View File

@ -49,7 +49,7 @@ define(
name: "Properties", name: "Properties",
rows: this.properties.map(function (property, index) { rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition // 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; row.key = index;
return row; return row;
}).filter(function (row) { }).filter(function (row) {

View File

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

View File

@ -66,7 +66,7 @@ define(
name: "Properties", name: "Properties",
rows: this.properties.map(function (property, index) { rows: this.properties.map(function (property, index) {
// Property definition is same as form row definition // 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; // Use index as the key into the formValue;
// this correlates to the indexing provided by // this correlates to the indexing provided by

View File

@ -77,14 +77,19 @@ define([], function () {
return promiseFn().then(nextFn); return promiseFn().then(nextFn);
}; };
} }
/**
* 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);
}
if (!this.isScheduled(id)) {
this.clearTransactionFns[id] = this.clearTransactionFns[id] =
this.transactionService.addToTransaction( this.transactionService.addToTransaction(
chain(onCommit, release), chain(onCommit, release),
chain(onCancel, release) chain(onCancel, release)
); );
}
}; };
/** /**

View File

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

View File

@ -20,7 +20,7 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="ls-indicator {{ngModel.getCssClass()}}" <div class="c-indicator {{ngModel.getCssClass()}}"
title="{{ngModel.getDescription()}}" title="{{ngModel.getDescription()}}"
ng-show="ngModel.getText().length > 0"> ng-show="ngModel.getText().length > 0">
<span class="label">{{ngModel.getText()}}</span> <span class="label">{{ngModel.getText()}}</span>

View File

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

View File

@ -1,5 +1,5 @@
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div ng-show="notifications.length > 0" class="ls-indicator s-status-{{highest.severity}} icon-bell" <div ng-show="notifications.length > 0" class="c-indicator c-indicator--clickable s-status-{{highest.severity}} icon-bell"
ng-controller="NotificationIndicatorController"> ng-controller="NotificationIndicatorController">
<span class="label"> <span class="label">
<a ng-click="showNotificationsList()"> <a ng-click="showNotificationsList()">

View File

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

View File

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

View File

@ -24,7 +24,6 @@ define([
"./src/actions/MoveAction", "./src/actions/MoveAction",
"./src/actions/CopyAction", "./src/actions/CopyAction",
"./src/actions/LinkAction", "./src/actions/LinkAction",
"./src/actions/GoToOriginalAction",
"./src/actions/SetPrimaryLocationAction", "./src/actions/SetPrimaryLocationAction",
"./src/services/LocatingCreationDecorator", "./src/services/LocatingCreationDecorator",
"./src/services/LocatingObjectDecorator", "./src/services/LocatingObjectDecorator",
@ -41,7 +40,6 @@ define([
MoveAction, MoveAction,
CopyAction, CopyAction,
LinkAction, LinkAction,
GoToOriginalAction,
SetPrimaryLocationAction, SetPrimaryLocationAction,
LocatingCreationDecorator, LocatingCreationDecorator,
LocatingObjectDecorator, LocatingObjectDecorator,
@ -104,14 +102,6 @@ define([
"linkService" "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", "key": "locate",
"name": "Set Primary Location", "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

@ -49,7 +49,7 @@ define(
}; };
ClockIndicator.prototype.getCssClass = function () { ClockIndicator.prototype.getCssClass = function () {
return "t-indicator-clock icon-clock no-collapse float-right"; return "t-indicator-clock icon-clock no-collapse c-indicator--not-clickable";
}; };
ClockIndicator.prototype.getText = function () { ClockIndicator.prototype.getText = function () {

View File

@ -80,15 +80,17 @@ define(['zepto'], function ($) {
var newObj; var newObj;
seen.push(parent.getId()); seen.push(parent.getId());
parentModel.composition.forEach(function (childId, index) {
if (!tree[childId] || seen.includes(childId)) { parentModel.composition.forEach(function (childId) {
let keystring = this.openmct.objects.makeKeyString(childId);
if (!tree[keystring] || seen.includes(keystring)) {
return; return;
} }
newObj = this.instantiate(tree[childId], childId); newObj = this.instantiate(tree[keystring], keystring);
parent.getCapability("composition").add(newObj);
newObj.getCapability("location") newObj.getCapability("location")
.setPrimaryLocation(tree[childId].location); .setPrimaryLocation(tree[keystring].location);
this.deepInstantiate(newObj, tree, seen); this.deepInstantiate(newObj, tree, seen);
}, this); }, this);
} }

View File

@ -100,7 +100,7 @@ define(
} }
CouchIndicator.prototype.getCssClass = function () { CouchIndicator.prototype.getCssClass = function () {
return "icon-database " + this.state.statusClass; return "c-indicator--clickable icon-database " + this.state.statusClass;
}; };
CouchIndicator.prototype.getGlyphClass = function () { CouchIndicator.prototype.getGlyphClass = function () {

View File

@ -84,7 +84,7 @@ define(
} }
ElasticIndicator.prototype.getCssClass = function () { ElasticIndicator.prototype.getCssClass = function () {
return "icon-database"; return "c-indicator--clickable icon-database";
}; };
ElasticIndicator.prototype.getGlyphClass = function () { ElasticIndicator.prototype.getGlyphClass = function () {
return this.state.glyphClass; return this.state.glyphClass;

View File

@ -41,7 +41,7 @@ define(
} }
LocalStorageIndicator.prototype.getCssClass = function () { LocalStorageIndicator.prototype.getCssClass = function () {
return "icon-database s-status-caution"; return "c-indicator--clickable icon-database s-status-caution";
}; };
LocalStorageIndicator.prototype.getGlyphClass = function () { LocalStorageIndicator.prototype.getGlyphClass = function () {
return 'caution'; return 'caution';

View File

@ -33,9 +33,13 @@ export default class LegacyContextMenuAction {
} }
invoke(objectPath) { invoke(objectPath) {
this.openmct.objects.getRoot().then((root) => {
let pathWithRoot = objectPath.slice();
pathWithRoot.push(root);
let context = { let context = {
category: 'contextual', category: 'contextual',
domainObject: this.openmct.legacyObject(objectPath) domainObject: this.openmct.legacyObject(pathWithRoot)
} }
let legacyAction = new this.LegacyAction(context); let legacyAction = new this.LegacyAction(context);
@ -47,6 +51,7 @@ export default class LegacyContextMenuAction {
}.bind(legacyAction); }.bind(legacyAction);
} }
legacyAction.perform(); legacyAction.perform();
});
} }
appliesTo(objectPath) { appliesTo(objectPath) {

View File

@ -36,7 +36,7 @@ define([
'./runs/RegisterLegacyTypes', './runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor', './services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews', './views/installLegacyViews',
'./policies/legacyCompositionPolicyAdapter', './policies/LegacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter' './actions/LegacyActionAdapter'
], function ( ], function (
legacyRegistry, legacyRegistry,

View File

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

View File

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

View File

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

View File

@ -28,11 +28,6 @@ export default class Editor extends EventEmitter {
super(); super();
this.editing = false; this.editing = false;
this.openmct = openmct; 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 * @private
*/ */
cancel() { cancel() {
this.getTransactionService().cancel(); let cancelPromise = this.getTransactionService().cancel();
this.editing = false; this.editing = false;
this.emit('isEditing', false); this.emit('isEditing', false);
return cancelPromise;
} }
/** /**

View File

@ -22,8 +22,20 @@ define([
publicAPI = {}; publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [ publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get', 'get',
'mutate' 'mutate',
'observe',
'areIdsEqual'
]); ]);
publicAPI.objects.areIdsEqual.and.callFake(function (id1, id2) {
return id1.namespace === id2.namespace && id1.key === id2.key;
});
publicAPI.composition = jasmine.createSpyObj('CompositionAPI', [
'checkPolicy'
]);
publicAPI.composition.checkPolicy.and.returnValue(true);
publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [ publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
'on' 'on'
]); ]);
@ -119,49 +131,16 @@ define([
expect(newComposition[2].key).toEqual('a'); expect(newComposition[2].key).toEqual('a');
}) })
}); });
it('supports adding an object to composition', function () {
// TODO: Implement add/removal in new default provider. let addListener = jasmine.createSpy('addListener');
xit('synchronizes changes between instances', function () { let mockChildObject = {
var otherComposition = compositionAPI.get(domainObject); identifier: {key: 'mock-key', namespace: ''}
var addListener = jasmine.createSpy('addListener'); };
var removeListener = jasmine.createSpy('removeListener');
var otherAddListener = jasmine.createSpy('otherAddListener');
var otherRemoveListener = jasmine.createSpy('otherRemoveListener');
composition.on('add', addListener); composition.on('add', addListener);
composition.on('remove', removeListener); composition.add(mockChildObject);
otherComposition.on('add', otherAddListener);
otherComposition.on('remove', otherRemoveListener);
return Promise.all([composition.load(), otherComposition.load()]) expect(domainObject.composition.length).toBe(4);
.then(function () { expect(domainObject.composition[3]).toEqual(mockChildObject.identifier);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
expect(removeListener).not.toHaveBeenCalled();
expect(otherRemoveListener).not.toHaveBeenCalled();
var object = addListener.calls.mostRecent().args[0];
composition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
composition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
removeListener.reset();
otherRemoveListener.reset();
otherComposition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
otherComposition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
});
}); });
}); });
@ -184,7 +163,9 @@ define([
key: 'thing' key: 'thing'
} }
]); ]);
} },
add: jasmine.createSpy('add'),
remove: jasmine.createSpy('remove')
}; };
domainObject = { domainObject = {
identifier: { identifier: {
@ -214,6 +195,25 @@ define([
}); });
}); });
}); });
describe('Calling add or remove', function () {
let mockChildObject;
beforeEach(function () {
mockChildObject = {
identifier: {key: 'mock-key', namespace: ''}
};
composition.add(mockChildObject);
});
it('calls add on the provider', function () {
expect(customProvider.add).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
});
it('calls remove on the provider', function () {
composition.remove(mockChildObject);
expect(customProvider.remove).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
});
});
}); });
describe('dynamic custom composition', function () { describe('dynamic custom composition', function () {

View File

@ -25,7 +25,6 @@ define([
], function ( ], function (
_ _
) { ) {
/** /**
* A CompositionCollection represents the list of domain objects contained * A CompositionCollection represents the list of domain objects contained
* by another domain object. It provides methods for loading this * by another domain object. It provides methods for loading this
@ -63,7 +62,6 @@ define([
this.onProviderRemove = this.onProviderRemove.bind(this); this.onProviderRemove = this.onProviderRemove.bind(this);
} }
/** /**
* Listen for changes to this composition. Supports 'add', 'remove', and * Listen for changes to this composition. Supports 'add', 'remove', and
* 'load' events. * 'load' events.
@ -76,7 +74,9 @@ define([
if (!this.listeners[event]) { if (!this.listeners[event]) {
throw new Error('Event not supported by composition: ' + event); throw new Error('Event not supported by composition: ' + event);
} }
if (!this.mutationListener) {
this._synchronize();
}
if (this.provider.on && this.provider.off) { if (this.provider.on && this.provider.off) {
if (event === 'add') { if (event === 'add') {
this.provider.on( this.provider.on(
@ -132,6 +132,8 @@ define([
this.listeners[event].splice(index, 1); this.listeners[event].splice(index, 1);
if (this.listeners[event].length === 0) { if (this.listeners[event].length === 0) {
this._destroy();
// Remove provider listener if this is the last callback to // Remove provider listener if this is the last callback to
// be removed. // be removed.
if (this.provider.off && this.provider.on) { if (this.provider.off && this.provider.on) {
@ -175,6 +177,9 @@ define([
*/ */
CompositionCollection.prototype.add = function (child, skipMutate) { CompositionCollection.prototype.add = function (child, skipMutate) {
if (!skipMutate) { if (!skipMutate) {
if (!this.publicAPI.composition.checkPolicy(this.domainObject, child)) {
throw `Object of type ${child.type} cannot be added to object of type ${this.domainObject.type}`;
}
this.provider.add(this.domainObject, child.identifier); this.provider.add(this.domainObject, child.identifier);
} else { } else {
this.emit('add', child); this.emit('add', child);
@ -266,6 +271,19 @@ define([
this.remove(child, true); this.remove(child, true);
}; };
CompositionCollection.prototype._synchronize = function () {
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
this.domainObject = JSON.parse(JSON.stringify(newDomainObject));
});
};
CompositionCollection.prototype._destroy = function () {
if (this.mutationListener) {
this.mutationListener();
delete this.mutationListener;
}
};
/** /**
* Emit events. * Emit events.
* @private * @private

View File

@ -48,24 +48,11 @@ define([
this.listeningTo = {}; this.listeningTo = {};
this.onMutation = this.onMutation.bind(this); this.onMutation = this.onMutation.bind(this);
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
this.cannotContainItself = this.cannotContainItself.bind(this); this.cannotContainItself = this.cannotContainItself.bind(this);
compositionAPI.addPolicy(this.cannotContainDuplicates);
compositionAPI.addPolicy(this.cannotContainItself); compositionAPI.addPolicy(this.cannotContainItself);
} }
/**
* @private
*/
DefaultCompositionProvider.prototype.cannotContainDuplicates = function (parent, child) {
return this.appliesTo(parent) &&
parent.composition.findIndex((composeeId) => {
return composeeId.namespace === child.identifier.namespace &&
composeeId.key === child.identifier.key;
}) === -1;
}
/** /**
* @private * @private
*/ */
@ -199,9 +186,18 @@ define([
* @memberof module:openmct.CompositionProvider# * @memberof module:openmct.CompositionProvider#
* @method add * @method add
*/ */
DefaultCompositionProvider.prototype.add = function (domainObject, child) { DefaultCompositionProvider.prototype.add = function (parent, childId) {
throw new Error('Default Provider does not implement adding.'); if (!this.includes(parent, childId)) {
// TODO: this needs to be synchronized via mutation parent.composition.push(childId);
this.publicAPI.objects.mutate(parent, 'composition', parent.composition);
}
};
/**
* @private
*/
DefaultCompositionProvider.prototype.includes = function (parent, childId) {
return parent.composition.findIndex(composee =>
this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1;
}; };
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) { DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {

View File

@ -1,3 +1,3 @@
<div class="ls-indicator" title=""> <div class="c-indicator c-indicator--clickable c-indicator--simple" title="">
<span class="label indicator-text"></span> <span class="label indicator-text"></span>
</div> </div>

View File

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

View File

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

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'./components/LadTable.vue', './components/LADTable.vue',
'vue' 'vue'
], function ( ], function (
LadTableComponent, LadTableComponent,

View File

@ -41,7 +41,7 @@
<script> <script>
import lodash from 'lodash'; import lodash from 'lodash';
import LadRow from './LadRow.vue'; import LadRow from './LADRow.vue';
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],

View File

@ -52,7 +52,7 @@
<script> <script>
import lodash from 'lodash'; import lodash from 'lodash';
import LadRow from './LadRow.vue'; import LadRow from './LADRow.vue';
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],

View File

@ -28,11 +28,17 @@ define([], function () {
key: "layout", key: "layout",
description: "A toolbar for objects inside a display layout.", description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) { forSelection: function (selection) {
// Apply the layout toolbar if the selected object if (!selection || selection.length === 0) {
// is inside a layout, or the main layout is selected. return false;
return (selection && }
((selection[1] && selection[1].context.item && selection[1].context.item.type === 'layout') ||
(selection[0].context.item && selection[0].context.item.type === 'layout'))); 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) { toolbar: function (selection) {
const DIALOG_FORM = { const DIALOG_FORM = {
@ -73,27 +79,36 @@ define([], function () {
return openmct.$injector.get('dialogService').getUserInput(form, {}); return openmct.$injector.get('dialogService').getUserInput(form, {});
} }
function getPath() { function getPath(selectionPath) {
return `configuration.items[${selection[0].context.index}]`; return `configuration.items[${selectionPath[0].context.index}]`;
} }
let selectedParent = selection[1] && selection[1].context.item, function getAllTypes(selection) {
selectedObject = selection[0].context.item, return selection.filter(selectionPath => {
layoutItem = selection[0].context.layoutItem, let type = selectionPath[0].context.layoutItem.type;
toolbar = []; return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view' ||
type === 'subobject-view';
});
}
if (selectedObject && selectedObject.type === 'layout') { function getAddButton(selection, selectionPath) {
toolbar.push({ if (selection.length === 1) {
selectionPath = selectionPath || selection[0];
return {
control: "menu", control: "menu",
domainObject: selectedObject, domainObject: selectionPath[0].context.item,
method: function (option) { method: function (option) {
let name = option.name.toLowerCase(); let name = option.name.toLowerCase();
let form = DIALOG_FORM[name]; let form = DIALOG_FORM[name];
if (form) { if (form) {
getUserInput(form) getUserInput(form)
.then(element => selection[0].context.addElement(name, element)); .then(element => selectionPath[0].context.addElement(name, element));
} else { } else {
selection[0].context.addElement(name); selectionPath[0].context.addElement(name);
} }
}, },
key: "add", key: "add",
@ -117,23 +132,43 @@ define([], function () {
"class": "icon-image" "class": "icon-image"
} }
] ]
});
}
if (!layoutItem) {
return toolbar;
}
let separator = {
control: "separator"
}; };
let remove = { }
}
function getToggleFrameButton(selectedParent, selection) {
return {
control: "toggle-button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath =>
selectionPath[0].context.layoutItem.type === 'subobject-view'
),
property: function (selectionPath) {
return getPath(selectionPath) + ".hasFrame";
},
options: [
{
value: false,
icon: 'icon-frame-show',
title: "Frame visible"
},
{
value: true,
icon: 'icon-frame-hide',
title: "Frame hidden"
}
]
};
}
function getRemoveButton(selectedParent, selectionPath, selection) {
return {
control: "button", control: "button",
domainObject: selectedParent, domainObject: selectedParent,
icon: "icon-trash", icon: "icon-trash",
title: "Delete the selected object", title: "Delete the selected object",
method: function () { method: function () {
let removeItem = selection[1].context.removeItem; let removeItem = selectionPath[1].context.removeItem;
let prompt = openmct.overlays.dialog({ let prompt = openmct.overlays.dialog({
iconClass: 'alert', iconClass: 'alert',
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`, message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
@ -142,7 +177,7 @@ define([], function () {
label: 'Ok', label: 'Ok',
emphasis: 'true', emphasis: 'true',
callback: function () { callback: function () {
removeItem(layoutItem, selection[0].context.index); removeItem(getAllTypes(selection));
prompt.dismiss(); prompt.dismiss();
} }
}, },
@ -156,7 +191,10 @@ define([], function () {
}); });
} }
}; };
let stackOrder = { }
function getStackOrder(selectedParent, selectionPath) {
return {
control: "menu", control: "menu",
domainObject: selectedParent, domainObject: selectedParent,
icon: "icon-layers", icon: "icon-layers",
@ -184,138 +222,122 @@ define([], function () {
} }
], ],
method: function (option) { method: function (option) {
selection[1].context.orderItem(option.value, selection[0].context.index); selectionPath[1].context.orderItem(option.value, getAllTypes(selection));
} }
}; };
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"
} }
]
}; function getXInput(selectedParent, selection) {
let x = { if (selection.length === 1) {
return {
control: "input", control: "input",
type: "number", type: "number",
domainObject: selectedParent, domainObject: selectedParent,
property: function () { applicableSelectedItems: getAllTypes(selection),
return getPath() + ".x"; property: function (selectionPath) {
return getPath(selectionPath) + ".x";
}, },
label: "X:", label: "X:",
title: "X position" title: "X position"
}, };
y = { }
}
function getYInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: "input", control: "input",
type: "number", type: "number",
domainObject: selectedParent, domainObject: selectedParent,
property: function () { applicableSelectedItems: getAllTypes(selection),
return getPath() + ".y"; property: function (selectionPath) {
return getPath(selectionPath) + ".y";
}, },
label: "Y:", label: "Y:",
title: "Y position", title: "Y position",
}, };
width = { }
}
function getWidthInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: 'input', control: 'input',
type: 'number', type: 'number',
domainObject: selectedParent, domainObject: selectedParent,
property: function () { applicableSelectedItems: getAllTypes(selection),
return getPath() + ".width"; property: function (selectionPath) {
return getPath(selectionPath) + ".width";
}, },
label: 'W:', label: 'W:',
title: 'Resize object width' title: 'Resize object width'
}, };
height = { }
}
function getHeightInput(selectedParent, selection) {
if (selection.length === 1) {
return {
control: 'input', control: 'input',
type: 'number', type: 'number',
domainObject: selectedParent, domainObject: selectedParent,
property: function () { applicableSelectedItems: getAllTypes(selection),
return getPath() + ".height"; property: function (selectionPath) {
return getPath(selectionPath) + ".height";
}, },
label: 'H:', label: 'H:',
title: 'Resize object height' title: 'Resize object height'
}; };
}
if (layoutItem.type === 'subobject-view') {
if (toolbar.length > 0) {
toolbar.push(separator);
} }
toolbar.push({ function getX2Input(selectedParent, selection) {
control: "toggle-button", if (selection.length === 1) {
return {
control: "input",
type: "number",
domainObject: selectedParent, domainObject: selectedParent,
property: function () { applicableSelectedItems: selection.filter(selectionPath => {
return getPath() + ".hasFrame"; return selectionPath[0].context.layoutItem.type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".x2";
}, },
options: [ label: "X2:",
{ title: "X2 position"
value: false, };
icon: 'icon-frame-show',
title: "Frame visible"
},
{
value: true,
icon: 'icon-frame-hide',
title: "Frame hidden"
} }
] }
});
toolbar.push(separator); function getY2Input(selectedParent, selection) {
toolbar.push(stackOrder); if (selection.length === 1) {
toolbar.push(x); return {
toolbar.push(y); control: "input",
toolbar.push(width); type: "number",
toolbar.push(height); domainObject: selectedParent,
toolbar.push(useGrid); applicableSelectedItems: selection.filter(selectionPath => {
toolbar.push(separator); return selectionPath[0].context.layoutItem.type === 'line-view';
toolbar.push(remove); }),
} else { 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]; const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128];
let fill = { return {
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 = {
control: "select-menu", control: "select-menu",
domainObject: selectedParent, domainObject: selectedParent,
property: function () { applicableSelectedItems: selection.filter(selectionPath => {
return getPath() + ".size"; 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", title: "Set text size",
options: TEXT_SIZE.map(size => { options: TEXT_SIZE.map(size => {
@ -324,13 +346,128 @@ define([], function () {
}; };
}) })
}; };
}
if (layoutItem.type === 'telemetry-view') { function getFillMenu(selectedParent, selection) {
let displayMode = { 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", control: "select-menu",
domainObject: selectedParent, domainObject: selectedParent,
property: function () { applicableSelectedItems: selection.filter(selectionPath => {
return getPath() + ".displayMode"; return selectionPath[0].context.layoutItem.type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".displayMode";
}, },
title: "Set display mode", title: "Set display mode",
options: [ options: [
@ -347,146 +484,196 @@ define([], function () {
value: "value" 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 * @param {number[]} pixelDelta the offset from the
* original position, in pixels * original position, in pixels
*/ */
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) { LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta); var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return { return {
position: max(add( 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; return LayoutDrag;
} }

View File

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

View File

@ -23,8 +23,11 @@
<template> <template>
<div class="l-layout" <div class="l-layout"
@dragover="handleDragOver" @dragover="handleDragOver"
@click="bypassSelection" @click.capture="bypassSelection"
@drop="handleDrop"> @drop="handleDrop"
:class="{
'is-multi-selected': selectedLayoutItems.length > 1
}">
<!-- Background grid --> <!-- Background grid -->
<div class="l-layout__grid-holder c-grid"> <div class="l-layout__grid-holder c-grid">
<div class="c-grid__x l-grid l-grid-x" <div class="c-grid__x l-grid l-grid-x"
@ -39,18 +42,38 @@
:is="item.type" :is="item.type"
:item="item" :item="item"
:key="item.id" :key="item.id"
:gridSize="item.useGrid ? gridSize : [1, 1]" :gridSize="gridSize"
:initSelect="initSelectIndex === index" :initSelect="initSelectIndex === index"
:index="index" :index="index"
@endDrag="endDrag" :multiSelect="selectedLayoutItems.length > 1"
> @move="move"
@endMove="endMove"
@endLineResize='endLineResize'>
</component> </component>
<edit-marquee v-if='showMarquee'
:gridSize="gridSize"
:selectedLayoutItems="selectedLayoutItems"
@endResize="endResize">
</edit-marquee>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base"; @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 { .l-layout {
@include abs(); @include abs();
display: flex; display: flex;
@ -70,7 +93,7 @@
.l-shell__main-container { .l-shell__main-container {
&[s-selected], &[s-selected],
&[s-selected-parent] { &[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 { > .l-layout {
background: $editUIGridColorBg; background: $editUIGridColorBg;
@ -84,7 +107,7 @@
.l-layout__frame { .l-layout__frame {
&[s-selected], &[s-selected],
&[s-selected-parent] { &[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 { > * > * > .l-layout {
background: $editUIGridColorBg; background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px; 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> </style>
<script> <script>
import uuid from 'uuid'; import uuid from 'uuid';
@ -108,6 +142,7 @@
import TextView from './TextView.vue' import TextView from './TextView.vue'
import LineView from './LineView.vue' import LineView from './LineView.vue'
import ImageView from './ImageView.vue' import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue'
const ITEM_TYPE_VIEW_MAP = { const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView, 'subobject-view': SubobjectView,
@ -123,9 +158,11 @@
down: -1, down: -1,
bottom: Number.NEGATIVE_INFINITY bottom: Number.NEGATIVE_INFINITY
}; };
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/'; const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee;
function getItemDefinition(itemType, ...options) { function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType]; let itemView = ITEM_TYPE_VIEW_MAP[itemType];
@ -141,7 +178,8 @@
let domainObject = JSON.parse(JSON.stringify(this.domainObject)); let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return { return {
internalDomainObject: domainObject, internalDomainObject: domainObject,
initSelectIndex: undefined initSelectIndex: undefined,
selection: []
}; };
}, },
computed: { computed: {
@ -150,82 +188,145 @@
}, },
layoutItems() { layoutItems() {
return this.internalDomainObject.configuration.items; 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', 'options'], inject: ['openmct', 'options'],
props: ['domainObject'], props: ['domainObject'],
components: ITEM_TYPE_VIEW_MAP, components: components,
methods: { methods: {
addElement(itemType, element) { addElement(itemType, element) {
this.addItem(itemType + '-view', element); this.addItem(itemType + '-view', element);
}, },
setSelection(selection) { setSelection(selection) {
if (selection.length === 0) { this.selection = selection;
return;
}
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
let itemIndex = selection[0].context.index;
if (itemIndex !== undefined) {
this.attachSelectionListener(itemIndex);
}
}, },
attachSelectionListener(index) { itemIsInCurrentSelection(item) {
let path = `configuration.items[${index}].useGrid`; return this.selection.some(selectionPath =>
this.removeSelectionListener = this.openmct.objects.observe(this.internalDomainObject, path, function (value) { selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id);
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));
}, },
bypassSelection($event) { bypassSelection($event) {
if (this.dragInProgress) { if (this.dragInProgress) {
if ($event) { if ($event) {
$event.stopImmediatePropagation(); $event.stopImmediatePropagation();
} }
this.dragInProgress = false;
return; return;
} }
}, },
endDrag(item, updates) { endLineResize(item, updates) {
this.dragInProgress = true; this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
let index = this.layoutItems.indexOf(item); let index = this.layoutItems.indexOf(item);
Object.assign(item, updates); Object.assign(item, updates);
this.mutate(`configuration.items[${index}]`, item); 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) { mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value); this.openmct.objects.mutate(this.internalDomainObject, path, value);
}, },
@ -313,11 +414,15 @@
this.objectViewMap[keyString] = true; this.objectViewMap[keyString] = true;
} }
}, },
removeItem(item, index) { removeItem(selectedItems) {
let indices = [];
this.initSelectIndex = -1; 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.mutate("configuration.items", this.layoutItems);
this.untrackItem(item);
this.$el.click(); this.$el.click();
}, },
untrackItem(item) { untrackItem(item) {
@ -383,20 +488,74 @@
this.mutate("configuration.items", layoutItems); this.mutate("configuration.items", layoutItems);
this.$el.click(); this.$el.click();
}, },
orderItem(position, index) { orderItem(position, selectedItems) {
let delta = ORDERS[position]; let delta = ORDERS[position];
let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0); let indices = [];
let item = this.layoutItems[index]; let newIndex = -1;
let items = [];
if (newIndex !== index) { Object.assign(items, this.layoutItems);
this.layoutItems.splice(index, 1); this.selectedLayoutItems.forEach(selectedItem => {
this.layoutItems.splice(newIndex, 0, item); indices.push(this.layoutItems.indexOf(selectedItem));
this.mutate('configuration.items', this.layoutItems); });
indices.sort((a, b) => a - b);
if (this.removeSelectionListener) { if (position === 'top' || position === 'up') {
this.removeSelectionListener(); indices.reverse();
this.attachSelectionListener(newIndex);
} }
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]);
} }
} }
}, },
@ -412,14 +571,10 @@
this.composition.load(); this.composition.load();
}, },
destroyed: function () { destroyed: function () {
this.openmct.off('change', this.setSelection); this.openmct.selection.off('change', this.setSelection);
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.unlisten(); this.unlisten();
if (this.removeSelectionListener) {
this.removeSelectionListener();
}
} }
} }
</script> </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> <template>
<layout-frame :item="item" <layout-frame :item="item"
:grid-size="gridSize" :grid-size="gridSize"
@endDrag="(item, updates) => $emit('endDrag', item, updates)"> @move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-image-view" <div class="c-image-view"
:style="style"> :style="style">
</div> </div>
@ -56,8 +57,7 @@
y: 1, y: 1,
width: 10, width: 10,
height: 5, height: 5,
url: element.url, url: element.url
useGrid: true
}; };
}, },
inject: ['openmct'], inject: ['openmct'],

View File

@ -24,25 +24,14 @@
<div class="l-layout__frame c-frame" <div class="l-layout__frame c-frame"
:class="{ :class="{
'no-frame': !item.hasFrame, 'no-frame': !item.hasFrame,
'u-inspectable': inspectable, 'u-inspectable': inspectable
'is-resizing': isResizing
}" }"
:style="style"> :style="style">
<slot></slot> <slot></slot>
<!-- Drag handles -->
<div class="c-frame-edit">
<div class="c-frame-edit__move" <div class="c-frame-edit__move"
@mousedown="startDrag([1,1], [0,0], $event, 'move')"></div> @mousedown="startMove([1,1], [0,0], $event)">
<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> </div>
</div> </div>
</template> </template>
@ -50,7 +39,7 @@
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base"; @import "~styles/sass-base";
/******************************* FRAME */ /******************* FRAME */
.c-frame { .c-frame {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -59,83 +48,74 @@
> *:first-child { > *:first-child {
flex: 1 1 auto; flex: 1 1 auto;
} }
&:not(.no-frame) {
background: $colorBodyBg;
border: $browseFrameBorder;
padding: $interiorMargin;
} }
} .c-frame-edit__move {
.c-frame-edit {
// In Layouts, this is the editing rect and handles
// In Fixed Position, this is a wrapper element
@include abs();
display: none; display: none;
}
&__move { .is-editing {
@include abs(); /******************* STYLES FOR C-FRAME WHILE EDITING */
.c-frame {
&:not([s-selected]) {
&:hover {
border: $editFrameBorderHov;
}
}
&[s-selected] {
// All frames selected while editing
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw;
.c-frame-edit__move {
cursor: move; 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 { /******************* DEFAULT STYLES FOR -EDIT__MOVE */
// Target frames that hold domain objects that include header elements, as opposed to drawing and alpha objects // All object types
// Make the __move element a more affordable drag UI element
.c-frame-edit__move { .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(); @include userSelectNone();
background: $editFrameMovebarColorBg; background: $editFrameMovebarColorBg;
box-shadow: rgba(black, 0.2) 0 1px; box-shadow: rgba(black, 0.2) 0 1px;
bottom: auto; bottom: auto;
height: 0; // Height is set on hover on s-selected.c-frame display: block;
height: 0; // Height is set on hover below
opacity: 0.8; opacity: 0.8;
max-height: 100%; max-height: 100%;
overflow: hidden; overflow: hidden;
@ -150,75 +130,48 @@
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
top: $tbOffset; right: $lrOffset; bottom: $tbOffset; left: $lrOffset; top: $tbOffset;
right: $lrOffset;
bottom: $tbOffset;
left: $lrOffset;
}
}
} }
&:hover { &:hover {
background: $editFrameHovMovebarColorBg; > .c-so-view.has-complex-content {
&:before { @include grippy($editFrameHovMovebarColorFg); } 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;
}
}
} }
} }
} }
.is-editing { /******************* > 1 ITEMS SELECTED */
.c-frame { &.is-multi-selected {
$moveBarOutDelay: 500ms; .l-layout__frame[s-selected] {
&.no-frame { > .c-so-view.has-complex-content + .c-frame-edit__move {
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;
}
}
&[s-selected] {
// All frames selected while editing
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw;
> .c-frame-edit {
[class*='__handle'] {
display: block; 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> </style>
<script> <script>
import LayoutDrag from './../LayoutDrag' import LayoutDrag from './../LayoutDrag'
@ -228,21 +181,9 @@
item: Object, item: Object,
gridSize: Array gridSize: Array
}, },
data() {
return {
dragPosition: undefined,
isResizing: undefined
}
},
computed: { computed: {
style() { style() {
let {x, y, width, height} = this.item; let {x, y, width, height} = this.item;
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
}
return { return {
left: (this.gridSize[0] * x) + 'px', left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px', top: (this.gridSize[1] * y) + 'px',
@ -264,36 +205,40 @@
return value - this.initialPosition[index]; return value - this.initialPosition[index];
}.bind(this)); }.bind(this));
}, },
startDrag(posFactor, dimFactor, event, type) { startMove(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueDrag); document.body.addEventListener('mousemove', this.continueMove);
document.body.addEventListener('mouseup', this.endDrag); document.body.addEventListener('mouseup', this.endMove);
this.dragPosition = { this.dragPosition = {
position: [this.item.x, this.item.y], position: [this.item.x, this.item.y]
dimensions: [this.item.width, this.item.height]
}; };
this.updatePosition(event); this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize); this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
this.isResizing = type === 'resize';
event.preventDefault(); event.preventDefault();
}, },
continueDrag(event) { continueMove(event) {
event.preventDefault(); event.preventDefault();
this.updatePosition(event); 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) { endMove(event) {
document.body.removeEventListener('mousemove', this.continueDrag); document.body.removeEventListener('mousemove', this.continueMove);
document.body.removeEventListener('mouseup', this.endDrag); document.body.removeEventListener('mouseup', this.endMove);
this.continueDrag(event); this.continueMove(event);
let [x, y] = this.dragPosition.position; this.$emit('endMove');
let [width, height] = this.dragPosition.dimensions;
this.$emit('endDrag', this.item, {x, y, width, height});
this.dragPosition = undefined; this.dragPosition = undefined;
this.initialPosition = undefined; this.initialPosition = undefined;
this.delta = undefined; this.delta = undefined;
this.isResizing = undefined;
event.preventDefault(); event.preventDefault();
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
} }
} }
} }

View File

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

View File

@ -22,7 +22,9 @@
<template> <template>
<layout-frame :item="item" <layout-frame :item="item"
:grid-size="gridSize" :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" <object-frame v-if="domainObject"
:domain-object="domainObject" :domain-object="domainObject"
:object-path="objectPath" :object-path="objectPath"
@ -66,8 +68,7 @@
x: position[0], x: position[0],
y: position[1], y: position[1],
identifier: domainObject.identifier, identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type), hasFrame: hasFrameByDefault(domainObject.type)
useGrid: true
}; };
}, },
inject: ['openmct'], inject: ['openmct'],

View File

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

View File

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

View File

@ -60,6 +60,7 @@ export default function DisplayLayoutPlugin(options) {
getSelectionContext() { getSelectionContext() {
return { return {
item: domainObject, item: domainObject,
supportsMultiSelect: true,
addElement: component && component.$refs.displayLayout.addElement, addElement: component && component.$refs.displayLayout.addElement,
removeItem: component && component.$refs.displayLayout.removeItem, removeItem: component && component.$refs.displayLayout.removeItem,
orderItem: component && component.$refs.displayLayout.orderItem orderItem: component && component.$refs.displayLayout.orderItem

View File

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

View File

@ -69,16 +69,17 @@ export default {
} }
} else { } else {
if (!this.updatedFilters[key]) { 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); this.$emit('updateFilters', this.keyString, this.updatedFilters);
}, },
updateTextFilter(key, comparator, value) { updateTextFilter(key, comparator, value) {
if (!this.updatedFilters[key]) { if (!this.updatedFilters[key]) {
this.updatedFilters[key] = {}; this.$set(this.updatedFilters, key, {});
this.$set(this.updatedFilters[key], comparator, '');
} }
this.updatedFilters[key][comparator] = value; this.updatedFilters[key][comparator] = value;
this.$emit('updateFilters', this.keyString, this.updatedFilters); this.$emit('updateFilters', this.keyString, this.updatedFilters);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
<template>
<div class="c-indicator c-indicator--clickable icon-session">
<span class="label">
<button @click="globalClearEmit">Clear All Data</button>
</span>
</div>
</template>
<script>
export default {
inject: ['openmct'],
methods: {
globalClearEmit() {
this.openmct.notifications.emit('clear');
}
}
}
</script>

View File

@ -0,0 +1,48 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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([
'./components/globalClearIndicator.vue',
'vue'
], function (
GlobaClearIndicator,
Vue
) {
return function plugin() {
return function install(openmct) {
let component = new Vue ({
provide: {
openmct
},
components: {
GlobalClearIndicator: GlobaClearIndicator.default
},
template: '<GlobalClearIndicator></GlobalClearIndicator>'
}),
indicator = {
element: component.$mount().$el
};
openmct.indicators.add(indicator);
};
};
});

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

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

View File

@ -64,37 +64,45 @@ define([
Object.keys(panels).forEach(key => { Object.keys(panels).forEach(key => {
let panel = panels[key]; let panel = panels[key];
let domainObject = childObjects[key]; let domainObject = childObjects[key];
let identifier = undefined;
if (isTelemetry(domainObject)) { if (isTelemetry(domainObject)) {
items.push({ // If object is a telemetry point, convert it to a plot and
width: panel.dimensions[0], // replace the object in migratedObject composition with the plot.
height: panel.dimensions[1], identifier = {
x: panel.position[0], key: uuid(),
y: panel.position[1], namespace: migratedObject.identifier.namespace
useGrid: true, };
identifier: domainObject.identifier, let plotObject = {
id: uuid(), identifier: identifier,
type: 'telemetry-view', location: domainObject.location,
displayMode: 'all', name: domainObject.name,
value: openmct.telemetry.getMetadata(domainObject).getDefaultDisplayValue(), type: "telemetry.plot.overlay"
stroke: "transparent", };
fill: "", let plotType = openmct.types.get('telemetry.plot.overlay');
color: "", plotType.definition.initialize(plotObject);
size: "13px" 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;
}
}); });
} else { }
items.push({ items.push({
width: panel.dimensions[0], width: panel.dimensions[0],
height: panel.dimensions[1], height: panel.dimensions[1],
x: panel.position[0], x: panel.position[0],
y: panel.position[1], y: panel.position[1],
useGrid: true, identifier: identifier || domainObject.identifier,
identifier: domainObject.identifier,
id: uuid(), id: uuid(),
type: 'subobject-view', type: 'subobject-view',
hasFrame: panel.hasFrame hasFrame: panel.hasFrame
}); });
}
}); });
migratedObject.configuration.items = items; migratedObject.configuration.items = items;
@ -104,7 +112,7 @@ define([
return migratedObject; return migratedObject;
} }
function migrateFixedPositionConfiguration(elements, telemetryObjects) { function migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize) {
const DEFAULT_STROKE = "transparent"; const DEFAULT_STROKE = "transparent";
const DEFAULT_SIZE = "13px"; const DEFAULT_SIZE = "13px";
const DEFAULT_COLOR = ""; const DEFAULT_COLOR = "";
@ -117,10 +125,16 @@ define([
y: element.y, y: element.y,
width: element.width, width: element.width,
height: element.height, height: element.height,
useGrid: element.useGrid,
id: uuid() 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") { if (element.type === "fixed.telemetry") {
item.type = "telemetry-view"; item.type = "telemetry-view";
item.stroke = element.stroke || DEFAULT_STROKE; item.stroke = element.stroke || DEFAULT_STROKE;
@ -163,7 +177,9 @@ define([
return [ return [
{ {
check(domainObject) { check(domainObject) {
return domainObject.type === 'layout' && domainObject.configuration.layout; return domainObject.type === 'layout' &&
domainObject.configuration &&
domainObject.configuration.layout;
}, },
migrate(domainObject) { migrate(domainObject) {
let childObjects = {}; let childObjects = {};
@ -182,7 +198,9 @@ define([
}, },
{ {
check(domainObject) { check(domainObject) {
return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display']; return domainObject.type === 'telemetry.fixed' &&
domainObject.configuration &&
domainObject.configuration['fixed-display'];
}, },
migrate(domainObject) { migrate(domainObject) {
const DEFAULT_GRID_SIZE = [64, 16]; const DEFAULT_GRID_SIZE = [64, 16];
@ -192,10 +210,11 @@ define([
name: domainObject.name, name: domainObject.name,
type: "layout" type: "layout"
}; };
let gridSize = domainObject.layoutGrid || DEFAULT_GRID_SIZE;
let layoutType = openmct.types.get('layout'); let layoutType = openmct.types.get('layout');
layoutType.definition.initialize(newLayoutObject); layoutType.definition.initialize(newLayoutObject);
newLayoutObject.composition = domainObject.composition; newLayoutObject.composition = domainObject.composition;
newLayoutObject.configuration.layoutGrid = domainObject.layoutGrid || DEFAULT_GRID_SIZE; newLayoutObject.configuration.layoutGrid = gridSize;
let elements = domainObject.configuration['fixed-display'].elements; let elements = domainObject.configuration['fixed-display'].elements;
let telemetryObjects = {}; let telemetryObjects = {};
@ -211,7 +230,7 @@ define([
return Promise.all(promises) return Promise.all(promises)
.then(function () { .then(function () {
newLayoutObject.configuration.items = newLayoutObject.configuration.items =
migrateFixedPositionConfiguration(elements, telemetryObjects); migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize);
return newLayoutObject; return newLayoutObject;
}); });
} }
@ -219,6 +238,7 @@ define([
{ {
check(domainObject) { check(domainObject) {
return domainObject.type === 'table' && return domainObject.type === 'table' &&
domainObject.configuration &&
domainObject.configuration.table; domainObject.configuration.table;
}, },
migrate(domainObject) { migrate(domainObject) {

View File

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

View File

@ -115,11 +115,13 @@ define([
Collection.prototype.remove = function (model) { Collection.prototype.remove = function (model) {
var index = this.indexOf(model); var index = this.indexOf(model);
if (index === -1) { if (index === -1) {
throw new Error('model not found in collection.'); throw new Error('model not found in collection.');
} }
this.models.splice(index, 1);
this.emit('remove', model, index); this.emit('remove', model, index);
this.models.splice(index, 1);
}; };
Collection.prototype.destroy = function (model) { Collection.prototype.destroy = function (model) {

View File

@ -377,6 +377,19 @@ define([
delete this.unsubscribe; delete this.unsubscribe;
} }
this.fetch(); this.fetch();
},
/**
* Clears the plot series, unsubscribes and resubscribes
* @public
*/
refresh: function () {
this.reset();
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
this.fetch();
} }
}); });

View File

@ -100,20 +100,34 @@ define([
removeTelemetryObject: function (identifier) { removeTelemetryObject: function (identifier) {
var plotObject = this.plot.get('domainObject'); var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') { if (plotObject.type === 'telemetry.plot.overlay') {
var index = _.findIndex(plotObject.configuration.series, function (s) {
var persistedIndex = _.findIndex(plotObject.configuration.series, function (s) {
return _.isEqual(identifier, s.identifier); return _.isEqual(identifier, s.identifier);
}); });
this.remove(this.at(index));
var configIndex = _.findIndex(this.models, function (m) {
return _.isEqual(m.domainObject.identifier, identifier);
});
/*
when cancelling out of edit mode, the config store and domain object are out of sync
thus it is necesarry to check both and remove the models that are no longer in composition
*/
if (persistedIndex === -1) {
this.remove(this.at(configIndex));
} else {
this.remove(this.at(persistedIndex));
// Because this is triggered by a composition change, we have // Because this is triggered by a composition change, we have
// to defer mutation of our plot object, otherwise we might // to defer mutation of our plot object, otherwise we might
// mutate an outdated version of the plotObject. // mutate an outdated version of the plotObject.
setTimeout(function () { setTimeout(function () {
var newPlotObject = this.plot.get('domainObject'); var newPlotObject = this.plot.get('domainObject');
var cSeries = newPlotObject.configuration.series.slice(); var cSeries = newPlotObject.configuration.series.slice();
cSeries.splice(index, 1); cSeries.splice(persistedIndex, 1);
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries); this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
}.bind(this)); }.bind(this));
} }
}
}, },
onSeriesAdd: function (series) { onSeriesAdd: function (series) {
var seriesColor = series.get('color'); var seriesColor = series.get('color');

View File

@ -25,23 +25,11 @@ define([
function ConfigStore() { function ConfigStore() {
this.store = {}; this.store = {};
this.tracking = {};
} }
ConfigStore.prototype.track = function (id) { ConfigStore.prototype.deleteStore = function (id) {
if (!this.tracking[id]) {
this.tracking[id] = 0;
}
this.tracking[id] += 1;
};
ConfigStore.prototype.untrack = function (id) {
this.tracking[id] -= 1;
if (this.tracking[id] <= 0) {
delete this.tracking[id];
this.store[id].destroy(); this.store[id].destroy();
delete this.store[id]; delete this.store[id];
}
}; };
ConfigStore.prototype.add = function (id, config) { ConfigStore.prototype.add = function (id, config) {

View File

@ -49,7 +49,6 @@ define([
}; };
PlotOptionsController.prototype.destroy = function () { PlotOptionsController.prototype.destroy = function () {
configStore.untrack(this.configId);
this.stopListening(); this.stopListening();
this.unlisten(); this.unlisten();
}; };
@ -60,7 +59,7 @@ define([
this.$timeout(this.setUpScope.bind(this)); this.$timeout(this.setUpScope.bind(this));
return; return;
} }
configStore.track(this.configId);
this.config = this.$scope.config = config; this.config = this.$scope.config = config;
this.$scope.plotSeries = []; this.$scope.plotSeries = [];
@ -70,12 +69,15 @@ define([
this.listenTo(this.$scope, '$destroy', this.destroy, this); this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.listenTo(config.series, 'add', this.addSeries, this); this.listenTo(config.series, 'add', this.addSeries, this);
this.listenTo(config.series, 'remove', this.resetAllSeries, this); this.listenTo(config.series, 'remove', this.resetAllSeries, this);
config.series.forEach(this.addSeries, this); config.series.forEach(this.addSeries, this);
}; };
PlotOptionsController.prototype.addSeries = function (series, index) { PlotOptionsController.prototype.addSeries = function (series, index) {
this.$timeout(function () {
this.$scope.plotSeries[index] = series; this.$scope.plotSeries[index] = series;
series.locateOldObject(this.$scope.domainObject); series.locateOldObject(this.$scope.domainObject);
}.bind(this));
}; };
PlotOptionsController.prototype.resetAllSeries = function (series, index) { PlotOptionsController.prototype.resetAllSeries = function (series, index) {

View File

@ -282,11 +282,19 @@ define([
}; };
MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) { MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) {
var currentXaxis = this.$scope.xAxis.get('displayRange'),
currentYaxis = this.$scope.yAxis.get('displayRange');
// when there is no plot data, the ranges can be undefined
// in which case we should not perform zoom
if (!currentXaxis || !currentYaxis) {
return;
}
this.freeze(); this.freeze();
this.trackHistory(); this.trackHistory();
var currentXaxis = this.$scope.xAxis.get('displayRange'),
currentYaxis = this.$scope.yAxis.get('displayRange'), var xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor; yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor;
if (zoomDirection === 'in') { if (zoomDirection === 'in') {
@ -322,12 +330,19 @@ define([
return; return;
} }
let xDisplayRange = this.$scope.xAxis.get('displayRange'),
yDisplayRange = this.$scope.yAxis.get('displayRange');
// when there is no plot data, the ranges can be undefined
// in which case we should not perform zoom
if (!xDisplayRange || !yDisplayRange) {
return;
}
this.freeze(); this.freeze();
window.clearTimeout(this.stillZooming); window.clearTimeout(this.stillZooming);
let xDisplayRange = this.$scope.xAxis.get('displayRange'), let xAxisDist = (xDisplayRange.max - xDisplayRange.min),
yDisplayRange = this.$scope.yAxis.get('displayRange'),
xAxisDist = (xDisplayRange.max - xDisplayRange.min),
yAxisDist = (yDisplayRange.max - yDisplayRange.min), yAxisDist = (yDisplayRange.max - yDisplayRange.min),
xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x, xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x,
xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min, xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min,

View File

@ -80,6 +80,10 @@ define([
'configuration.filters', 'configuration.filters',
this.updateFiltersAndResubscribe.bind(this) this.updateFiltersAndResubscribe.bind(this)
); );
this.refresh = this.refresh.bind(this);
this.openmct.notifications.on('clear', this.refresh);
} }
eventHelpers.extend(PlotController.prototype); eventHelpers.extend(PlotController.prototype);
@ -148,7 +152,6 @@ define([
}); });
configStore.add(configId, config); configStore.add(configId, config);
} }
configStore.track(configId);
return config; return config;
}; };
@ -157,7 +160,8 @@ define([
}; };
PlotController.prototype.destroy = function () { PlotController.prototype.destroy = function () {
configStore.untrack(this.config.id); configStore.deleteStore(this.config.id);
this.stopListening(); this.stopListening();
if (this.checkForSize) { if (this.checkForSize) {
clearInterval(this.checkForSize); clearInterval(this.checkForSize);
@ -166,6 +170,8 @@ define([
if (this.filterObserver) { if (this.filterObserver) {
this.filterObserver(); this.filterObserver();
} }
this.openmct.notifications.off('clear', this.refresh);
}; };
PlotController.prototype.loadMoreData = function (range, purge) { PlotController.prototype.loadMoreData = function (range, purge) {
@ -263,6 +269,12 @@ define([
}); });
}; };
PlotController.prototype.refresh = function (updatedFilters) {
this.config.series.forEach(function (series) {
series.refresh();
});
};
/** /**
* Export view as JPG. * Export view as JPG.
*/ */

View File

@ -79,6 +79,15 @@ define([
$scope.$broadcast('plot:tickWidth', _.max(tickWidthMap)); $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; thisRequest.pending += 1;
openmct.objects.get(domainObject.getId()) openmct.objects.get(domainObject.getId())
.then(function (obj) { .then(function (obj) {
@ -89,10 +98,12 @@ define([
composition = openmct.composition.get(obj); composition = openmct.composition.get(obj);
composition.on('add', addChild); composition.on('add', addChild);
composition.on('remove', removeChild); composition.on('remove', removeChild);
composition.on('reorder', compositionReorder);
composition.load(); composition.load();
unlisten = function () { unlisten = function () {
composition.off('add', addChild); composition.off('add', addChild);
composition.off('remove', removeChild); composition.off('remove', removeChild);
composition.off('reorder', compositionReorder);
}; };
}); });
} }

View File

@ -23,6 +23,7 @@
define([ define([
'lodash', 'lodash',
'./utcTimeSystem/plugin', './utcTimeSystem/plugin',
'./localTimeSystem/plugin',
'../../example/generator/plugin', '../../example/generator/plugin',
'./autoflow/AutoflowTabularPlugin', './autoflow/AutoflowTabularPlugin',
'./timeConductor/plugin', './timeConductor/plugin',
@ -41,10 +42,13 @@ define([
'./tabs/plugin', './tabs/plugin',
'./LADTable/plugin', './LADTable/plugin',
'./filters/plugin', './filters/plugin',
'./objectMigration/plugin' './objectMigration/plugin',
'./goToOriginalAction/plugin',
'./globalClearIndicator/plugin'
], function ( ], function (
_, _,
UTCTimeSystem, UTCTimeSystem,
LocalTimeSystem,
GeneratorPlugin, GeneratorPlugin,
AutoflowPlugin, AutoflowPlugin,
TimeConductorPlugin, TimeConductorPlugin,
@ -63,7 +67,9 @@ define([
Tabs, Tabs,
LADTable, LADTable,
Filters, Filters,
ObjectMigration ObjectMigration,
GoToOriginalAction,
GlobalClearIndicator
) { ) {
var bundleMap = { var bundleMap = {
LocalStorage: 'platform/persistence/local', LocalStorage: 'platform/persistence/local',
@ -79,6 +85,7 @@ define([
}); });
plugins.UTCTimeSystem = UTCTimeSystem; plugins.UTCTimeSystem = UTCTimeSystem;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.ImportExport = ImportExport; plugins.ImportExport = ImportExport;
@ -160,6 +167,8 @@ define([
plugins.LADTable = LADTable; plugins.LADTable = LADTable;
plugins.Filters = Filters; plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default; plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default;
plugins.GlobalClearIndicator = GlobalClearIndicator;
return plugins; return plugins;
}); });

View File

@ -85,6 +85,10 @@ export default class RemoveAction {
); );
this.openmct.objects.mutate(parent, 'composition', composition); this.openmct.objects.mutate(parent, 'composition', composition);
if (this.inNavigationPath(child) && this.openmct.editor.isEditing()) {
this.openmct.editor.save();
}
} }
appliesTo(objectPath) { appliesTo(objectPath) {

View File

@ -70,17 +70,15 @@ define([
*/ */
function onValueInput(event) { function onValueInput(event) {
var elem = event.target, var elem = event.target,
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber), value = isNaN(Number(elem.value)) ? elem.value : Number(elem.value),
inputIndex = self.valueInputs.indexOf(elem); inputIndex = self.valueInputs.indexOf(elem);
if (elem.tagName.toUpperCase() === 'INPUT') {
self.eventEmitter.emit('change', { self.eventEmitter.emit('change', {
value: value, value: value,
property: 'values[' + inputIndex + ']', property: 'values[' + inputIndex + ']',
index: self.index index: self.index
}); });
} }
}
this.listenTo(this.deleteButton, 'click', this.remove, this); this.listenTo(this.deleteButton, 'click', this.remove, this);
this.listenTo(this.duplicateButton, 'click', this.duplicate, this); this.listenTo(this.duplicateButton, 'click', this.duplicate, this);
@ -108,8 +106,7 @@ define([
Object.values(this.selects).forEach(function (select) { Object.values(this.selects).forEach(function (select) {
$('.t-configuration', self.domElement).append(select.getDOM()); $('.t-configuration', self.domElement).append(select.getDOM());
}); });
this.listenTo($('.t-value-inputs', this.domElement), 'input', onValueInput);
this.listenTo($(this.domElement), 'input', onValueInput);
} }
Condition.prototype.getDOM = function (container) { Condition.prototype.getDOM = function (container) {
@ -167,7 +164,9 @@ define([
/** /**
* When an operation is selected, create the appropriate value inputs * When an operation is selected, create the appropriate value inputs
* and add them to the view * and add them to the view. If an operation is of type enum, create
* a drop-down menu instead.
*
* @param {string} operation The key of currently selected operation * @param {string} operation The key of currently selected operation
*/ */
Condition.prototype.generateValueInputs = function (operation) { Condition.prototype.generateValueInputs = function (operation) {
@ -176,24 +175,48 @@ define([
inputCount, inputCount,
inputType, inputType,
newInput, newInput,
index = 0; index = 0,
emitChange = false;
inputArea.html(''); inputArea.html('');
this.valueInputs = []; this.valueInputs = [];
this.config.values = [];
if (evaluator.getInputCount(operation)) { if (evaluator.getInputCount(operation)) {
inputCount = evaluator.getInputCount(operation); inputCount = evaluator.getInputCount(operation);
inputType = evaluator.getInputType(operation); inputType = evaluator.getInputType(operation);
while (index < inputCount) { while (index < inputCount) {
if (!this.config.values[index]) { if (inputType === 'select') {
this.config.values[index] = (inputType === 'number' ? 0 : ''); newInput = $('<select>' + this.generateSelectOptions() + '</select>');
} emitChange = true;
} else {
this.config.values[index] = inputType === 'number' ? 0 : '';
newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>'); newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
}
this.valueInputs.push(newInput.get(0)); this.valueInputs.push(newInput.get(0));
inputArea.append(newInput); inputArea.append(newInput);
index += 1; index += 1;
} }
if (emitChange) {
this.eventEmitter.emit('change', {
value: Number(newInput[0].options[0].value),
property: 'values[0]',
index: this.index
});
} }
}
};
Condition.prototype.generateSelectOptions = function () {
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
let options = '';
telemetryMetadata[this.config.key].enumerations.forEach(enumeration => {
options += '<option value="' + enumeration.value + '">'+ enumeration.string + '</option>';
});
return options;
}; };
return Condition; return Condition;

View File

@ -24,7 +24,8 @@ define([], function () {
*/ */
this.inputTypes = { this.inputTypes = {
number: 'number', number: 'number',
string: 'text' string: 'text',
enum: 'select'
}; };
/** /**
@ -34,7 +35,8 @@ define([], function () {
*/ */
this.inputValidators = { this.inputValidators = {
number: this.validateNumberInput, number: this.validateNumberInput,
string: this.validateStringInput string: this.validateStringInput,
enum: this.validateNumberInput
}; };
/** /**
@ -201,7 +203,7 @@ define([], function () {
return typeof input[0] === 'undefined'; return typeof input[0] === 'undefined';
}, },
text: 'is undefined', text: 'is undefined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is undefined'; return ' is undefined';
@ -212,11 +214,33 @@ define([], function () {
return typeof input[0] !== 'undefined'; return typeof input[0] !== 'undefined';
}, },
text: 'is defined', text: 'is defined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is defined'; return ' is defined';
} }
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
} }
}; };
} }
@ -310,13 +334,16 @@ define([], function () {
validator; validator;
if (cache[object] && typeof cache[object][key] !== 'undefined') { if (cache[object] && typeof cache[object][key] !== 'undefined') {
telemetryValue = [cache[object][key]]; let value = cache[object][key];
telemetryValue = [isNaN(Number(value)) ? value : Number(value)];
} }
op = this.operations[operation] && this.operations[operation].operation; op = this.operations[operation] && this.operations[operation].operation;
input = telemetryValue && telemetryValue.concat(values); input = telemetryValue && telemetryValue.concat(values);
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]]; validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
if (op && input && validator) { if (op && input && validator) {
if (this.operations[operation].appliesTo.length === 2) { if (this.operations[operation].appliesTo.length > 1) {
return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input); return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input);
} else { } else {
return validator(input) && op(input); return validator(input) && op(input);
@ -372,7 +399,7 @@ define([], function () {
}; };
/** /**
* Returns true only of the given operation applies to a given type * Returns true only if the given operation applies to a given type
* @param {string} key The key of the operation * @param {string} key The key of the operation
* @param {string} type The value type to query * @param {string} type The value type to query
* @returns {boolean} True if the condition applies, false otherwise * @returns {boolean} True if the condition applies, false otherwise

View File

@ -130,7 +130,9 @@ define ([
this.telemetryTypesById[objectId] = {}; this.telemetryTypesById[objectId] = {};
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) { Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
var type; var type;
if (valueMetadata.hints.hasOwnProperty('range')) { if (valueMetadata.enumerations !== undefined) {
type = 'enum';
} else if (valueMetadata.hints.hasOwnProperty('range')) {
type = 'number'; type = 'number';
} else if (valueMetadata.hints.hasOwnProperty('domain')) { } else if (valueMetadata.hints.hasOwnProperty('domain')) {
type = 'number'; type = 'number';
@ -163,11 +165,18 @@ define ([
* @param {datum} datum The new data from the telemetry source * @param {datum} datum The new data from the telemetry source
* @private * @private
*/ */
ConditionManager.prototype.handleSubscriptionCallback = function (objId, datum) { ConditionManager.prototype.handleSubscriptionCallback = function (objId, telemetryDatum) {
this.subscriptionCache[objId] = datum; this.subscriptionCache[objId] = this.createNormalizedDatum(objId, telemetryDatum);
this.eventEmitter.emit('receiveTelemetry'); this.eventEmitter.emit('receiveTelemetry');
}; };
ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDatum) {
return Object.values(this.telemetryMetadataById[objId]).reduce((normalizedDatum, metadatum) => {
normalizedDatum[metadatum.key] = telemetryDatum[metadatum.source];
return normalizedDatum;
}, {});
};
/** /**
* Event handler for an add event in this Summary Widget's composition. * Event handler for an add event in this Summary Widget's composition.
* Sets up subscription handlers and parses its property types. * Sets up subscription handlers and parses its property types.
@ -236,7 +245,9 @@ define ([
id.namespace === identifier.namespace; id.namespace === identifier.namespace;
}); });
delete this.compositionObjs[objectId]; delete this.compositionObjs[objectId];
delete this.subscriptionCache[objectId];
this.subscriptions[objectId](); //unsubscribe from telemetry source this.subscriptions[objectId](); //unsubscribe from telemetry source
delete this.subscriptions[objectId];
this.eventEmitter.emit('remove', identifier); this.eventEmitter.emit('remove', identifier);
if (_.isEmpty(this.compositionObjs)) { if (_.isEmpty(this.compositionObjs)) {

View File

@ -110,9 +110,11 @@ define([
type = self.manager.getTelemetryPropertyType(self.config.object, key); type = self.manager.getTelemetryPropertyType(self.config.object, key);
if (type !== undefined) {
self.operationKeys = operations.filter(function (operation) { self.operationKeys = operations.filter(function (operation) {
return self.evaluator.operationAppliesTo(operation, type); return self.evaluator.operationAppliesTo(operation, type);
}); });
}
}; };
OperationSelect.prototype.destroy = function () { OperationSelect.prototype.destroy = function () {

View File

@ -38,7 +38,7 @@ define([
return this.openmct.time.getAllTimeSystems().map(function (ts, i) { return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
return { return {
key: ts.key, key: ts.key,
name: 'UTC', name: ts.name,
format: ts.timeFormat, format: ts.timeFormat,
hints: { hints: {
domain: i domain: i
@ -64,7 +64,7 @@ define([
// Generally safe assumption is that we have one domain per timeSystem. // Generally safe assumption is that we have one domain per timeSystem.
values: this.getDomains().concat([ values: this.getDomains().concat([
{ {
name: 'state', name: 'State',
key: 'state', key: 'state',
source: 'ruleIndex', source: 'ruleIndex',
format: 'enum', format: 'enum',

View File

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

View File

@ -174,7 +174,7 @@ define([
return typeof input[0] === 'undefined'; return typeof input[0] === 'undefined';
}, },
text: 'is undefined', text: 'is undefined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is undefined'; return ' is undefined';
@ -185,11 +185,33 @@ define([
return typeof input[0] !== 'undefined'; return typeof input[0] !== 'undefined';
}, },
text: 'is defined', text: 'is defined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is defined'; return ' is defined';
} }
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
} }
}; };

View File

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

View File

@ -7,7 +7,8 @@
}"> }">
<div class="c-drop-hint" <div class="c-drop-hint"
@drop="onDrop" @drop="onDrop"
ref="dropHint"> @dragenter="dragenter"
@dragleave="dragleave">
</div> </div>
<div class="c-tabs-view__empty-message" <div class="c-tabs-view__empty-message"
v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div> v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div>
@ -25,7 +26,7 @@
<div class="c-tabs-view__object-holder" <div class="c-tabs-view__object-holder"
v-for="(tab, index) in tabsList" v-for="(tab, index) in tabsList"
:key="index" :key="index"
:class="{'invisible': !isCurrent(tab)}"> :class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}">
<div v-if="currentTab" <div v-if="currentTab"
class="c-tabs-view__object-name l-browse-bar__object-name--w" class="c-tabs-view__object-name l-browse-bar__object-name--w"
:class="currentTab.type.definition.cssClass"> :class="currentTab.type.definition.cssClass">
@ -68,6 +69,14 @@
flex: 1 1 auto; flex: 1 1 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&--hidden {
height: 1000px;
width: 1000px;
position: absolute;
left: -9999px;
top: -9999px;
}
} }
&__object-name { &__object-name {
@ -78,7 +87,10 @@
} }
&__object { &__object {
display: flex;
flex-flow: column nowrap;
flex: 1 1 auto; flex: 1 1 auto;
height: 0; // Chrome 73 oveflow bug fix
} }
&__empty-message { &__empty-message {
@ -186,13 +198,6 @@ export default {
document.addEventListener('dragstart', this.dragstart); document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend); document.addEventListener('dragend', this.dragend);
let dropHint = this.$refs.dropHint;
if (dropHint) {
dropHint.addEventListener('dragenter', this.dragenter);
dropHint.addEventListener('dragleave', this.dragleave);
}
}, },
destroyed() { destroyed() {
this.composition.off('add', this.addItem); this.composition.off('add', this.addItem);
@ -201,12 +206,6 @@ export default {
document.removeEventListener('dragstart', this.dragstart); document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend); document.removeEventListener('dragend', this.dragend);
},
beforeDestroy() {
let dropHint = this.$refs.dropHint;
dropHint.removeEventListener('dragenter', this.dragenter);
dropHint.removeEventListener('dragleave', this.dragleave);
} }
} }
</script> </script>

View File

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

View File

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

View File

@ -62,6 +62,7 @@ define([
openmct.time.on('bounds', this.refreshData); openmct.time.on('bounds', this.refreshData);
openmct.time.on('timeSystem', this.refreshData); openmct.time.on('timeSystem', this.refreshData);
openmct.notifications.on('clear', this.refreshData);
} }
initialize() { initialize() {
@ -137,12 +138,17 @@ define([
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject); let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
return this.openmct.telemetry.request(telemetryObject, requestOptions) return this.openmct.telemetry.request(telemetryObject, requestOptions)
.then(telemetryData => { .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 keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString); let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows); this.boundedRows.add(telemetryRows);
}).finally(() => {
this.decrementOutstandingRequests(); this.decrementOutstandingRequests();
}); });
} }
@ -193,6 +199,10 @@ define([
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => { 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)); this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
}, subscribeOptions); }, subscribeOptions);
} }
@ -230,7 +240,8 @@ define([
this.filteredRows.destroy(); this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this); Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData); this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.on('timeSystem', this.refreshData); this.openmct.time.off('timeSystem', this.refreshData);
this.openmct.notifications.off('clear', this.refreshData);
if (this.filterObserver) { if (this.filterObserver) {
this.filterObserver(); this.filterObserver();
} }

View File

@ -31,7 +31,7 @@ define(
) { ) {
class BoundedTableRowCollection extends SortedTableRowCollection { class BoundedTableRowCollection extends SortedTableRowCollection {
constructor (openmct) { constructor(openmct) {
super(); super();
this.futureBuffer = new SortedTableRowCollection(); this.futureBuffer = new SortedTableRowCollection();
@ -46,12 +46,13 @@ define(
openmct.time.on('bounds', this.bounds); openmct.time.on('bounds', this.bounds);
} }
addOne (item) { addOne(item) {
let parsedValue = this.getValueForSortColumn(item);
// Insert into either in-bounds array, or the future buffer. // Insert into either in-bounds array, or the future buffer.
// Data in the future buffer will be re-evaluated for possible // Data in the future buffer will be re-evaluated for possible
// insertion on next bounds change // insertion on next bounds change
let beforeStartOfBounds = this.parseTime(item.datum[this.sortOptions.key]) < this.lastBounds.start; let beforeStartOfBounds = parsedValue < this.lastBounds.start;
let afterEndOfBounds = this.parseTime(item.datum[this.sortOptions.key]) > this.lastBounds.end; let afterEndOfBounds = parsedValue > this.lastBounds.end;
if (!afterEndOfBounds && !beforeStartOfBounds) { if (!afterEndOfBounds && !beforeStartOfBounds) {
return super.addOne(item); return super.addOne(item);
@ -86,7 +87,7 @@ define(
* @fires TelemetryCollection#discarded * @fires TelemetryCollection#discarded
* @param bounds * @param bounds
*/ */
bounds (bounds) { bounds(bounds) {
let startChanged = this.lastBounds.start !== bounds.start; let startChanged = this.lastBounds.start !== bounds.start;
let endChanged = this.lastBounds.end !== bounds.end; let endChanged = this.lastBounds.end !== bounds.end;
@ -135,9 +136,13 @@ define(
} }
} }
getValueForSortColumn(row) {
return this.parseTime(row.datum[this.sortOptions.key]);
}
destroy() { destroy() {
this.openmct.time.off('bounds', this.bounds); this.openmct.time.off('bounds', this.bounds);
} }
} }
return BoundedTableRowCollection; return BoundedTableRowCollection;
}); });

View File

@ -116,10 +116,9 @@ define(
return 0; return 0;
} }
const sortOptionsKey = this.sortOptions.key; const testRowValue = this.getValueForSortColumn(testRow);
const testRowValue = testRow.datum[sortOptionsKey]; const firstValue = this.getValueForSortColumn(this.rows[0]);
const firstValue = this.rows[0].datum[sortOptionsKey]; const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]);
const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey];
lodashFunction = lodashFunction || _.sortedIndex; lodashFunction = lodashFunction || _.sortedIndex;
@ -133,7 +132,7 @@ define(
return 0; return 0;
} else { } else {
return lodashFunction(rows, testRow, (thisRow) => { return lodashFunction(rows, testRow, (thisRow) => {
return thisRow.datum[sortOptionsKey]; return this.getValueForSortColumn(thisRow);
}); });
} }
} else { } else {
@ -147,7 +146,7 @@ define(
} else { } else {
// Use a custom comparison function to support descending sort. // Use a custom comparison function to support descending sort.
return lodashFunction(rows, testRow, (thisRow) => { return lodashFunction(rows, testRow, (thisRow) => {
const thisRowValue = thisRow.datum[sortOptionsKey]; const thisRowValue = this.getValueForSortColumn(thisRow);
if (testRowValue === thisRowValue) { if (testRowValue === thisRowValue) {
return EQUAL; return EQUAL;
} else if (testRowValue < thisRowValue) { } else if (testRowValue < thisRowValue) {
@ -218,25 +217,32 @@ define(
} }
return true; return true;
}); });
this.emit('remove', removed); this.emit('remove', removed);
} }
getValueForSortColumn(row) {
return row.datum[this.sortOptions.key];
}
remove(removedRows) { remove(removedRows) {
this.rows = this.rows.filter(row => { this.rows = this.rows.filter(row => {
return removedRows.indexOf(row) === -1; return removedRows.indexOf(row) === -1;
}); });
this.emit('remove', removedRows); this.emit('remove', removedRows);
} }
getRows () { getRows() {
return this.rows; return this.rows;
} }
clear() { clear() {
let removedRows = this.rows; let removedRows = this.rows;
this.rows = []; this.rows = [];
this.emit('remove', removedRows); this.emit('remove', removedRows);
} }
} }
return SortedTableRowCollection; return SortedTableRowCollection;
}); });

View File

@ -34,39 +34,13 @@
isSortable ? 'is-sortable' : '', isSortable ? 'is-sortable' : '',
isSortable && sortOptions.key === headerKey ? 'is-sorting' : '', isSortable && sortOptions.key === headerKey ? 'is-sorting' : '',
isSortable && sortOptions.direction].join(' ')"> 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" @mousedown="resizeColumnStart"
></div> ></div>
<slot></slot> <slot></slot>
</div> </div>
</th> </th>
</template> </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> <script>
import _ from 'lodash'; import _ from 'lodash';
const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex'; 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}"> <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"> <table class="c-table__headers c-telemetry-table__headers">
<thead> <thead>
<tr class="c-telemetry-table__headers__name"> <tr class="c-telemetry-table__headers__labels">
<table-column-header <table-column-header
v-for="(title, key, headerIndex) in headers" v-for="(title, key, headerIndex) in headers"
:key="key" :key="key"
@ -49,7 +49,8 @@
:columnWidth="columnWidths[key]" :columnWidth="columnWidths[key]"
:sortOptions="sortOptions" :sortOptions="sortOptions"
:isEditing="isEditing" :isEditing="isEditing"
>{{title}}</table-column-header> ><span class="c-telemetry-table__headers__label">{{title}}</span>
</table-column-header>
</tr> </tr>
<tr class="c-telemetry-table__headers__filter"> <tr class="c-telemetry-table__headers__filter">
<table-column-header <table-column-header
@ -77,7 +78,7 @@
<!-- Content table --> <!-- 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-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> <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'}"> :style="{ height: totalHeight + 'px'}">
<tbody> <tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows" <telemetry-table-row v-for="(row, rowIndex) in visibleRows"
@ -159,6 +160,32 @@
thead { thead {
display: block; 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 */ /******************************* ELEMENTS */
@ -221,7 +248,7 @@
/******************************* EDITING */ /******************************* EDITING */
.is-editing { .is-editing {
.c-telemetry-table__headers__name { .c-telemetry-table__headers__labels {
th[draggable], th[draggable],
th[draggable] > * { th[draggable] > * {
cursor: move; cursor: move;
@ -233,6 +260,10 @@
> * { background: $b; } > * { background: $b; }
} }
} }
.c-telemetry-table__resize-hitarea {
display: block;
}
} }
/******************************* LEGACY */ /******************************* LEGACY */
@ -253,7 +284,7 @@ import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100; const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17; const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200; const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20; const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
const RESIZE_HOT_ZONE = 10; const RESIZE_HOT_ZONE = 10;
const MOVE_TRIGGER_WAIT = 500; const MOVE_TRIGGER_WAIT = 500;
const VERTICAL_SCROLL_WIDTH = 30; const VERTICAL_SCROLL_WIDTH = 30;
@ -333,14 +364,15 @@ export default {
}, },
methods: { methods: {
updateVisibleRows() { updateVisibleRows() {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
let start = 0; let start = 0;
let end = VISIBLE_ROW_COUNT; let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows(); let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length; let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
if (filteredRowsLength < VISIBLE_ROW_COUNT) { if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength; end = filteredRowsLength;
} else { } else {
@ -362,13 +394,18 @@ export default {
} }
this.rowOffset = start; this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end); this.visibleRows = filteredRows.slice(start, end);
this.updatingView = false;
});
}
}, },
calculateFirstVisibleRow() { calculateFirstVisibleRow() {
return Math.floor(this.scrollable.scrollTop / this.rowHeight); let scrollTop = this.scrollable.scrollTop;
return Math.floor(scrollTop / this.rowHeight);
}, },
calculateLastVisibleRow() { calculateLastVisibleRow() {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight; let scrollBottom = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight); return Math.ceil(scrollBottom / this.rowHeight);
}, },
updateHeaders() { updateHeaders() {
this.headers = this.table.configuration.getVisibleHeaders(); this.headers = this.table.configuration.getVisibleHeaders();
@ -412,10 +449,7 @@ export default {
} }
this.table.sortBy(this.sortOptions); this.table.sortBy(this.sortOptions);
}, },
scroll() { scroll () {
if (!this.processingScroll) {
this.processingScroll = true;
requestAnimationFrame(()=> {
this.updateVisibleRows(); this.updateVisibleRows();
this.synchronizeScrollX(); this.synchronizeScrollX();
@ -426,57 +460,59 @@ export default {
// Auto-scroll will be re-enabled if user scrolls to bottom again. // Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false; this.autoScroll = false;
} }
this.processingScroll = false;
});
}
}, },
shouldSnapToBottom() { shouldSnapToBottom() {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT); return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
}, },
scrollToBottom() { scrollToBottom() {
this.scrollable.scrollTop = this.scrollable.scrollHeight; this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
}, },
synchronizeScrollX() { synchronizeScrollX() {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft; this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
}, },
filterChanged(columnKey) { filterChanged(columnKey) {
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]); this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
this.setHeight();
}, },
clearFilter(columnKey) { clearFilter(columnKey) {
this.filters[columnKey] = ''; this.filters[columnKey] = '';
this.table.filteredRows.setColumnFilter(columnKey, ''); this.table.filteredRows.setColumnFilter(columnKey, '');
this.setHeight();
}, },
rowsAdded(rows) { rowsAdded (rows) {
this.setHeight();
let sizingRow; let sizingRow;
if (Array.isArray(rows)) { if (Array.isArray(rows)) {
sizingRow = rows[0]; sizingRow = rows[0];
} else { } else {
sizingRow = rows; sizingRow = rows;
} }
if (!this.sizingRows[sizingRow.objectKeyString]) { if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow; this.sizingRows[sizingRow.objectKeyString] = sizingRow;
this.$nextTick().then(this.calculateColumnWidths); this.$nextTick().then(this.calculateColumnWidths);
} }
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
if (this.autoScroll) { if (this.autoScroll) {
this.$nextTick().then(this.scrollToBottom); this.scrollToBottom();
} }
this.updatingView = false;
});
}
},
rowsRemoved(rows) {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows(); 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() { exportAsCSV() {
const headerKeys = Object.keys(this.headers); const headerKeys = Object.keys(this.headers);
@ -557,13 +593,22 @@ export default {
let el = this.$el; let el = this.$el;
let width = el.clientWidth; let width = el.clientWidth;
let height = el.clientHeight; let height = el.clientHeight;
let scrollTop = this.scrollable.scrollTop;
this.resizePollHandle = setInterval(() => { this.resizePollHandle = setInterval(() => {
if ((el.clientWidth !== width || el.clientHeight !== height) && this.isAutosizeEnabled) { if ((el.clientWidth !== width || el.clientHeight !== height) && this.isAutosizeEnabled) {
this.calculateTableSize(); 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; width = el.clientWidth;
height = el.clientHeight; height = el.clientHeight;
} }
scrollTop = this.scrollable.scrollTop;
}, RESIZE_POLL_INTERVAL); }, RESIZE_POLL_INTERVAL);
}, },
@ -572,6 +617,9 @@ export default {
this.filterChanged = _.debounce(this.filterChanged, 500); this.filterChanged = _.debounce(this.filterChanged, 500);
}, },
mounted() { 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-added', this.addObject);
this.table.on('object-removed', this.removeObject); this.table.on('object-removed', this.removeObject);
@ -585,6 +633,7 @@ export default {
//Default sort //Default sort
this.sortOptions = this.table.filteredRows.sortBy(); this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w'); 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.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w'); this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');

View File

@ -20,7 +20,15 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['EventEmitter'], function (EventEmitter) { define(
[
'EventEmitter',
'lodash'
],
function (
EventEmitter,
_
) {
/** /**
* Manages selection state for Open MCT * Manages selection state for Open MCT
@ -47,21 +55,87 @@ define(['EventEmitter'], function (EventEmitter) {
* Selects the selectable object and emits the 'change' event. * Selects the selectable object and emits the 'change' event.
* *
* @param {object} selectable an object with element and context properties * @param {object} selectable an object with element and context properties
* @param {Boolean} isMultiSelectEvent flag indication shift key is pressed or not
* @private * @private
*/ */
Selection.prototype.select = function (selectable) { Selection.prototype.select = function (selectable, isMultiSelectEvent) {
if (!Array.isArray(selectable)) { if (!Array.isArray(selectable)) {
selectable = [selectable]; selectable = [selectable];
} }
if (this.selected[0] && this.selected[0].element) { let multiSelect = isMultiSelectEvent &&
this.selected[0].element.removeAttribute('s-selected'); 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.emit('change', this.selected);
this.selected[1].element.removeAttribute('s-selected-parent'); };
/**
* @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) { if (selectable[0] && selectable[0].element) {
selectable[0].element.setAttribute('s-selected', ""); selectable[0].element.setAttribute('s-selected', "");
} }
@ -69,16 +143,36 @@ define(['EventEmitter'], function (EventEmitter) {
if (selectable[1] && selectable[1].element) { if (selectable[1] && selectable[1].element) {
selectable[1].element.setAttribute('s-selected-parent', ""); 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 * @private
*/ */
Selection.prototype.capture = function (selectable) { Selection.prototype.capture = function (selectable) {
if (!this.capturing) { let capturingContainsSelectable = this.capturing && this.capturing.includes(selectable);
if (!this.capturing || capturingContainsSelectable) {
this.capturing = []; this.capturing = [];
} }
@ -88,13 +182,14 @@ define(['EventEmitter'], function (EventEmitter) {
/** /**
* @private * @private
*/ */
Selection.prototype.selectCapture = function (selectable) { Selection.prototype.selectCapture = function (selectable, event) {
if (!this.capturing) { if (!this.capturing) {
return; return;
} }
this.select(this.capturing.reverse()); let reversedCapturing = this.capturing.reverse();
delete this.capturing; delete this.capturing;
this.select(reversedCapturing, event.shiftKey);
}; };
/** /**
@ -112,7 +207,7 @@ define(['EventEmitter'], function (EventEmitter) {
* @public * @public
*/ */
Selection.prototype.selectable = function (element, context, select) { Selection.prototype.selectable = function (element, context, select) {
var selectable = { let selectable = {
context: context, context: context,
element: element element: element
}; };

View File

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

View File

@ -79,7 +79,7 @@ $colorKeyHov: #26d8ff;
$colorKeyFilter: invert(36%) sepia(76%) saturate(2514%) hue-rotate(170deg) brightness(99%) contrast(101%); $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%); $colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%) contrast(100%);
$colorKeySelectedBg: $colorKey; $colorKeySelectedBg: $colorKey;
$uiColor: #00b2ff; // Resize bars, splitter bars, etc. $uiColor: #0093ff; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2); $colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: #ccc; $colorA: #ccc;
$colorAHov: #fff; $colorAHov: #fff;
@ -143,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg $editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%); $editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
@ -159,8 +161,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ccc; // Border of selected frames $editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px; $editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element $editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style $editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@ -168,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame $editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
// Icons // Icons
$colorIconAlias: #4af6f3; $colorIconAlias: #4af6f3;
@ -224,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$paletteItemBorderOuterColorSelected: black; $paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white; $paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3); $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed
$mixedSettingBgSize: 5px;
// Forms // Forms
$colorCheck: $colorKey; $colorCheck: $colorKey;
@ -385,8 +390,10 @@ $colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1); $colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions // Transitions
$transIn: all 50ms ease-in-out; $transInTime: 50ms;
$transOut: all 250ms ease-in-out; $transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5); $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3); $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@ -147,6 +147,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg $editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%); $editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
@ -163,8 +165,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ccc; // Border of selected frames $editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px; $editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element $editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style $editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@ -172,6 +174,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame $editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
// Icons // Icons
$colorIconAlias: #4af6f3; $colorIconAlias: #4af6f3;
@ -228,6 +231,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$paletteItemBorderOuterColorSelected: black; $paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white; $paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3); $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed
$mixedSettingBgSize: 5px;
// Forms // Forms
$colorCheck: $colorKey; $colorCheck: $colorKey;
@ -389,8 +394,10 @@ $colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1); $colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions // Transitions
$transIn: all 50ms ease-in-out; $transInTime: 50ms;
$transOut: all 250ms ease-in-out; $transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5); $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3); $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@ -143,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
/************************************************** EDITING */ /************************************************** EDITING */
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #cae1ff; // Base color, toolbar bg $editUIBaseColor: #cae1ff; // Base color, toolbar bg
$editUIBaseColorHov: pushBack($editUIBaseColor, 20%); $editUIBaseColorHov: pushBack($editUIBaseColor, 20%);
@ -159,7 +161,7 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #333; // Border of selected frames $editFrameColorSelected: #333; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color $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 $editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
@ -168,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame $editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
// Icons // Icons
$colorIconAlias: #4af6f3; $colorIconAlias: #4af6f3;
@ -206,7 +209,7 @@ $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7);
$btnStdH: 24px; $btnStdH: 24px;
$colorCursorGuide: rgba(black, 0.6); $colorCursorGuide: rgba(black, 0.6);
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px; $shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8); $colorLocalControlOvrBg: rgba($colorBodyFg, 0.8);
// Menus // Menus
$colorMenuBg: pushBack($colorBodyBg, 10%); $colorMenuBg: pushBack($colorBodyBg, 10%);
@ -224,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$paletteItemBorderOuterColorSelected: black; $paletteItemBorderOuterColorSelected: black;
$paletteItemBorderInnerColorSelected: white; $paletteItemBorderInnerColorSelected: white;
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3); $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.9)); // Used in .c-click-icon--mixed
$mixedSettingBgSize: 10px;
// Forms // Forms
$colorCheck: $colorKey; $colorCheck: $colorKey;
@ -242,8 +247,8 @@ $colorInputPlaceholder: pushBack($colorBodyFg, 20%);
$colorFormText: pushBack($colorBodyFg, 10%); $colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%); $colorInputIcon: pushBack($colorBodyFg, 25%);
$colorFieldHint: pullForward($colorBodyFg, 40%); $colorFieldHint: pullForward($colorBodyFg, 40%);
$shdwInput: inset rgba(black, 0.7) 0 0 1px; $shdwInput: inset rgba(black, 0.5) 0 0 2px;
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px; $shdwInputHov: inset rgba(black, 0.8) 0 0 2px;
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px; $shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
$formTBPad: $interiorMargin; $formTBPad: $interiorMargin;
$formLRPad: $interiorMargin; $formLRPad: $interiorMargin;
@ -385,8 +390,10 @@ $colorLoadingFg: #776ba2;
$colorLoadingBg: rgba($colorLoadingFg, 0.1); $colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions // Transitions
$transIn: all 50ms ease-in-out; $transInTime: 50ms;
$transOut: all 250ms ease-in-out; $transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5); $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3); $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@ -28,6 +28,7 @@ $dirImgs: 'images/';
$controlFadeMs: 100ms; $controlFadeMs: 100ms;
$browseToEditAnimMs: 400ms; $browseToEditAnimMs: 400ms;
$editBorderPulseMs: 500ms; $editBorderPulseMs: 500ms;
$moveBarOutDelay: 500ms;
/************************** SPATIAL */ /************************** SPATIAL */
$interiorMarginSm: 3px; $interiorMarginSm: 3px;
@ -84,6 +85,8 @@ $waitSpinnerTreeBorderW: 3px;
/*************** Messages */ /*************** Messages */
$messageIconD: 80px; $messageIconD: 80px;
$messageListIconD: 32px; $messageListIconD: 32px;
/*************** Tables */
$tableResizeColHitareaD: 6px;
/************************** MOBILE */ /************************** MOBILE */
$mobileMenuIconD: 24px; // Used $mobileMenuIconD: 24px; // Used

View File

@ -49,6 +49,21 @@ button {
} }
} }
&[class*='__collapse-button'] {
box-shadow: none;
background: $splitterBtnColorBg;
color: $splitterBtnColorFg;
border-radius: $smallCr;
font-size: 6px;
line-height: 90%;
padding: 3px 15px;
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
}
&.is-active { &.is-active {
background: $colorBtnActiveBg; background: $colorBtnActiveBg;
color: $colorBtnActiveFg; color: $colorBtnActiveFg;
@ -70,13 +85,7 @@ button {
@include cClickIconButton(); @include cClickIconButton();
&--menu { &--menu {
&:after { @include hasMenu();
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
font-size: 0.7em;
margin-left: floor($interiorMarginSm * 0.8);
opacity: 0.5;
}
} }
} }
@ -85,6 +94,15 @@ button {
margin-left: $interiorMargin; 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 { &--swatched {
// Color control, show swatch element // Color control, show swatch element
display: flex; display: flex;
@ -93,9 +111,9 @@ button {
justify-content: center; justify-content: center;
> [class*='swatch'] { > [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; flex: 0 0 auto;
height: 4px; height: 5px;
width: 100%; width: 100%;
margin-top: 1px; margin-top: 1px;
} }
@ -105,12 +123,19 @@ button {
flex: 1 1 auto; flex: 1 1 auto;
font-size: 1.1em; font-size: 1.1em;
} }
&--mixed {
// Styling for swatched buttons when settings are mixed
> [class*='swatch'] {
@include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
}
}
} }
} }
/******************************************************** DISCLOSURE CONTROLS */ /******************************************************** DISCLOSURE CONTROLS */
/********* Disclosure Button */ /********* Disclosure Button */
// Provides a downward arrow icon that when clicked displays a context menu // Provides a downward arrow icon that when clicked displays additional options and/or info.
// Always placed AFTER an element // Always placed AFTER an element
.c-disclosure-button { .c-disclosure-button {
@include cClickIcon(); @include cClickIcon();
@ -148,8 +173,6 @@ button {
display: block; display: block;
font-family: symbolsfont; font-family: symbolsfont;
font-size: 1rem * $s; font-size: 1rem * $s;
transform-origin: center;
transition: $transOut;
} }
} }
@ -379,6 +402,11 @@ select {
color: $colorMenuIc; color: $colorMenuIc;
font-size: 1em; font-size: 1em;
margin-right: $interiorMargin; margin-right: $interiorMargin;
min-width: 1em;
}
&:not([class]):before {
content: ''; // Add this element so that menu items without an icon still indent properly
} }
} }
} }
@ -571,6 +599,10 @@ select {
margin-left: 2px; margin-left: 2px;
} }
&__button {
}
&__separator { &__separator {
@include cToolbarSeparator(); @include cToolbarSeparator();
} }
@ -721,6 +753,7 @@ input[type="range"] {
} }
} }
/******************************************************** LOCAL CONTROLS */
.h-local-controls { .h-local-controls {
// Holder for local controls // Holder for local controls
&--horz { &--horz {

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 { .gl-plot {
color: $colorPlotFg; color: $colorPlotFg;
display: flex; display: flex;
@ -548,3 +564,10 @@ mct-plot {
top: 0; bottom: 0; top: 0; bottom: 0;
} }
} }
.s-status-timeconductor-unsynced {
.t-object-alert.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
}

View File

@ -482,6 +482,8 @@ body.mobile.phone {
.widget-edit-holder { .widget-edit-holder {
display: flex; // Overrides `display: none` during Browse mode display: flex; // Overrides `display: none` during Browse mode
flex: 1 1 auto;
.flex-accordion-holder { .flex-accordion-holder {
// Needed because otherwise accordion elements "creep" when contents expand and contract // Needed because otherwise accordion elements "creep" when contents expand and contract
display: block !important; display: block !important;
@ -497,12 +499,6 @@ body.mobile.phone {
// Test data is expanded and rules are collapsed // Test data is expanded and rules are collapsed
// Make text data take up all the vertical space // Make text data take up all the vertical space
.flex-accordion-holder { display: flex; } .flex-accordion-holder { display: flex; }
.widget-test-data {
flex-grow: 999999;
}
.w-widget-test-data-items {
max-height: inherit;
}
} }
} }
&.expanded-widget-rules { &.expanded-widget-rules {
@ -786,6 +782,10 @@ mct-indicators mct-include {
display: contents; display: contents;
} }
/*
// MOVED TO INDICATORS.VUE
.ls-indicator { .ls-indicator {
$bg: rgba(white, 0.2) !important; $bg: rgba(white, 0.2) !important;
$hbg: $colorStatusBarBg; $hbg: $colorStatusBarBg;
@ -863,7 +863,7 @@ mct-indicators mct-include {
border-radius: $br; border-radius: $br;
font-size: .6rem; font-size: .6rem;
left: 0; left: 0;
bottom: 140%; top: 140%;
opacity: 0; opacity: 0;
padding: $interiorMarginSm $interiorMargin; padding: $interiorMarginSm $interiorMargin;
position: absolute; position: absolute;
@ -877,8 +877,8 @@ mct-indicators mct-include {
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
top: 100%; bottom: 100%;
@include triangle('down', $size: 4px, $ratio: 1, $color: $hbg); @include triangle('up', $size: 4px, $ratio: 1, $color: $hbg);
} }
} }
@ -898,7 +898,8 @@ mct-indicators mct-include {
} }
} }
/* Mobile */
// Hide the clock indicator when we're phone portrait // Hide the clock indicator when we're phone portrait
body.phone.portrait { body.phone.portrait {
.ls-indicator.t-indicator-clock { .ls-indicator.t-indicator-clock {
@ -906,6 +907,8 @@ body.phone.portrait {
} }
} }
*/
/************************************************* DATETIME UI */ /************************************************* DATETIME UI */
@mixin complexFieldHolder($myW) { @mixin complexFieldHolder($myW) {
width: $myW + $interiorMargin; width: $myW + $interiorMargin;

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