Compare commits

...

33 Commits

Author SHA1 Message Date
d7a3510f34 Fixed code issues in gaugeRadial 2018-11-10 22:35:19 -08:00
0519824109 Added todo 2018-11-08 00:07:10 -08:00
4380f88a08 Apply hero font to gauge
- Placing, sizing;
2018-11-08 00:05:28 -08:00
20d5db6e44 Change default Fixed Layout grid sizing 2018-11-07 23:58:59 -08:00
5978b8e19d Added new theme stylesheet
- Loads thematic fonts;
- Font mixins;
- Override styling for c-frame;
2018-11-07 23:58:26 -08:00
a0b71f92b8 Merge branch 'gauges' into maelstrom2-core 2018-11-07 22:38:27 -08:00
7c3f7ff384 Added description 2018-11-07 22:38:05 -08:00
6f2a567299 Merge in latest topic-core-refactor 2018-11-07 22:34:04 -08:00
746badd065 Merge in gauges branch 2018-11-07 22:30:58 -08:00
55d3ab5e8a Added new glyph 2018-11-07 22:27:03 -08:00
c073a21ba6 [Table] Custom column widths and order (#2204)
* Renders with resize hotzones

* Implemented basic reordering of columns

* Refactored column headers into component

* Custom column widths persist during resize

* Initial working version of fixed column sizes

* Only calculate default sizes when first data received.

* Further fixes for fixed column widths. Add option to switch to auto-widths

* Fixed bug with table auto-sizing

* Only allow reorder and resize in edit mode

* Bug fixes

* Allow for scroll bar width

* Bug fix with tables reverting to 100% width

* Fixed bug with drop position indicator when scrolled

* Moved events on to component

* Do not throttle mouse events. Let Vue throttle them

* Do not hard code vertical offset for drop target

* Addressed review issues

* Clarified mouse event handling on column resize
2018-11-07 11:04:56 -08:00
ed8137726d cstyle update 1106 (#2211)
* Temp fixes for legacy styling of overlay and forms

* Added new glyphs: rows, columns, plus-in-rect
2018-11-07 09:58:33 -08:00
7d99877eb9 Fixed dial value SVG path to avoid visual overflow at 90 deg 2018-11-06 23:52:40 -08:00
3eac91a6d9 Added gauge icon 2018-11-06 23:31:02 -08:00
4d426580ae Gauge now a plugin 2018-11-06 22:48:24 -08:00
9270f02ca4 Merge branch 'topic-core-refactor' into gauges 2018-11-06 22:11:03 -08:00
3ebdab5e51 Symbols font update (#2206)
* Very significant update to symbols font

- New Icomoon project and font files for 16px symbols;
- Symbol char codes now PUA and hexadecimal compliant;
- Symbols reorganized;
- New symbols added;
- Remove loading of legacy glyphs scss file;

* Added new fonts

- Updated Tabs View icon art;
- Generators icons added;

* Added new icon glyphs and background SVG art

- New icons for generators;
- New icon for Flexible Layout;
- New icon-grippy-ew, for Flexible Layout, others;

* Remove unused legacy font files
2018-11-06 13:22:44 -08:00
815b1449f4 Merge branch 'topic-core-refactor' into gauges 2018-11-05 19:53:48 -08:00
0874ada4d2 Maths fixed!
TODO: clipping path shows a little sliver of dial at 90%;
2018-10-31 01:20:32 -07:00
03812437d3 Current value and ranges now SVG text
- Scales properly;
- Math enhancements;
- TODO: fix math when negative numbers involved;
2018-10-31 00:15:15 -07:00
2b8272cf05 Adding dynamic SVG text 2018-10-30 09:54:00 -07:00
35d1b894e2 Tab view (#2197)
* first cut, working for objects with explicit height

* working for all objects by setting min-height explicitly to 70vh

* add composition listeners

* Tabs view WIP

- Markup and CSS BEMized;
- Stubbed in type icon in tab elements;
- TODO: refine styling on tabs;

* Tabs view WIP

- Layout enhancements;

* add types and header

* remove static icon-layout class

* move Tabs into its own plugin folder:

* simplify on composition add listener callback

* fix icon rendering

* add document dragstart and dragend listeners, with v-if div for drop target

* remove third argument from document listeners, move drop target to tab container

* Sanding and shimming on Tabs View

- WIP

* Tabs View styling

- Shippable to Deep;
- Added new c-drop-hint element in _controls.scss;
- Added new theme constants;
- Added 'empty' tabs view message;
- TODOs: add listener for dragover event for is-mouse-over styling,
integrate forthcoming changes to symbolsfont;

* add is-mouse-over class when drag enters dropHint div, add and remove listeners

* .c-drop-hint styling refined

- Refined hover effects;
- Bg icon instead of glyph character;
- TODOs: Change $bg-icon to plus sign;

* fix bug regarding persisting drop-hint styling even after drop is ended
2018-10-26 14:14:00 -07:00
71a2f27e0c Refined art, WIP 2018-10-26 09:39:47 -07:00
5460ca2009 Initial markup, CSS and code for radial gauge
- WIP!
2018-10-25 10:21:57 -07:00
7c54ec4f9f Tree listens for composition 2018-10-23 11:11:54 -07:00
cbcfd44016 Elements pool and drag drop (#2196)
* Implemented drag-and-drop composition

* Added composition policy for tables

* Reimplemented elements pool in Vue

* No need to resolve all objects on the navigated path

* Only show elements pool in edit mode

* Remove old elements pool

* Updated legacy code to use composition policy API

* Keep object in sync when mutated
2018-10-23 10:52:37 -07:00
a296bc2b81 Fix regression in layout frame edit handle by using a modifier convention. 2018-10-22 10:14:22 -07:00
06b9e0fa97 Create menu (#2195)
* Wire up create with old create action

* refactor grid and list view to listen on composition, use lodash for sorts, remove item count

* remove item count on grid view

item count needs to be supported by composition API and not by
inspecting model, otherwise it will be inconsistent.

* close menu after create or clickaway
2018-10-19 15:07:30 -07:00
4374a6fa28 Toolbar in Fixed Position (#2194)
* Initial attempt at getting the toolbar for the fixed position working with the Vue toolbar controls.

* Set title for controls

* Significant mods to support legacy Fixed Position

- Moved selection and editing styles into _global.scss;
- Changed class naming in legacy fixed.html to map to newer CSS styles;

* Pass in the color value

* Do not show the toolbar container if structure is empty.

* Use a plugin for fixed position to get access to openmct. Show the toolbar only if the object is being edited.

* Ensure fixedController is on the selection context when editing

* Add listener for a domain object with the same id only once.
Update the toolbar value after the object mutation.
Remove editor isEditing listener on destroyed.

* Remove space between the size and px.
If newObject exists, update the toolbar value.

* Remove --nwse class name which seems to be a typo

* use modifier convention
2018-10-19 11:42:30 -07:00
67883519ee Overlay Service - rewritten/redesigned in Vue (#2190)
* modify overlay service to accept bottomBarButtons option, working annotate on snapshot view

* working blocking message

* move blocking message to overlay service

* return dismiss function on show

* added jsdocs for overlayService

* added progress bar, with setter and getter functions

* make reviewer requested changes

* re-overhaul of overlayAPI

* Integrate work in dialog-service-vue-style

- Markup in DialogComponent and OverlayComponent now up to date;
- Colors, constants;
- Notebook entry now passes correct buttons config;
- New bg data URIs added;
- u-icon-bg-* classes added to global.scss;

* Added deprecation comments to .vue files

* Removed styles from deprecated file

* Temp restore of CSS so that dialog doesn't break.

* remove old OverlayService

* Fixed to overlay CSS

- Fixed large, small, fit sizes;
- Added margin to overlay buttons;
- Code cleanup;

* Remove unused constants

* Tweak styles for __close-button

* Code cleanup

* small cleanup

* wip progressDialog

* Progress bar fixed and Vue-ized

- Markup, styles;
- Constants moved from theme files into _constants.scss;

* added jsdocs to new Overlay API

* remove unused example function

* use progressBar.vue in NotificationBanner.vue, wire up the maximize method with progressDialog and Dialog from OverlayAPI

* Styling in progress for status message banners

* openmct members are camelCase

* Remove unimplemented/unused apis
2018-10-19 11:15:57 -07:00
6f1b5b4ae3 Notifications progress method (#2193)
* Added progress method to notifications so no longer dependent on reactive properties

* Updated notification launch controller to use new progress method

* Added progress function to Notifications API. Introduced NotificationService compatibility layer for legacy code
2018-10-15 10:00:05 -07:00
c3b7e7869e Fix code to remove warnings in conductor (#2192) 2018-10-11 11:33:59 -07:00
d48cc2deee Vue toolbar (#2191)
* Add a toolbar provider for display layout.

* Move toolbar provider to the plugin

* basic toolbar generation

* componentize different toolbar control types

Break toolbar control types down into different parts and provide
a test toolbar generator in index.html that utilizes all the
controls.

* Get the 'Show frame' checkbox working in the toolbar

* - Remove extra listener.
- Display toolbar only when editing.

* Modify the Selection API to set s-selected and s-selected-parent attributes instead of adding to the css class names.

* Move the logic for allowing the toolbar in the edit mode to the provider.

* Use toggle-button component to toggle frame

* Delete old files

* Remove MCTToolbar

* Modify the toggle button component to return the computed value

* Remove reload=true

* Revert to the original setting

* use value from props

* Always update toolbars on edit status change

* restore fixed position bundle

* bring back reload when hmr unavailable
2018-10-11 11:33:33 -07:00
177 changed files with 6676 additions and 6981 deletions

2
app.js
View File

@ -46,7 +46,7 @@ webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) }); webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
webpackConfig.entry.openmct = [ webpackConfig.entry.openmct = [
'webpack-hot-middleware/client', 'webpack-hot-middleware/client?reload=true',
webpackConfig.entry.openmct webpackConfig.entry.openmct
]; ];

View File

@ -49,7 +49,7 @@ define([
{ {
"key": "eventGenerator", "key": "eventGenerator",
"name": "Event Message Generator", "name": "Event Message Generator",
"cssClass": "icon-folder-new", "cssClass": "icon-generator-events",
"description": "For development use. Creates sample event message data that mimics a live data stream.", "description": "For development use. Creates sample event message data that mimics a live data stream.",
"priority": 10, "priority": 10,
"features": "creation", "features": "creation",

View File

@ -38,7 +38,7 @@ define([
openmct.types.addType("example.state-generator", { openmct.types.addType("example.state-generator", {
name: "State Generator", name: "State Generator",
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states", description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
cssClass: "icon-telemetry", cssClass: "icon-generator-telemetry",
creatable: true, creatable: true,
form: [ form: [
{ {
@ -66,7 +66,7 @@ define([
openmct.types.addType("generator", { openmct.types.addType("generator", {
name: "Sine Wave Generator", name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
cssClass: "icon-telemetry", cssClass: "icon-generator-telemetry",
creatable: true, creatable: true,
form: [ form: [
{ {

View File

@ -79,30 +79,34 @@ define(
* periodically, tracking an ongoing process. * periodically, tracking an ongoing process.
*/ */
$scope.newProgress = function () { $scope.newProgress = function () {
let progress = 0;
var notificationModel = { var notificationModel = {
title: "Progress notification example", title: "Progress notification example",
severity: "info", severity: "info",
progress: 0, progress: progress,
actionText: getExampleActionText() actionText: getExampleActionText()
}; };
let notification;
/** /**
* Simulate an ongoing process and update the progress bar. * Simulate an ongoing process and update the progress bar.
* @param notification * @param notification
*/ */
function incrementProgress() { function incrementProgress() {
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30)); progress = Math.min(100, Math.floor(progress + Math.random() * 30))
notificationModel.progressText = ["Estimated time" + let progressText = ["Estimated time" +
" remaining:" + " remaining:" +
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" "); " about ", 60 - Math.floor((progress / 100) * 60), " seconds"].join(" ");
if (notificationModel.progress < 100) { notification.progress(progress, progressText);
if (progress < 100) {
$timeout(function () { $timeout(function () {
incrementProgress(notificationModel); incrementProgress(notificationModel);
}, 1000); }, 1000);
} }
} }
notificationService.notify(notificationModel); notification = notificationService.notify(notificationModel);
incrementProgress(); incrementProgress();
}; };

View File

@ -49,6 +49,7 @@
openmct.install(openmct.plugins.ExampleImagery()); openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem()); openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.ImportExport()); openmct.install(openmct.plugins.ImportExport());
openmct.install(openmct.plugins.FixedView());
openmct.install(openmct.plugins.AutoflowView({ openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel" type: "telemetry.panel"
})); }));
@ -76,8 +77,208 @@
openmct.install(openmct.plugins.SummaryWidget()); openmct.install(openmct.plugins.SummaryWidget());
openmct.install(openmct.plugins.Notebook()); openmct.install(openmct.plugins.Notebook());
openmct.install(openmct.plugins.FolderView()); openmct.install(openmct.plugins.FolderView());
openmct.install(openmct.plugins.Tabs());
openmct.install(openmct.plugins.Gauge());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0}); openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc'); openmct.time.timeSystem('utc');
openmct.start(); openmct.start();
// openmct.toolbars.addProvider({
// name: "Testing Toolbar",
// key: "testing",
// description: "a mock toolbar that exercises all controls",
// forSelection: function (selection) {
// return true; // always applies.
// },
// toolbar: function (selection) {
// return [
// {
// control: 'menu',
// icon: 'icon-plus',
// label: 'Add',
// options: [
// { name: 'Box', class: 'icon-box', title: 'Add Box' },
// { name: 'Line', class: 'icon-line-horz', title: 'Add Line' },
// { name: 'Text', class: 'icon-font', title: 'Add Text' },
// { name: 'Image', class: 'icon-image', title: 'Add Image' }
// ]
// },
// {
// control: 'separator'
// },
// {
// control: 'color-picker',
// icon: 'icon-paint-bucket',
// value: '#33ff00',
// },
// {
// control: 'color-picker',
// icon: 'icon-pencil',
// value: '#ffffff',
// },
// {
// control: 'color-picker',
// icon: 'icon-font',
// value: '#333333',
// },
//
// {
// control: 'separator'
// },
// {
// control: 'select-menu',
// value: 11,
// options: [
// { value: 9, name: '9 px' },
// { value: 10, name: '10 px' },
// { value: 11, name: '11 px' },
// { value: 12, name: '12 px' },
// { value: 13, name: '13 px' },
// { value: 14, name: '14 px' },
// { value: 16, name: '16 px' },
// { value: 18, name: '18 px' },
// { value: 20, name: '20 px' },
// { value: 24, name: '24 px' },
// { value: 28, name: '28 px' },
// { value: 32, name: '32 px' },
// { value: 40, name: '40 px' },
// { value: 48, name: '48 px' },
// { value: 56, name: '56 px' },
// { value: 64, name: '64 px' },
// { value: 72, name: '72 px' },
// { value: 80, name: '80 px' },
// { value: 88, name: '88 px' },
// { value: 96, name: '96 px' },
// { value: 128, name: '128 px' },
// { value: 160, name: '160 px' }
// ]
// },
//
// {
// control: 'separator'
// },
// {
// control: 'menu',
// icon: 'icon-layers',
// options: [
// { name: 'Move to top', class: 'icon-arrow-double-up', title: 'Move to top' },
// { name: 'Move up', class: 'icon-arrow-up', title: 'Move up' },
// { name: 'Move down', class: 'icon-arrow-down', title: 'Move down' },
// { name: 'Move to bottom', class: 'icon-arrow-double-down', title: 'Move to bottom' }
// ]
// },
//
// {
// control: 'separator'
// },
// {
// control: 'button',
// icon: 'icon-gear'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'input',
// type: 'number',
// label: 'X',
// value: 1,
// title: 'X position'
// },
// {
// control: 'input',
// type: 'number',
// label: 'Y',
// value: 2,
// title: 'Y position'
// },
// {
// control: 'input',
// type: 'number',
// label: 'W',
// value: 3,
// title: 'Width'
// },
// {
// control: 'input',
// type: 'number',
// label: 'H',
// value: 4,
// title: 'Height'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'button',
// icon: 'icon-trash',
// label: 'delete',
// modifier: 'caution'
// },
//
// {
// control: 'separator'
// },
// {
// control: 'checkbox',
// name: 'this is a checkbox',
// },
// {
// control: 'separator'
// },
// {
// control: 'toggle-button',
// title: 'Toggle Frame',
// property: 'hideFrame',
// value: false,
// options: [
// {
// value: true,
// icon: 'icon-frame-hide'
// },
// {
// value: false,
// icon: 'icon-frame-show'
// }
// ]
// },
// {
// control: 'toggle-button',
// title: 'Snap to grid',
// property: 'snapToGrid',
// value: true,
// options: [
// {
// value: true,
// icon: 'icon-grid-snap-to'
// },
// {
// value: false,
// icon: 'icon-grid-snap-no'
// }
// ]
// },
// {
// control: 'toggle-button',
// title: 'Toggle label',
// property: 'showLabel',
// value: true,
// options: [
// {
// value: true,
// icon: 'icon-two-parts-both'
// },
// {
// value: false,
// icon: 'icon-two-parts-one-only'
// }
// ]
// }
// ];
// }
// });
</script> </script>
</html> </html>

View File

@ -2,31 +2,14 @@
ng-class="'message-severity-' + ngModel.severity"> ng-class="'message-severity-' + ngModel.severity">
<div class="w-message-contents"> <div class="w-message-contents">
<div class="top-bar"> <div class="top-bar">
<div class="title">{{ngModel.title}}</div> <div class="title">{{ngModel.message}}</div>
</div>
<div class="hint" ng-hide="ngModel.hint === undefined">
{{ngModel.hint}}
<span ng-if="ngModel.timestamp !== undefined">[{{ngModel.timestamp}}]</span>
</div> </div>
<div class="message-body"> <div class="message-body">
<div class="message-action">
{{ngModel.actionText}}
</div>
<mct-include key="'progress-bar'" <mct-include key="'progress-bar'"
ng-model="ngModel" ng-model="ngModel"
ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include> ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div> </div>
<div class="bottom-bar"> <div class="bottom-bar">
<a ng-repeat="dialogOption in ngModel.options"
class="s-button"
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</a>
<a class="s-button major"
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -23,7 +23,6 @@
define([ define([
"./src/controllers/EditActionController", "./src/controllers/EditActionController",
"./src/controllers/EditPanesController", "./src/controllers/EditPanesController",
"./src/controllers/ElementsController",
"./src/controllers/EditObjectController", "./src/controllers/EditObjectController",
"./src/actions/EditAndComposeAction", "./src/actions/EditAndComposeAction",
"./src/actions/EditAction", "./src/actions/EditAction",
@ -47,7 +46,6 @@ define([
"./src/creation/LocatorController", "./src/creation/LocatorController",
"./src/creation/CreationPolicy", "./src/creation/CreationPolicy",
"./src/creation/CreateActionProvider", "./src/creation/CreateActionProvider",
"./src/creation/AddActionProvider",
"./src/creation/CreationService", "./src/creation/CreationService",
"./res/templates/create/locator.html", "./res/templates/create/locator.html",
"./res/templates/create/create-button.html", "./res/templates/create/create-button.html",
@ -55,13 +53,11 @@ define([
"./res/templates/library.html", "./res/templates/library.html",
"./res/templates/edit-object.html", "./res/templates/edit-object.html",
"./res/templates/edit-action-buttons.html", "./res/templates/edit-action-buttons.html",
"./res/templates/elements.html",
"./res/templates/topbar-edit.html", "./res/templates/topbar-edit.html",
'legacyRegistry' 'legacyRegistry'
], function ( ], function (
EditActionController, EditActionController,
EditPanesController, EditPanesController,
ElementsController,
EditObjectController, EditObjectController,
EditAndComposeAction, EditAndComposeAction,
EditAction, EditAction,
@ -85,7 +81,6 @@ define([
LocatorController, LocatorController,
CreationPolicy, CreationPolicy,
CreateActionProvider, CreateActionProvider,
AddActionProvider,
CreationService, CreationService,
locatorTemplate, locatorTemplate,
createButtonTemplate, createButtonTemplate,
@ -93,7 +88,6 @@ define([
libraryTemplate, libraryTemplate,
editObjectTemplate, editObjectTemplate,
editActionButtonsTemplate, editActionButtonsTemplate,
elementsTemplate,
topbarEditTemplate, topbarEditTemplate,
legacyRegistry legacyRegistry
) { ) {
@ -115,14 +109,6 @@ define([
"$scope" "$scope"
] ]
}, },
{
"key": "ElementsController",
"implementation": ElementsController,
"depends": [
"$scope",
"openmct"
]
},
{ {
"key": "EditObjectController", "key": "EditObjectController",
"implementation": EditObjectController, "implementation": EditObjectController,
@ -225,10 +211,10 @@ define([
"description": "Save changes made to these objects.", "description": "Save changes made to these objects.",
"depends": [ "depends": [
"$injector", "$injector",
"policyService",
"dialogService", "dialogService",
"copyService", "copyService",
"notificationService" "notificationService",
"openmct"
], ],
"priority": "mandatory" "priority": "mandatory"
}, },
@ -296,13 +282,6 @@ define([
"action" "action"
] ]
}, },
{
"key": "edit-elements",
"template": elementsTemplate,
"gestures": [
"drop"
]
},
{ {
"key": "topbar-edit", "key": "topbar-edit",
"template": topbarEditTemplate "template": topbarEditTemplate
@ -319,12 +298,6 @@ define([
] ]
} }
], ],
"templates": [
{
key: "elementsPool",
template: elementsTemplate
}
],
"components": [ "components": [
{ {
"type": "decorator", "type": "decorator",
@ -356,18 +329,6 @@ define([
"policyService" "policyService"
] ]
}, },
{
"key": "AddActionProvider",
"provides": "actionService",
"type": "provider",
"implementation": AddActionProvider,
"depends": [
"$q",
"typeService",
"dialogService",
"policyService"
]
},
{ {
"key": "CreationService", "key": "CreationService",
"provides": "creationService", "provides": "creationService",

View File

@ -1,49 +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.
-->
<div ng-controller="ElementsController" class="flex-elem l-flex-col holder grows">
<mct-include key="'input-filter'"
class="flex-elem holder"
ng-model="filterBy">
</mct-include>
<div class="flex-elem grows vscroll scroll-pad">
<ul class="tree" id="inspector-elements-tree"
ng-if="composition.length > 0">
<li ng-repeat="containedObject in composition | filter:searchElements">
<span class="tree-item">
<span class="grippy-sm"
ng-if="composition.length > 1"
data-id="{{ containedObject.id }}"
mct-drag-down="dragDown($event)"
mct-drag="drag($event)"
mct-drag-up="dragUp($event)">
</span>
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="containedObject">
</mct-representation>
</span>
</li>
</ul>
<div ng-if="composition.length === 0">No contained elements</div>
</div>
</div>

View File

@ -40,20 +40,20 @@ function (
*/ */
function SaveAsAction( function SaveAsAction(
$injector, $injector,
policyService,
dialogService, dialogService,
copyService, copyService,
notificationService, notificationService,
openmct,
context context
) { ) {
this.domainObject = (context || {}).domainObject; this.domainObject = (context || {}).domainObject;
this.injectObjectService = function () { this.injectObjectService = function () {
this.objectService = $injector.get("objectService"); this.objectService = $injector.get("objectService");
}; };
this.policyService = policyService;
this.dialogService = dialogService; this.dialogService = dialogService;
this.copyService = copyService; this.copyService = copyService;
this.notificationService = notificationService; this.notificationService = notificationService;
this.openmct = openmct;
} }
/** /**
@ -63,7 +63,7 @@ function (
return new CreateWizard( return new CreateWizard(
this.domainObject, this.domainObject,
parent, parent,
this.policyService this.openmct
); );
}; };

View File

@ -51,8 +51,11 @@ define(
*/ */
EditorCapability.prototype.edit = function () { EditorCapability.prototype.edit = function () {
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.'); console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
this.openmct.editor.edit();
this.domainObject.getCapability('status').set('editing', true); if (!this.openmct.editor.isEditing()) {
this.openmct.editor.edit();
this.domainObject.getCapability('status').set('editing', true);
}
}; };
/** /**
@ -82,6 +85,7 @@ define(
*/ */
EditorCapability.prototype.save = function () { EditorCapability.prototype.save = function () {
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.'); console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
return Promise.resolve();
}; };
EditorCapability.prototype.invoke = EditorCapability.prototype.edit; EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
@ -93,6 +97,7 @@ define(
*/ */
EditorCapability.prototype.finish = function () { EditorCapability.prototype.finish = function () {
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.'); console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
return Promise.resolve();
}; };
/** /**

View File

@ -1,197 +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(
['zepto'],
function ($) {
/**
* The ElementsController prepares the elements view for display
*
* @constructor
*/
function ElementsController($scope, openmct) {
this.scope = $scope;
this.scope.composition = [];
this.openmct = openmct;
this.dragDown = this.dragDown.bind(this);
this.dragUp = this.dragUp.bind(this);
var self = this;
function filterBy(text) {
if (typeof text === 'undefined') {
return $scope.searchText;
} else {
$scope.searchText = text;
}
}
function searchElements(value) {
if ($scope.searchText) {
return value.getModel().name.toLowerCase().search(
$scope.searchText.toLowerCase()) !== -1;
} else {
return true;
}
}
function setSelection(selection) {
if (!selection[0]) {
return;
}
if (self.mutationListener) {
self.mutationListener();
delete self.mutationListener;
}
var domainObject = selection[0].context.oldItem;
self.refreshComposition(domainObject);
if (domainObject) {
self.mutationListener = domainObject.getCapability('mutation')
.listen(self.refreshComposition.bind(self, domainObject));
}
}
$scope.filterBy = filterBy;
$scope.searchElements = searchElements;
openmct.selection.on('change', setSelection);
setSelection(openmct.selection.get());
$scope.dragDown = this.dragDown;
$scope.drag = this.drag;
$scope.dragUp = this.dragUp;
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
});
}
/**
* Invoked on DragStart - Adds reordering class to parent UL element
* Sets selected object ID, to be used on Drag End
*
* @param {object} event | Mouse Event
*/
ElementsController.prototype.dragDown = function (event) {
if (!this.parentUL) {
this.parentUL = $(document).find('#inspector-elements-tree');
}
this.selectedTreeItem = $(event.target).parent();
this.selectedObjectId = event.target.getAttribute('data-id');
this.parentUL.addClass('reordering');
this.selectedTreeItem.addClass('reorder-actor');
};
/**
* Invoked on dragEnd - Removes selected object from position in composition
* and replaces it at the target position. Composition is then updated with current
* scope
*
* @param {object} event - Mouse Event
*/
ElementsController.prototype.dragUp = function (event) {
this.targetObjectId = event.target.getAttribute('data-id');
if (this.targetObjectId && this.selectedObjectId) {
var selectedObjectPosition,
targetObjectPosition;
selectedObjectPosition = findObjectInCompositionFromId(this.selectedObjectId, this.scope.composition);
targetObjectPosition = findObjectInCompositionFromId(this.targetObjectId, this.scope.composition);
if ((selectedObjectPosition !== -1) && (targetObjectPosition !== -1)) {
var selectedObject = this.scope.composition.splice(selectedObjectPosition, 1),
selection = this.openmct.selection.get(),
domainObject = selection ? selection[0].context.oldItem : undefined;
this.scope.composition.splice(targetObjectPosition, 0, selectedObject[0]);
if (domainObject) {
domainObject.getCapability('mutation').mutate(function (model) {
model.composition = this.scope.composition.map(function (dObject) {
return dObject.id;
});
}.bind(this));
}
}
}
if (this.parentUL) {
this.parentUL.removeClass('reordering');
}
if (this.selectedTreeItem) {
this.selectedTreeItem.removeClass('reorder-actor');
}
};
ElementsController.prototype.drag = function (event) {
};
/**
* Gets the composition for the selected object and populates the scope with it.
*
* @param domainObject the selected object
* @private
*/
ElementsController.prototype.refreshComposition = function (domainObject) {
var refreshTracker = {};
this.currentRefresh = refreshTracker;
var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
if (selectedObjectComposition) {
selectedObjectComposition.then(function (composition) {
if (this.currentRefresh === refreshTracker) {
this.scope.composition = composition;
}
}.bind(this));
} else {
this.scope.composition = [];
}
};
/**
* Finds position of object with given ID in Composition
*
* @param {String} id
* @param {Array} composition
* @private
*/
function findObjectInCompositionFromId(id, composition) {
var mapped = composition.map(function (element) {
return element.id;
});
return mapped.indexOf(id);
}
return ElementsController;
}
);

View File

@ -1,133 +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.
*****************************************************************************/
/**
* Module defining AddAction. Created by ahenry on 01/21/16.
*/
define(
[
'./CreateWizard'
],
function (CreateWizard) {
/**
* The Add Action is performed to create new instances of
* domain objects of a specific type that are subobjects of an
* object being edited. This is the action that is performed when a
* user uses the Add menu option.
*
* @memberof platform/commonUI/browse
* @implements {Action}
* @constructor
*
* @param {Type} type the type of domain object to create
* @param {DomainObject} parent the domain object that should
* act as a container for the newly-created object
* (note that the user will have an opportunity to
* override this)
* @param {ActionContext} context the context in which the
* action is being performed
* @param {DialogService} dialogService
*/
function AddAction(type, parent, context, $q, dialogService, policyService) {
this.metadata = {
key: 'add',
cssClass: type.getCssClass(),
name: type.getName(),
type: type.getKey(),
description: type.getDescription(),
context: context
};
this.type = type;
this.parent = parent;
this.$q = $q;
this.dialogService = dialogService;
this.policyService = policyService;
}
/**
*
* Create a new object of the given type.
* This will prompt for user input first.
*
* @returns {Promise} that will be resolved with the object that the
* action was originally invoked on (ie. the 'parent')
*/
AddAction.prototype.perform = function () {
var newModel = this.type.getInitialModel(),
newObject,
parentObject = this.parent,
wizard;
newModel.type = this.type.getKey();
newObject = parentObject.getCapability('instantiation').instantiate(newModel);
newObject.useCapability('mutation', function (model) {
model.location = parentObject.getId();
});
wizard = new CreateWizard(newObject, this.parent, this.policyService);
function populateObjectFromInput(formValue) {
return wizard.populateObjectFromInput(formValue, newObject);
}
function persistAndReturn(domainObject) {
return domainObject.getCapability('persistence')
.persist()
.then(function () {
return domainObject;
});
}
function addToParent(populatedObject) {
parentObject.getCapability('composition').add(populatedObject);
return persistAndReturn(parentObject);
}
return this.dialogService
.getUserInput(wizard.getFormStructure(false), wizard.getInitialFormValue())
.then(populateObjectFromInput)
.then(persistAndReturn)
.then(addToParent);
};
/**
* Metadata associated with a Add action.
* @typedef {ActionMetadata} AddActionMetadata
* @property {string} type the key for the type of domain object
* to be created
*/
/**
* Get metadata about this action.
* @returns {AddActionMetadata} metadata about this action
*/
AddAction.prototype.getMetadata = function () {
return this.metadata;
};
return AddAction;
}
);

View File

@ -1,82 +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.
*****************************************************************************/
/**
* Module defining AddActionProvider.js. Created by ahenry on 01/21/16.
*/
define(
["./AddAction"],
function (AddAction) {
/**
* The AddActionProvider is an ActionProvider which introduces
* an Add action for creating sub objects.
*
* @memberof platform/commonUI/browse
* @constructor
* @implements {ActionService}
*
* @param {TypeService} typeService the type service, used to discover
* available types
* @param {DialogService} dialogService the dialog service, used by
* specific Create actions to get user input to populate the
* model of the newly-created domain object.
* @param {CreationService} creationService the creation service (also
* introduced in this bundle), responsible for handling actual
* object creation.
*/
function AddActionProvider($q, typeService, dialogService, policyService) {
this.typeService = typeService;
this.dialogService = dialogService;
this.$q = $q;
this.policyService = policyService;
}
AddActionProvider.prototype.getActions = function (actionContext) {
var context = actionContext || {},
key = context.key,
destination = context.domainObject;
// We only provide Add actions, and we need a
// domain object to serve as the container for the
// newly-created object (although the user may later
// make a different selection)
if (key !== 'add' || !destination) {
return [];
}
// Introduce one create action per type
return ['timeline', 'activity'].map(function (type) {
return new AddAction(
this.typeService.getType(type),
destination,
context,
this.$q,
this.dialogService,
this.policyService
);
}, this);
};
return AddActionProvider;
}
);

View File

@ -44,7 +44,7 @@ define(
* @param {ActionContext} context the context in which the * @param {ActionContext} context the context in which the
* action is being performed * action is being performed
*/ */
function CreateAction(type, parent, context) { function CreateAction(type, parent, context, openmct) {
this.metadata = { this.metadata = {
key: 'create', key: 'create',
cssClass: type.getCssClass(), cssClass: type.getCssClass(),
@ -55,6 +55,7 @@ define(
}; };
this.type = type; this.type = type;
this.parent = parent; this.parent = parent;
this.openmct = openmct;
} }
/** /**
@ -63,37 +64,28 @@ define(
*/ */
CreateAction.prototype.perform = function () { CreateAction.prototype.perform = function () {
var newModel = this.type.getInitialModel(), var newModel = this.type.getInitialModel(),
openmct = this.openmct,
newObject, newObject,
editAction, editAction;
editorCapability;
function closeEditor() {
return editorCapability.finish();
}
function onSave() { function onSave() {
return editorCapability.save() openmct.editor.save();
.then(closeEditor);
} }
function onCancel() { function onCancel() {
return closeEditor(); openmct.editor.cancel();
} }
newModel.type = this.type.getKey(); newModel.type = this.type.getKey();
newModel.location = this.parent.getId(); newModel.location = this.parent.getId();
newObject = this.parent.useCapability('instantiation', newModel); newObject = this.parent.useCapability('instantiation', newModel);
editorCapability = newObject.hasCapability('editor') && newObject.getCapability("editor");
openmct.editor.edit();
editAction = newObject.getCapability("action").getActions("edit")[0]; editAction = newObject.getCapability("action").getActions("edit")[0];
//If an edit action is available, perform it newObject.getCapability("action").perform("save-as").then(onSave, onCancel);
if (editAction) { // TODO: support editing object without saving object first.
return editAction.perform(); // Which means we have to toggle createwizard afterwards. For now,
} else if (editorCapability) { // We will disable this.
//otherwise, use the save as action
editorCapability.edit();
return newObject.getCapability("action").perform("save-as").then(onSave, onCancel);
}
}; };

View File

@ -34,13 +34,13 @@ define(
* @memberof platform/commonUI/browse * @memberof platform/commonUI/browse
* @constructor * @constructor
*/ */
function CreateWizard(domainObject, parent, policyService) { function CreateWizard(domainObject, parent, openmct) {
this.type = domainObject.getCapability('type'); this.type = domainObject.getCapability('type');
this.model = domainObject.getModel(); this.model = domainObject.getModel();
this.domainObject = domainObject; this.domainObject = domainObject;
this.properties = this.type.getProperties(); this.properties = this.type.getProperties();
this.parent = parent; this.parent = parent;
this.policyService = policyService; this.openmct = openmct;
} }
/** /**
@ -56,15 +56,10 @@ define(
*/ */
CreateWizard.prototype.getFormStructure = function (includeLocation) { CreateWizard.prototype.getFormStructure = function (includeLocation) {
var sections = [], var sections = [],
domainObject = this.domainObject, domainObject = this.domainObject;
policyService = this.policyService;
function validateLocation(parent) { function validateLocation(parent) {
return parent && policyService.allow( return parent && this.openmct.composition.checkPolicy(parent.useCapability('adapter'), domainObject.useCapability('adapter'));
"composition",
parent,
domainObject
);
} }
sections.push({ sections.push({
@ -93,7 +88,7 @@ define(
rows: [{ rows: [{
name: "Save In", name: "Save In",
control: "locator", control: "locator",
validate: validateLocation, validate: validateLocation.bind(this),
key: "createParent" key: "createParent"
}] }]
}); });

View File

@ -1,254 +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/api/objects/object-utils',
'lodash'
],
function (
objectUtils,
_
) {
/**
* Provides initial structure and state (as suitable for provision
* to the `mct-toolbar` directive) for a view's toolbar, based on
* that view's declaration of what belongs in its toolbar and on
* the current selection.
*
* @param $scope the Angular scope
* @param {Object} openmct the openmct object
* @param structure the toolbar structure
* @memberof platform/commonUI/edit
* @constructor
*/
function EditToolbar($scope, openmct, structure) {
this.toolbarStructure = [];
this.properties = [];
this.toolbarState = [];
this.openmct = openmct;
this.domainObjectsById = {};
this.unobserveObjects = [];
this.stateTracker = [];
$scope.$watchCollection(this.getState.bind(this), this.handleStateChanges.bind(this));
$scope.$on("$destroy", this.destroy.bind(this));
this.updateToolbar(structure);
this.registerListeners(structure);
}
/**
* Updates the toolbar with a new structure.
*
* @param {Array} structure the toolbar structure
*/
EditToolbar.prototype.updateToolbar = function (structure) {
var self = this;
function addKey(item) {
self.stateTracker.push({
id: objectUtils.makeKeyString(item.domainObject.identifier),
domainObject: item.domainObject,
property: item.property
});
self.properties.push(item.property);
return self.properties.length - 1; // Return index of property
}
function convertItem(item) {
var converted = Object.create(item || {});
if (item.property) {
converted.key = addKey(item);
}
if (item.method) {
converted.click = function (value) {
item.method(value);
};
}
return converted;
}
// Get initial value for a given property
function initializeState(property) {
var result;
structure.forEach(function (item) {
if (item.property === property) {
result = _.get(item.domainObject, item.property);
}
});
return result;
}
// Tracks the domain object and property for every element in the state array
this.stateTracker = [];
this.toolbarStructure = structure.map(convertItem);
this.toolbarState = this.properties.map(initializeState);
};
/**
* Gets the structure of the toolbar, as appropriate to
* pass to `mct-toolbar`.
*
* @returns {Array} the toolbar structure
*/
EditToolbar.prototype.getStructure = function () {
return this.toolbarStructure;
};
/**
* Gets the current state of the toolbar, as appropriate
* to two-way bind to the state handled by `mct-toolbar`.
*
* @returns {Array} state of the toolbar
*/
EditToolbar.prototype.getState = function () {
return this.toolbarState;
};
/**
* Mutates the domain object's property with a new value.
*
* @param {Object} dominObject the domain object
* @param {string} property the domain object's property to update
* @param value the property's new value
*/
EditToolbar.prototype.updateDomainObject = function (domainObject, property, value) {
this.openmct.objects.mutate(domainObject, property, value);
};
/**
* Updates state with the new value.
*
* @param {number} index the index of the corresponding
* element in the state array
* @param value the new value to update the state array with
*/
EditToolbar.prototype.updateState = function (index, value) {
this.toolbarState[index] = value;
};
/**
* Register listeners for domain objects to watch for updates.
*
* @param {Array} the toolbar structure
*/
EditToolbar.prototype.registerListeners = function (structure) {
var self = this;
function observeObject(domainObject, id) {
var unobserveObject = self.openmct.objects.observe(domainObject, '*', function (newObject) {
self.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject));
self.scheduleStateUpdate();
});
self.unobserveObjects.push(unobserveObject);
}
structure.forEach(function (item) {
var domainObject = item.domainObject;
var id = objectUtils.makeKeyString(domainObject.identifier);
if (!self.domainObjectsById[id]) {
self.domainObjectsById[id] = {
domainObject: domainObject,
properties: []
};
observeObject(domainObject, id);
}
self.domainObjectsById[id].properties.push(item.property);
});
};
/**
* Delays updating the state.
*/
EditToolbar.prototype.scheduleStateUpdate = function () {
if (this.stateUpdateScheduled) {
return;
}
this.stateUpdateScheduled = true;
setTimeout(this.updateStateAfterMutation.bind(this));
};
EditToolbar.prototype.updateStateAfterMutation = function () {
this.stateTracker.forEach(function (state, index) {
if (!this.domainObjectsById[state.id].newObject) {
return;
}
var domainObject = this.domainObjectsById[state.id].domainObject;
var newObject = this.domainObjectsById[state.id].newObject;
var currentValue = _.get(domainObject, state.property);
var newValue = _.get(newObject, state.property);
state.domainObject = newObject;
if (currentValue !== newValue) {
this.updateState(index, newValue);
}
}, this);
Object.values(this.domainObjectsById).forEach(function (tracker) {
if (tracker.newObject) {
tracker.domainObject = tracker.newObject;
}
delete tracker.newObject;
});
this.stateUpdateScheduled = false;
};
/**
* Removes the listeners.
*/
EditToolbar.prototype.deregisterListeners = function () {
this.unobserveObjects.forEach(function (unobserveObject) {
unobserveObject();
});
this.unobserveObjects = [];
};
EditToolbar.prototype.handleStateChanges = function (state) {
(state || []).map(function (newValue, index) {
var domainObject = this.stateTracker[index].domainObject;
var property = this.stateTracker[index].property;
var currentValue = _.get(domainObject, property);
if (currentValue !== newValue) {
this.updateDomainObject(domainObject, property, newValue);
}
}, this);
};
EditToolbar.prototype.destroy = function () {
this.deregisterListeners();
};
return EditToolbar;
}
);

View File

@ -1,184 +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.
*****************************************************************************/
/*global describe,it,expect,beforeEach,jasmine*/
define(
["../../src/controllers/ElementsController"],
function (ElementsController) {
describe("The Elements Pane controller", function () {
var mockScope,
mockOpenMCT,
mockSelection,
mockDomainObject,
mockMutationCapability,
mockCompositionCapability,
mockCompositionObjects,
mockComposition,
mockUnlisten,
selectable = [],
controller;
function mockPromise(value) {
return {
then: function (thenFunc) {
return mockPromise(thenFunc(value));
}
};
}
function createDomainObject() {
return {
useCapability: function () {
return mockCompositionCapability;
}
};
}
beforeEach(function () {
mockComposition = ["a", "b"];
mockCompositionObjects = mockComposition.map(createDomainObject);
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockUnlisten = jasmine.createSpy('unlisten');
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
"listen"
]);
mockMutationCapability.listen.and.returnValue(mockUnlisten);
mockDomainObject = jasmine.createSpyObj("domainObject", [
"getCapability",
"useCapability"
]);
mockDomainObject.useCapability.and.returnValue(mockCompositionCapability);
mockDomainObject.getCapability.and.returnValue(mockMutationCapability);
mockScope = jasmine.createSpyObj("$scope", ['$on']);
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.and.returnValue([]);
mockOpenMCT = {
selection: mockSelection
};
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
spyOn(ElementsController.prototype, 'refreshComposition').and.callThrough();
controller = new ElementsController(mockScope, mockOpenMCT);
});
function getModel(model) {
return function () {
return model;
};
}
it("filters objects in elements pool based on input text and" +
" object name", function () {
var objects = [
{
getModel: getModel({name: "first element"})
},
{
getModel: getModel({name: "second element"})
},
{
getModel: getModel({name: "third element"})
},
{
getModel: getModel({name: "THIRD Element 1"})
}
];
mockScope.filterBy("third element");
expect(objects.filter(mockScope.searchElements).length).toBe(2);
mockScope.filterBy("element");
expect(objects.filter(mockScope.searchElements).length).toBe(4);
});
it("refreshes composition on selection", function () {
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject);
});
it("listens on mutation and refreshes composition", function () {
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation');
expect(mockMutationCapability.listen).toHaveBeenCalled();
expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(1);
mockMutationCapability.listen.calls.mostRecent().args[0](mockDomainObject);
expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(2);
});
it("cleans up mutation listener when selection changes", function () {
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockMutationCapability.listen).toHaveBeenCalled();
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockUnlisten).toHaveBeenCalled();
});
it("does not listen on mutation for element proxy selectable", function () {
selectable[0] = {
context: {
elementProxy: {}
}
};
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
});
it("checks concurrent changes to composition", function () {
var secondMockComposition = ["a", "b", "c"],
secondMockCompositionObjects = secondMockComposition.map(createDomainObject),
firstCompositionCallback,
secondCompositionCallback;
spyOn(mockCompositionCapability, "then").and.callThrough();
controller.refreshComposition(mockDomainObject);
controller.refreshComposition(mockDomainObject);
firstCompositionCallback = mockCompositionCapability.then.calls.all()[0].args[0];
secondCompositionCallback = mockCompositionCapability.then.calls.all()[1].args[0];
secondCompositionCallback(secondMockCompositionObjects);
firstCompositionCallback(mockCompositionObjects);
expect(mockScope.composition).toBe(secondMockCompositionObjects);
});
});
}
);

View File

@ -1,105 +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.
*****************************************************************************/
/**
* MCTRepresentationSpec. Created by ahenry on 01/21/14.
*/
define(
["../../src/creation/AddActionProvider"],
function (AddActionProvider) {
describe("The add action provider", function () {
var mockTypeService,
mockDialogService,
mockPolicyService,
mockTypeMap,
mockTypes,
mockDomainObject,
mockQ,
provider;
function createMockType(name) {
var mockType = jasmine.createSpyObj(
"type" + name,
[
"getKey",
"getGlyph",
"getCssClass",
"getName",
"getDescription",
"getProperties",
"getInitialModel",
"hasFeature"
]
);
mockType.hasFeature.and.returnValue(true);
mockType.getName.and.returnValue(name);
mockType.getKey.and.returnValue(name);
return mockType;
}
beforeEach(function () {
mockTypeService = jasmine.createSpyObj(
"typeService",
["getType"]
);
mockDialogService = {};
mockPolicyService = {};
mockDomainObject = {};
mockTypes = [
"timeline",
"activity",
"other"
].map(createMockType);
mockTypeMap = {};
mockTypes.forEach(function (type) {
mockTypeMap[type.getKey()] = type;
});
mockTypeService.getType.and.callFake(function (key) {
return mockTypeMap[key];
});
provider = new AddActionProvider(
mockQ,
mockTypeService,
mockDialogService,
mockPolicyService
);
});
it("provides actions for timeline and activity", function () {
var actions = provider.getActions({
key: "add",
domainObject: mockDomainObject
});
expect(actions.length).toBe(2);
expect(actions[0].metadata.type).toBe('timeline');
expect(actions[1].metadata.type).toBe('activity');
// Make sure it was creation which was used to check
});
});
}
);

View File

@ -1,75 +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/representers/EditToolbar'],
function (EditToolbar) {
describe("An Edit mode toolbar", function () {
var mockOpenMCT,
mockScope,
mockObjects,
mockDomainObject,
testStructure,
toolbar;
beforeEach(function () {
mockOpenMCT = jasmine.createSpy('openmct', ['objects']);
mockObjects = jasmine.createSpyObj('objects', ['observe']);
mockObjects.observe.and.returnValue();
mockOpenMCT.objects = mockObjects;
mockScope = jasmine.createSpyObj("$scope", [
"$watchCollection",
"$on"
]);
mockScope.$watchCollection.and.returnValue();
mockDomainObject = jasmine.createSpyObj("domainObject", [
'identifier'
]);
testStructure = [
{ name: "A", property: "a", domainObject: mockDomainObject },
{ name: "B", property: "b", domainObject: mockDomainObject },
{ name: "C", property: "c", domainObject: mockDomainObject },
{ name: "X", property: "x", domainObject: mockDomainObject },
{ name: "Y", property: "y", domainObject: mockDomainObject },
{ name: "Z", property: "z", domainObject: mockDomainObject },
{ name: "M", method: "m", domainObject: mockDomainObject }
];
toolbar = new EditToolbar(mockScope, mockOpenMCT, testStructure);
});
it("adds click functions when a method is specified", function () {
var structure = toolbar.getStructure();
expect(structure[6].click).toBeDefined();
});
it("adds key for controls that define a property", function () {
var structure = toolbar.getStructure();
expect(structure[0].key).toEqual(0);
});
});
}
);

View File

@ -1,10 +1,10 @@
<span class="l-progress-bar s-progress-bar" <span class="l-progress-bar s-progress-bar"
ng-class="{ indeterminate:ngModel.unknownProgress }"> ng-class="{ indeterminate:ngModel.progressPerc === 'unknown' }">
<span class="progress-amt-holder"> <span class="progress-amt-holder">
<span class="progress-amt" style="width: {{ngModel.progress}}%"></span> <span class="progress-amt" style="width: {{ngModel.progressPerc === 'unknown' ? 100 : ngModel.progressPerc}}%"></span>
</span> </span>
</span> </span>
<div class="progress-info hint" ng-hide="ngModel.progressText === undefined"> <div class="progress-info hint" ng-hide="ngModel.progressText === undefined">
<span class="progress-amt-text" ng-show="ngModel.progress > 0">{{ngModel.progress}}% complete. </span> <span class="progress-amt-text" ng-show="ngModel.progressPerc !== 'unknown' && ngModel.progressPerc > 0">{{ngModel.progressPerc}}% complete. </span>
{{ngModel.progressText}} {{ngModel.progressText}}
</div> </div>

View File

@ -50,7 +50,7 @@ define(
}; };
$scope.dismiss = function (notification, $event) { $scope.dismiss = function (notification, $event) {
$event.stopPropagation(); $event.stopPropagation();
notification.dismissOrMinimize(); notification.dismiss();
}; };
$scope.maximize = function (notification) { $scope.maximize = function (notification) {
if (notification.model.severity !== "info") { if (notification.model.severity !== "info") {

View File

@ -23,11 +23,13 @@
define([ define([
"./src/NotificationIndicatorController", "./src/NotificationIndicatorController",
"./src/NotificationIndicator", "./src/NotificationIndicator",
"./src/NotificationService",
"./res/notification-indicator.html", "./res/notification-indicator.html",
'legacyRegistry' 'legacyRegistry'
], function ( ], function (
NotificationIndicatorController, NotificationIndicatorController,
NotificationIndicator, NotificationIndicator,
NotificationService,
notificationIndicatorTemplate, notificationIndicatorTemplate,
legacyRegistry legacyRegistry
) { ) {
@ -46,7 +48,7 @@ define([
"implementation": NotificationIndicatorController, "implementation": NotificationIndicatorController,
"depends": [ "depends": [
"$scope", "$scope",
"notificationService", "openmct",
"dialogService" "dialogService"
] ]
} }
@ -61,7 +63,7 @@ define([
{ {
"key": "notificationService", "key": "notificationService",
"implementation": function (openmct) { "implementation": function (openmct) {
return openmct.notifications; return new NotificationService.default(openmct);
}, },
"depends": [ "depends": [
"openmct" "openmct"

View File

@ -35,9 +35,9 @@ define(
* @param dialogService * @param dialogService
* @constructor * @constructor
*/ */
function NotificationIndicatorController($scope, notificationService, dialogService) { function NotificationIndicatorController($scope, openmct, dialogService) {
$scope.notifications = notificationService.notifications; $scope.notifications = openmct.notifications.notifications;
$scope.highest = notificationService.highest; $scope.highest = openmct.notifications.highest;
/** /**
* Launch a dialog showing a list of current notifications. * Launch a dialog showing a list of current notifications.
@ -48,7 +48,7 @@ define(
title: "Messages", title: "Messages",
//Launch the message list dialog with the models //Launch the message list dialog with the models
// from the notifications // from the notifications
messages: notificationService.notifications messages: openmct.notifications.notifications
} }
}); });

View File

@ -0,0 +1,64 @@
/*****************************************************************************
* 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 NotificationService {
constructor(openmct) {
this.openmct = openmct;
}
info(message) {
if (typeof message === 'string') {
return this.openmct.notifications.info(message);
} else {
if (message.hasOwnProperty('progress')) {
return this.openmct.notifications.progress(message.title, message.progress, message.progressText);
} else {
return this.openmct.notifications.info(message.title);
}
}
}
alert(message) {
if (typeof message === 'string') {
return this.openmct.notifications.alert(message);
} else {
return this.openmct.notifications.alert(message.title);
}
}
error(message) {
if (typeof message === 'string') {
return this.openmct.notifications.error(message);
} else {
return this.openmct.notifications.error(message.title);
}
}
notify(options) {
switch (options.severity) {
case 'info':
return this.info(options);
case 'alert':
return this.alert(options);
case 'error':
return this.error(options);
}
}
getAllNotifications() {
return this.openmct.notifications.notifications;
}
}

View File

@ -58,7 +58,8 @@ define([
"category": "action", "category": "action",
"implementation": ComposeActionPolicy, "implementation": ComposeActionPolicy,
"depends": [ "depends": [
"$injector" "$injector",
"openmct"
], ],
"message": "Objects of this type cannot contain objects of that type." "message": "Objects of this type cannot contain objects of that type."
}, },

View File

@ -36,10 +36,11 @@ define(
* @memberof platform/containment * @memberof platform/containment
* @implements {Policy.<Action, ActionContext>} * @implements {Policy.<Action, ActionContext>}
*/ */
function ComposeActionPolicy($injector) { function ComposeActionPolicy($injector, openmct) {
this.getPolicyService = function () { this.getPolicyService = function () {
return $injector.get('policyService'); return $injector.get('policyService');
}; };
this.openmct = openmct;
} }
ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) { ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) {
@ -49,11 +50,8 @@ define(
// ...and delegate to the composition policy // ...and delegate to the composition policy
return containerObject.getId() !== selectedObject.getId() && return containerObject.getId() !== selectedObject.getId() &&
this.policyService.allow( this.openmct.composition.checkPolicy(containerObject.useCapability('adapter'),
'composition', selectedObject.useCapability('adapter'));
containerObject,
selectedObject
);
}; };
/** /**

View File

@ -170,7 +170,7 @@ define([
"description": "Provides a service for moving objects", "description": "Provides a service for moving objects",
"implementation": MoveService, "implementation": MoveService,
"depends": [ "depends": [
"policyService", "openmct",
"linkService", "linkService",
"$q" "$q"
] ]
@ -181,7 +181,7 @@ define([
"description": "Provides a service for linking objects", "description": "Provides a service for linking objects",
"implementation": LinkService, "implementation": LinkService,
"depends": [ "depends": [
"policyService" "openmct"
] ]
}, },
{ {
@ -192,7 +192,7 @@ define([
"depends": [ "depends": [
"$q", "$q",
"policyService", "policyService",
"now" "openmct"
] ]
}, },
{ {

View File

@ -33,9 +33,10 @@ define(
* @memberof platform/entanglement * @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService} * @implements {platform/entanglement.AbstractComposeService}
*/ */
function CopyService($q, policyService) { function CopyService($q, policyService, openmct) {
this.$q = $q; this.$q = $q;
this.policyService = policyService; this.policyService = policyService;
this.openmct = openmct;
} }
CopyService.prototype.validate = function (object, parentCandidate) { CopyService.prototype.validate = function (object, parentCandidate) {
@ -45,11 +46,7 @@ define(
if (parentCandidate.getId() === object.getId()) { if (parentCandidate.getId() === object.getId()) {
return false; return false;
} }
return this.policyService.allow( return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter'));
"composition",
parentCandidate,
object
);
}; };
/** /**

View File

@ -32,8 +32,8 @@ define(
* @memberof platform/entanglement * @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService} * @implements {platform/entanglement.AbstractComposeService}
*/ */
function LinkService(policyService) { function LinkService(openmct) {
this.policyService = policyService; this.openmct = openmct;
} }
LinkService.prototype.validate = function (object, parentCandidate) { LinkService.prototype.validate = function (object, parentCandidate) {
@ -49,11 +49,7 @@ define(
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) { if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
return false; return false;
} }
return this.policyService.allow( return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter'));
"composition",
parentCandidate,
object
);
}; };
LinkService.prototype.perform = function (object, parentObject) { LinkService.prototype.perform = function (object, parentObject) {

View File

@ -31,8 +31,8 @@ define(
* @memberof platform/entanglement * @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService} * @implements {platform/entanglement.AbstractComposeService}
*/ */
function MoveService(policyService, linkService) { function MoveService(openmct, linkService) {
this.policyService = policyService; this.openmct = openmct;
this.linkService = linkService; this.linkService = linkService;
} }
@ -53,10 +53,9 @@ define(
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) { if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
return false; return false;
} }
return this.policyService.allow( return this.openmct.composition.checkPolicy(
"composition", parentCandidate.useCapability('adapter'),
parentCandidate, object.useCapability('adapter')
object
); );
}; };

View File

@ -1,399 +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([
"../layout/res/templates/fixed.html",
'legacyRegistry'
], function (
fixedTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/fixed", {
"name": "Fixed position components.",
"description": "Plug in adding Fixed Position object type.",
"extensions": {
"views": [
{
"key": "fixed-display",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"type": "telemetry.fixed",
"template": fixedTemplate,
"uses": [],
"editable": true
}
],
"toolbars": [
{
name: "Fixed Position Toolbar",
key: "fixed.position",
description: "Toolbar for the selected element inside a fixed position display.",
forSelection: function (selection) {
if (!selection) {
return;
}
return (
selection[0] && selection[0].context.elementProxy &&
selection[1] && selection[1].context.item.type === 'telemetry.fixed' ||
selection[0] && selection[0].context.item.type === 'telemetry.fixed'
);
},
toolbar: function (selection) {
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"];
var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"];
var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"];
var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"];
var fixedPageProperties = ["add"];
var properties = [],
fixedItem = selection[0] && selection[0].context.item,
elementProxy = selection[0] && selection[0].context.elementProxy,
domainObject = selection[1] && selection[1].context.item,
path;
if (elementProxy) {
var type = elementProxy.element.type;
path = "configuration['fixed-display'].elements[" + elementProxy.index + "]";
properties =
type === 'fixed.image' ? imageProperties :
type === 'fixed.text' ? textProperties :
type === 'fixed.box' ? boxProperties :
type === 'fixed.line' ? lineProperties :
type === 'fixed.telemetry' ? telemetryProperties : [];
} else if (fixedItem) {
properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties;
}
return [
{
control: "menu-button",
domainObject: domainObject || selection[0].context.item,
method: function (value) {
selection[0].context.fixedController.add(value);
},
key: "add",
cssClass: "icon-plus",
text: "Add",
options: [
{
"name": "Box",
"cssClass": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"cssClass": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"cssClass": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"cssClass": "icon-image",
"key": "fixed.image"
}
]
},
{
control: "menu-button",
domainObject: domainObject,
method: function (value) {
selection[0].context.fixedController.order(
selection[0].context.elementProxy,
value
);
},
key: "order",
cssClass: "icon-layers",
title: "Layering",
description: "Move the selected object above or below other objects",
options: [
{
"name": "Move to Top",
"cssClass": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"cssClass": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"cssClass": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"cssClass": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
control: "color",
domainObject: domainObject,
property: path + ".fill",
cssClass: "icon-paint-bucket",
title: "Fill color",
description: "Set fill color",
key: 'fill'
},
{
control: "color",
domainObject: domainObject,
property: path + ".stroke",
cssClass: "icon-line-horz",
title: "Border color",
description: "Set border color",
key: 'stroke'
},
{
control: "dialog-button",
domainObject: domainObject,
property: path + ".url",
cssClass: "icon-image",
title: "Image Properties",
description: "Edit image properties",
key: 'url',
dialog: {
control: "textfield",
name: "Image URL",
cssClass: "l-input-lg",
required: true
}
},
{
control: "color",
domainObject: domainObject,
property: path + ".color",
cssClass: "icon-T",
title: "Text color",
mandatory: true,
description: "Set text color",
key: 'color'
},
{
control: "select",
domainObject: domainObject,
property: path + ".size",
title: "Text size",
description: "Set text size",
"options": [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
return { "name": size + " px", "value": size + "px" };
}),
key: 'size'
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x",
text: "X",
name: "X",
key: "x",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y",
text: "Y",
name: "Y",
key: "y",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x",
text: "X1",
name: "X1",
key: "x1",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y",
text: "Y1",
name: "Y1",
key: "y1",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x2",
text: "X2",
name: "X2",
key: "x2",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y2",
text: "Y2",
name: "Y2",
key: "y2",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".height",
text: "H",
name: "H",
key: "height",
cssClass: "l-input-sm",
description: "Resize object height",
min: "1"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".width",
text: "W",
name: "W",
key: "width",
cssClass: "l-input-sm",
description: "Resize object width",
min: "1"
},
{
control: "checkbox",
domainObject: domainObject,
property: path + ".useGrid",
name: "Snap to Grid",
key: "useGrid"
},
{
control: "dialog-button",
domainObject: domainObject,
property: path + ".text",
cssClass: "icon-gear",
title: "Text Properties",
description: "Edit text properties",
key: "text",
dialog: {
control: "textfield",
name: "Text",
required: true
}
},
{
control: "checkbox",
domainObject: domainObject,
property: path + ".titled",
name: "Show Title",
key: "titled"
},
{
control: "button",
domainObject: domainObject,
method: function () {
selection[0].context.fixedController.remove(
selection[0].context.elementProxy
);
},
key: "remove",
cssClass: "icon-trash"
}
].filter(function (item) {
var filtered;
properties.forEach(function (property) {
if (item.property && item.key === property ||
item.method && item.key === property) {
filtered = item;
}
});
return filtered;
});
}
}
],
"types": [
{
"key": "telemetry.fixed",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"description": "Collect and display telemetry elements in " +
"alphanumeric format in a simple canvas workspace. " +
"Elements can be positioned and sized. " +
"Lines, boxes and images can be added as well.",
"priority": 899,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"layoutGrid": [64, 16],
"composition": []
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",
"property": "layoutGrid",
"conversion": "number[]"
}
],
"views": [
"fixed-display"
]
}
]
}
});
});

View File

@ -0,0 +1,475 @@
/*****************************************************************************
* 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/FixedController",
"./templates/fixed.html",
"./templates/frame.html",
"./templates/elements/telemetry.html",
"./templates/elements/box.html",
"./templates/elements/line.html",
"./templates/elements/text.html",
"./templates/elements/image.html",
"legacyRegistry"
], function (
FixedController,
fixedTemplate,
frameTemplate,
telemetryTemplate,
boxTemplate,
lineTemplate,
textTemplate,
imageTemplate,
legacyRegistry
) {
return function() {
return function (openmct) {
openmct.legacyRegistry.register("platform/features/fixed", {
"name": "Fixed position components.",
"description": "Plug in adding Fixed Position object type.",
"extensions": {
"views": [
{
"key": "fixed-display",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"type": "telemetry.fixed",
"template": fixedTemplate,
"uses": ["composition"],
"editable": true
}
],
"templates": [
{
"key": "fixed.telemetry",
"template": telemetryTemplate
},
{
"key": "fixed.box",
"template": boxTemplate
},
{
"key": "fixed.line",
"template": lineTemplate
},
{
"key": "fixed.text",
"template": textTemplate
},
{
"key": "fixed.image",
"template": imageTemplate
}
],
"controllers": [
{
"key": "FixedController",
"implementation": FixedController,
"depends": [
"$scope",
"$q",
"dialogService",
"openmct",
"$element"
]
}
],
"toolbars": [
{
name: "Fixed Position Toolbar",
key: "fixed.position",
description: "Toolbar for the selected element inside a fixed position display.",
forSelection: function (selection) {
if (!selection) {
return;
}
return (openmct.editor.isEditing() &&
(selection[0] && selection[0].context.elementProxy &&
selection[1] && selection[1].context.item.type === 'telemetry.fixed' ||
selection[0] && selection[0].context.item.type === 'telemetry.fixed'));
},
toolbar: function (selection) {
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"];
var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"];
var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"];
var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"];
var fixedPageProperties = ["add"];
var properties = [],
fixedItem = selection[0] && selection[0].context.item,
elementProxy = selection[0] && selection[0].context.elementProxy,
domainObject = selection[1] && selection[1].context.item,
path;
if (elementProxy) {
var type = elementProxy.element.type;
path = "configuration['fixed-display'].elements[" + elementProxy.index + "]";
properties =
type === 'fixed.image' ? imageProperties :
type === 'fixed.text' ? textProperties :
type === 'fixed.box' ? boxProperties :
type === 'fixed.line' ? lineProperties :
type === 'fixed.telemetry' ? telemetryProperties : [];
} else if (fixedItem) {
properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties;
}
return [
{
control: "menu",
domainObject: domainObject || selection[0].context.item,
method: function (option) {
selection[0].context.fixedController.add(option.key);
},
key: "add",
icon: "icon-plus",
label: "Add",
options: [
{
"name": "Box",
"class": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"class": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"class": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"class": "icon-image",
"key": "fixed.image"
}
]
},
{
control: "menu",
domainObject: domainObject,
method: function (option) {
console.log('option', option)
selection[0].context.fixedController.order(
selection[0].context.elementProxy,
option.key
);
},
key: "order",
icon: "icon-layers",
title: "Move the selected object above or below other objects",
options: [
{
"name": "Move to Top",
"class": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"class": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"class": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"class": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
control: "color-picker",
domainObject: domainObject,
property: path + ".fill",
icon: "icon-paint-bucket",
title: "Set fill color",
key: 'fill'
},
{
control: "color-picker",
domainObject: domainObject,
property: path + ".stroke",
icon: "icon-line-horz",
title: "Set border color",
key: 'stroke'
},
{
control: "button",
domainObject: domainObject,
property: path + ".url",
icon: "icon-image",
title: "Edit image properties",
key: 'url',
dialog: {
control: "input",
type: "text",
name: "Image URL",
class: "l-input-lg",
required: true
}
},
{
control: "color-picker",
domainObject: domainObject,
property: path + ".color",
icon: "icon-T",
mandatory: true,
title: "Set text color",
key: 'color'
},
{
control: "select-menu",
domainObject: domainObject,
property: path + ".size",
title: "Set text size",
key: 'size',
options: [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
return { "value": size + "px"};
})
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".x",
label: "X",
title: "X position",
key: "x",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".y",
label: "Y",
title: "Y position",
key: "y",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".x",
label: "X1",
title: "X1 position",
key: "x1",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".y",
label: "Y1",
title: "Y1 position",
key: "y1",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".x2",
label: "X2",
title: "X2 position",
key: "x2",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".y2",
label: "Y2",
title: "Y2 position",
key: "y2",
class: "l-input-sm",
min: "0"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".height",
label: "H",
title: "Resize object height",
key: "height",
class: "l-input-sm",
min: "1"
},
{
control: "input",
type: "number",
domainObject: domainObject,
property: path + ".width",
label: "W",
title: "Resize object width",
key: "width",
class: "l-input-sm",
min: "1"
},
{
control: "toggle-button",
domainObject: domainObject,
property: path + ".useGrid",
key: "useGrid",
options: [
{
value: true,
icon: 'icon-grid-snap-to',
title: 'Snap to grid'
},
{
value: false,
icon: 'icon-grid-snap-no',
title: "Do not snap to grid"
}
]
},
{
control: "button",
domainObject: domainObject,
property: path + ".text",
icon: "icon-gear",
title: "Edit text properties",
key: "text",
dialog: {
control: "input",
type: "text",
name: "Text",
required: true
}
},
{
control: "toggle-button",
domainObject: domainObject,
property: path + ".titled",
key: "titled",
options: [
{
value: true,
icon: 'icon-two-parts-both',
title: 'Show label'
},
{
value: false,
icon: 'icon-two-parts-one-only',
title: "Hide label"
}
]
},
{
control: "button",
domainObject: domainObject,
method: function () {
selection[0].context.fixedController.remove(
selection[0].context.elementProxy
);
},
key: "remove",
icon: "icon-trash"
}
].filter(function (item) {
var filtered;
properties.forEach(function (property) {
if (item.property && item.key === property ||
item.method && item.key === property) {
filtered = item;
}
});
return filtered;
});
}
}
],
"types": [
{
"key": "telemetry.fixed",
"name": "Fixed Position Display",
"cssClass": "icon-box-with-dashed-lines",
"description": "Collect and display telemetry elements in " +
"alphanumeric format in a simple canvas workspace. " +
"Elements can be positioned and sized. " +
"Lines, boxes and images can be added as well.",
"priority": 899,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"layoutGrid": [64, 16],
"composition": []
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",
"property": "layoutGrid",
"conversion": "number[]"
}
],
"views": [
"fixed-display"
]
}
]
}
});
openmct.legacyRegistry.enable("platform/features/fixed");
}
}
});

View File

@ -225,6 +225,8 @@ define(
this.openmct.time.on("bounds", updateDisplayBounds); this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', this.setSelection.bind(this)); this.openmct.selection.on('change', this.setSelection.bind(this));
this.openmct.editor.on('isEditing', this.handleEditing.bind(this));
this.$element.on('click', this.bypassSelection.bind(this)); this.$element.on('click', this.bypassSelection.bind(this));
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) { this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
this.newDomainObject = JSON.parse(JSON.stringify(obj)); this.newDomainObject = JSON.parse(JSON.stringify(obj));
@ -421,6 +423,7 @@ define(
this.openmct.selection.off("change", this.setSelection); this.openmct.selection.off("change", this.setSelection);
this.composition.off('add', this.onCompositionAdd, this); this.composition.off('add', this.onCompositionAdd, this);
this.composition.off('remove', this.onCompositionRemove, this); this.composition.off('remove', this.onCompositionRemove, this);
this.openmct.editor.off('isEditing', this.handleEditing, this);
}; };
/** /**
@ -706,6 +709,12 @@ define(
this.openmct.objects.mutate(this.newDomainObject, path, value); this.openmct.objects.mutate(this.newDomainObject, path, value);
}; };
FixedController.prototype.handleEditing = function (isEditing) {
// Listen for edit mode changes and update selection if necessary.
// Mainly to ensure fixedController is on the selection context when editing.
this.setSelection(this.openmct.selection.get());
}
return FixedController; return FixedController;
} }
); );

View File

@ -42,7 +42,8 @@ define(
}, },
"fixed.text": { "fixed.text": {
fill: "transparent", fill: "transparent",
stroke: "transparent" stroke: "transparent",
size: "13px"
} }
}, },
DIALOGS = { DIALOGS = {

View File

@ -67,10 +67,6 @@ define(
*/ */
proxy.size = new AccessorMutator(element, 'size'); proxy.size = new AccessorMutator(element, 'size');
if (proxy.size() === undefined) {
proxy.size("13px");
}
return proxy; return proxy;
} }

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -23,18 +23,18 @@
ng-controller="FixedController as controller"> ng-controller="FixedController as controller">
<!-- Background grid --> <!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.bypassSelection($event)"> <div class="l-fixed-position__grid-holder l-grid-holder c-grid" ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x" <div class="c-grid__x l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3" ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div> ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
<div class="l-grid l-grid-y" <div class="c-grid__y l-grid l-grid-y"
ng-if="!controller.getGridSize()[1] < 3" ng-if="!controller.getGridSize()[1] < 3"
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div> ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div> </div>
<!-- Fixed position elements --> <!-- Fixed position elements -->
<div ng-repeat="element in controller.getElements()" <div ng-repeat="element in controller.getElements()"
class="l-fixed-position-item s-selectable s-moveable s-hover-border" class="l-fixed-position-item s-selectable s-moveable is-selectable is-moveable"
ng-style="element.style" ng-style="element.style"
mct-selectable="controller.getContext(element)" mct-selectable="controller.getContext(element)"
mct-init-select="controller.shouldSelect(element)"> mct-init-select="controller.shouldSelect(element)">
@ -44,15 +44,15 @@
</mct-include> </mct-include>
</div> </div>
<!-- Selection highlight, handles --> <!-- Selection highlight, handles -->
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()"> <span class="c-frame-edit" ng-if="controller.isElementSelected()">
<div class="l-fixed-position-item t-edit-handle-holder" <div class="c-frame-edit__move"
mct-drag-down="controller.moveHandle().startDrag()" mct-drag-down="controller.moveHandle().startDrag()"
mct-drag="controller.moveHandle().continueDrag(delta)" mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.endDrag()" mct-drag-up="controller.endDrag()"
ng-style="controller.getSelectedElementStyle()"> ng-style="controller.getSelectedElementStyle()">
</div> </div>
<div ng-repeat="handle in controller.handles()" <div ng-repeat="handle in controller.handles()"
class="l-fixed-position-item-handle edit-corner" class="c-frame-edit__handle c-frame-edit__handle--nwse"
ng-style="handle.style()" ng-style="handle.style()"
mct-drag-down="handle.startDrag()" mct-drag-down="handle.startDrag()"
mct-drag="handle.continueDrag(delta)" mct-drag="handle.continueDrag(delta)"

View File

@ -1,356 +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/LayoutController",
"./src/FixedController",
"./src/LayoutCompositionPolicy",
'./src/MCTTriggerModal',
"./res/templates/layout.html",
"./res/templates/fixed.html",
"./res/templates/frame.html",
"./res/templates/elements/telemetry.html",
"./res/templates/elements/box.html",
"./res/templates/elements/line.html",
"./res/templates/elements/text.html",
"./res/templates/elements/image.html",
'legacyRegistry'
], function (
LayoutController,
FixedController,
LayoutCompositionPolicy,
MCTTriggerModal,
layoutTemplate,
fixedTemplate,
frameTemplate,
telemetryTemplate,
boxTemplate,
lineTemplate,
textTemplate,
imageTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/layout", {
"name": "Layout components.",
"description": "Plug in adding Layout capabilities.",
"extensions": {
"views": [
{
"key": "layout",
"name": "Display Layout",
"cssClass": "icon-layout",
"type": "layout",
"template": layoutTemplate,
"editable": true,
"uses": []
},
{
"key": "fixed",
"name": "Fixed Position",
"cssClass": "icon-box-with-dashed-lines",
"type": "telemetry.panel",
"template": fixedTemplate,
"uses": [
"composition"
],
"toolbar": {
"sections": [
{
"items": [
{
"method": "add",
"cssClass": "icon-plus",
"control": "menu-button",
"text": "Add",
"title": "Add",
"description": "Add new items",
"options": [
{
"name": "Box",
"cssClass": "icon-box",
"key": "fixed.box"
},
{
"name": "Line",
"cssClass": "icon-line-horz",
"key": "fixed.line"
},
{
"name": "Text",
"cssClass": "icon-T",
"key": "fixed.text"
},
{
"name": "Image",
"cssClass": "icon-image",
"key": "fixed.image"
}
]
}
]
},
{
"items": [
{
"method": "order",
"cssClass": "icon-layers",
"control": "menu-button",
"title": "Layering",
"description": "Move the selected object above or below other objects",
"options": [
{
"name": "Move to Top",
"cssClass": "icon-arrow-double-up",
"key": "top"
},
{
"name": "Move Up",
"cssClass": "icon-arrow-up",
"key": "up"
},
{
"name": "Move Down",
"cssClass": "icon-arrow-down",
"key": "down"
},
{
"name": "Move to Bottom",
"cssClass": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
"property": "fill",
"cssClass": "icon-paint-bucket",
"title": "Fill color",
"description": "Set fill color",
"control": "color"
},
{
"property": "stroke",
"cssClass": "icon-line-horz",
"title": "Border color",
"description": "Set border color",
"control": "color"
},
{
"property": "color",
"cssClass": "icon-T",
"title": "Text color",
"description": "Set text color",
"mandatory": true,
"control": "color"
},
{
"property": "url",
"cssClass": "icon-image",
"control": "dialog-button",
"title": "Image Properties",
"description": "Edit image properties",
"dialog": {
"control": "textfield",
"name": "Image URL",
"cssClass": "l-input-lg",
"required": true
}
},
{
"property": "text",
"cssClass": "icon-gear",
"control": "dialog-button",
"title": "Text Properties",
"description": "Edit text properties",
"dialog": {
"control": "textfield",
"name": "Text",
"required": true
}
},
{
"method": "showTitle",
"cssClass": "icon-two-parts-both",
"control": "button",
"title": "Show title",
"description": "Show telemetry element title"
},
{
"method": "hideTitle",
"cssClass": "icon-two-parts-one-only",
"control": "button",
"title": "Hide title",
"description": "Hide telemetry element title"
}
]
},
{
"items": [
{
"method": "remove",
"control": "button",
"cssClass": "icon-trash",
"title": "Delete",
"description": "Delete the selected item"
}
]
}
]
}
}
],
"representations": [
{
"key": "frame",
"template": frameTemplate
}
],
"directives": [
{
"key": "mctTriggerModal",
"implementation": MCTTriggerModal,
"depends": [
"$document"
]
}
],
"controllers": [
{
"key": "LayoutController",
"implementation": LayoutController,
"depends": [
"$scope",
"$element",
"openmct"
]
},
{
"key": "FixedController",
"implementation": FixedController,
"depends": [
"$scope",
"$q",
"dialogService",
"openmct",
"$element"
]
}
],
"templates": [
{
"key": "fixed.telemetry",
"template": telemetryTemplate
},
{
"key": "fixed.box",
"template": boxTemplate
},
{
"key": "fixed.line",
"template": lineTemplate
},
{
"key": "fixed.text",
"template": textTemplate
},
{
"key": "fixed.image",
"template": imageTemplate
}
],
"policies": [
{
"category": "composition",
"implementation": LayoutCompositionPolicy
}
],
"toolbars": [
{
name: "Display Layout Toolbar",
key: "layout",
description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) {
// Apply the layout toolbar if the selected object is inside a layout.
return (selection && selection[1] && selection[1].context.item.type === 'layout');
},
toolbar: function (selection) {
return [
{
control: "checkbox",
name: "Show frame",
domainObject: selection[1].context.item,
property: "configuration.layout.panels[" + selection[0].context.oldItem.id + "].hasFrame"
}
];
}
}
],
"types": [
{
"key": "layout",
"name": "Display Layout",
"cssClass": "icon-layout",
"description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.",
"priority": 900,
"features": "creation",
"model": {
"composition": [],
configuration: {
layout: {
panels: {
}
}
}
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"pattern": "^(\\d*[1-9]\\d*)?$",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"key": "layoutGrid",
"conversion": "number[]"
},
{
"name": "Advanced",
"control": "textfield",
"key": "layoutAdvancedCss",
"cssClass": "l-input-lg"
}
]
}
]
}
});
});

View File

@ -1,82 +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/LayoutCompositionPolicy"],
function (LayoutCompositionPolicy) {
describe("Layout's composition policy", function () {
var mockChild,
mockCandidateObj,
mockCandidate,
mockContext,
candidateType,
contextType,
policy;
beforeEach(function () {
mockChild = jasmine.createSpyObj(
'childObject',
['getCapability']
);
mockCandidate =
jasmine.createSpyObj('candidateType', ['instanceOf']);
mockContext =
jasmine.createSpyObj('contextType', ['instanceOf']);
mockCandidateObj = jasmine.createSpyObj('domainObj', [
'getCapability'
]);
mockCandidateObj.getCapability.and.returnValue(mockCandidate);
mockChild.getCapability.and.returnValue(mockContext);
mockCandidate.instanceOf.and.callFake(function (t) {
return t === candidateType;
});
mockContext.instanceOf.and.callFake(function (t) {
return t === contextType;
});
policy = new LayoutCompositionPolicy();
});
it("disallows folders in layouts", function () {
candidateType = 'layout';
contextType = 'folder';
expect(policy.allow(mockCandidateObj, mockChild)).toBe(false);
});
it("does not disallow folders elsewhere", function () {
candidateType = 'nonlayout';
contextType = 'folder';
expect(policy.allow(mockCandidateObj, mockChild)).toBe(true);
});
it("allows things other than folders in layouts", function () {
candidateType = 'layout';
contextType = 'nonfolder';
expect(policy.allow(mockCandidateObj, mockChild)).toBe(true);
});
});
}
);

View File

@ -22,7 +22,6 @@
define([ define([
"./src/MCTForm", "./src/MCTForm",
"./src/MCTToolbar",
"./src/MCTControl", "./src/MCTControl",
"./src/MCTFileInput", "./src/MCTFileInput",
"./src/FileInputService", "./src/FileInputService",
@ -48,7 +47,6 @@ define([
'legacyRegistry' 'legacyRegistry'
], function ( ], function (
MCTForm, MCTForm,
MCTToolbar,
MCTControl, MCTControl,
MCTFileInput, MCTFileInput,
FileInputService, FileInputService,
@ -83,10 +81,6 @@ define([
"key": "mctForm", "key": "mctForm",
"implementation": MCTForm "implementation": MCTForm
}, },
{
"key": "mctToolbar",
"implementation": MCTToolbar
},
{ {
"key": "mctControl", "key": "mctControl",
"implementation": MCTControl, "implementation": MCTControl,

View File

@ -1,49 +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.
-->
<form novalidate>
<div class="tool-bar btn-bar contents abs">
<span ng-repeat="item in structure">
<span ng-if="item.control === 'divider'" class="l-control-group">
</span>
<ng-form ng-class="{ 'input-labeled': item.name }"
ng-hide="item.hidden"
ng-if="item.control !== 'divider'"
class="inline"
title="{{item.description}}"
name="mctFormInner">
<label ng-if="item.name">
{{item.name}}:
</label>
<mct-control key="item.control"
ng-class="{ disabled: item.disabled }"
ng-model="ngModel"
ng-required="item.required"
ng-pattern="getRegExp(item.pattern)"
options="item.options"
structure="item"
field="item.key">
</mct-control>
</ng-form>
</span>
</div>
</form>

View File

@ -1,69 +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.
*****************************************************************************/
/**
* Module defining MCTForm. Created by vwoeltje on 11/10/14.
*/
define(
[
"./MCTForm",
"../res/templates/toolbar.html",
"./controllers/ToolbarController"
],
function (
MCTForm,
toolbarTemplate,
ToolbarController
) {
/**
* The mct-toolbar directive allows generation of displayable
* forms based on a declarative description of the form's
* structure.
*
* This directive accepts three attributes:
*
* * `ng-model`: The model for the form; where user input
* will be stored.
* * `structure`: The declarative structure of the toolbar.
* Describes what controls should be shown and where
* their values should be read/written in the model.
* * `name`: The name under which to expose the form's
* dirty/valid state. This is similar to ng-form's use
* of name, except this will be made available in the
* parent scope.
*
* @memberof platform/forms
* @constructor
*/
function MCTToolbar() {
// Use Directive Definition Object from mct-form,
// but use the toolbar's template and controller instead.
var ddo = new MCTForm();
ddo.template = toolbarTemplate;
ddo.controller = ['$scope', 'openmct', ToolbarController];
return ddo;
}
return MCTToolbar;
}
);

View File

@ -1,84 +0,0 @@
define(
[
'../../../commonUI/edit/src/representers/EditToolbar'
],
function (EditToolbar) {
// Default ng-pattern; any non whitespace
var NON_WHITESPACE = /\S/;
/**
* Controller for mct-toolbar directive.
*
* @memberof platform/forms
* @constructor
*/
function ToolbarController($scope, openmct) {
var regexps = [];
// ng-pattern seems to want a RegExp, and not a
// string (despite what documentation says) but
// we want toolbar structure to be JSON-expressible,
// so we make RegExp's from strings as-needed
function getRegExp(pattern) {
// If undefined, don't apply a pattern
if (!pattern) {
return NON_WHITESPACE;
}
// Just echo if it's already a regexp
if (pattern instanceof RegExp) {
return pattern;
}
// Otherwise, assume a string
// Cache for easy lookup later (so we don't
// creat a new RegExp every digest cycle)
if (!regexps[pattern]) {
regexps[pattern] = new RegExp(pattern);
}
return regexps[pattern];
}
this.openmct = openmct;
this.$scope = $scope;
$scope.editToolbar = {};
$scope.getRegExp = getRegExp;
$scope.$on("$destroy", this.destroy.bind(this));
openmct.selection.on('change', this.handleSelection.bind(this));
}
ToolbarController.prototype.handleSelection = function (selection) {
var domainObject = selection[0].context.oldItem;
var element = selection[0].context.elementProxy;
if ((domainObject && domainObject === this.selectedObject) || (element && element === this.selectedObject)) {
return;
}
this.selectedObject = domainObject || element;
if (this.editToolbar) {
this.editToolbar.destroy();
}
var structure = this.openmct.toolbars.get(selection) || [];
this.editToolbar = new EditToolbar(this.$scope, this.openmct, structure);
this.$scope.$parent.editToolbar = this.editToolbar;
this.$scope.$parent.editToolbar.structure = this.editToolbar.getStructure();
this.$scope.$parent.editToolbar.state = this.editToolbar.getState();
setTimeout(function () {
this.$scope.$apply();
}.bind(this));
};
ToolbarController.prototype.destroy = function () {
this.openmct.selection.off("change", this.handleSelection);
};
return ToolbarController;
}
);

View File

@ -1,112 +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/MCTToolbar"],
function (MCTToolbar) {
describe("The mct-toolbar directive", function () {
var mockScope,
mockOpenMCT,
mockSelection,
mctToolbar;
function installController() {
var Controller = mctToolbar.controller[2];
return new Controller(mockScope, mockOpenMCT);
}
beforeEach(function () {
mockScope = jasmine.createSpyObj("$scope", [
"$watch",
"$on"
]);
mockScope.$parent = {};
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off'
]);
mockOpenMCT = {
selection: mockSelection
};
mctToolbar = new MCTToolbar();
});
it("is restricted to elements", function () {
expect(mctToolbar.restrict).toEqual("E");
});
it("listens for selection change event", function () {
installController();
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
"change",
jasmine.any(Function)
);
});
it("allows strings to be converted to RegExps", function () {
// This is needed to support ng-pattern in the template
installController();
// Should have added getRegExp to the scope,
// to convert strings to regular expressions
expect(mockScope.getRegExp("^\\d+$")).toEqual(/^\d+$/);
});
it("returns the same regexp instance for the same string", function () {
// Don't want new instances each digest cycle, for performance
var strRegExp = "^[a-z]\\d+$",
regExp;
// Add getRegExp to scope
installController();
regExp = mockScope.getRegExp(strRegExp);
// Same object instance each time...
expect(mockScope.getRegExp(strRegExp)).toBe(regExp);
expect(mockScope.getRegExp(strRegExp)).toBe(regExp);
});
it("passes RegExp objects through untouched", function () {
// Permit using forms to simply provide their own RegExp object
var regExp = /^\d+[a-d]$/;
// Add getRegExp to scope
installController();
// Should have added getRegExp to the scope,
// to convert strings to regular expressions
expect(mockScope.getRegExp(regExp)).toBe(regExp);
});
it("passes a non-whitespace regexp when no pattern is defined", function () {
// If no pattern is supplied, ng-pattern should match anything
installController();
expect(mockScope.getRegExp()).toEqual(/\S/);
expect(mockScope.getRegExp(undefined)).toEqual(/\S/);
});
});
}
);

View File

@ -26,6 +26,7 @@ define([
'uuid', 'uuid',
'./defaultRegistry', './defaultRegistry',
'./api/api', './api/api',
'./api/overlays/OverlayAPI',
'./selection/Selection', './selection/Selection',
'./api/objects/object-utils', './api/objects/object-utils',
'./plugins/plugins', './plugins/plugins',
@ -40,7 +41,6 @@ define([
'./styles-new/core.scss', './styles-new/core.scss',
'./styles-new/notebook.scss', './styles-new/notebook.scss',
'./ui/components/layout/Layout.vue', './ui/components/layout/Layout.vue',
'./ui/overlayService/overlayService',
'vue' 'vue'
], function ( ], function (
EventEmitter, EventEmitter,
@ -48,6 +48,7 @@ define([
uuid, uuid,
defaultRegistry, defaultRegistry,
api, api,
OverlayAPI,
Selection, Selection,
objectUtils, objectUtils,
plugins, plugins,
@ -62,7 +63,6 @@ define([
coreStyles, coreStyles,
NotebookStyles, NotebookStyles,
Layout, Layout,
OverlayService,
Vue Vue
) { ) {
/** /**
@ -185,15 +185,6 @@ define([
*/ */
this.types = new api.TypeRegistry(); this.types = new api.TypeRegistry();
/**
* Utilities for attaching common behaviors to views.
*
* @type {module:openmct.GestureAPI}
* @memberof module:openmct.MCT#
* @name gestures
*/
this.gestures = new api.GestureAPI();
/** /**
* An interface for interacting with domain objects and the domain * An interface for interacting with domain objects and the domain
* object hierarchy. * object hierarchy.
@ -225,11 +216,9 @@ define([
this.notifications = new api.NotificationAPI(); this.notifications = new api.NotificationAPI();
this.Dialog = api.Dialog;
this.editor = new api.EditorAPI.default(this); this.editor = new api.EditorAPI.default(this);
this.OverlayService = new OverlayService(); this.overlays = new OverlayAPI.default();
this.legacyRegistry = defaultRegistry; this.legacyRegistry = defaultRegistry;
this.install(this.plugins.Plot()); this.install(this.plugins.Plot());

View File

@ -28,7 +28,6 @@ define([
'./services/Instantiate', './services/Instantiate',
'./services/MissingModelCompatibilityDecorator', './services/MissingModelCompatibilityDecorator',
'./capabilities/APICapabilityDecorator', './capabilities/APICapabilityDecorator',
'./policies/AdapterCompositionPolicy',
'./policies/AdaptedViewPolicy', './policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer', './runs/AlternateCompositionInitializer',
'./runs/TimeSettingsURLHandler', './runs/TimeSettingsURLHandler',
@ -36,7 +35,8 @@ define([
'./runs/LegacyTelemetryProvider', './runs/LegacyTelemetryProvider',
'./runs/RegisterLegacyTypes', './runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor', './services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews' './views/installLegacyViews',
'./policies/legacyCompositionPolicyAdapter'
], function ( ], function (
legacyRegistry, legacyRegistry,
ActionDialogDecorator, ActionDialogDecorator,
@ -45,7 +45,6 @@ define([
Instantiate, Instantiate,
MissingModelCompatibilityDecorator, MissingModelCompatibilityDecorator,
APICapabilityDecorator, APICapabilityDecorator,
AdapterCompositionPolicy,
AdaptedViewPolicy, AdaptedViewPolicy,
AlternateCompositionInitializer, AlternateCompositionInitializer,
TimeSettingsURLHandler, TimeSettingsURLHandler,
@ -53,7 +52,8 @@ define([
LegacyTelemetryProvider, LegacyTelemetryProvider,
RegisterLegacyTypes, RegisterLegacyTypes,
LegacyObjectAPIInterceptor, LegacyObjectAPIInterceptor,
installLegacyViews installLegacyViews,
legacyCompositionPolicyAdapter
) { ) {
legacyRegistry.register('src/adapter', { legacyRegistry.register('src/adapter', {
"extensions": { "extensions": {
@ -117,11 +117,6 @@ define([
} }
], ],
policies: [ policies: [
{
category: "composition",
implementation: AdapterCompositionPolicy,
depends: ["openmct"]
},
{ {
category: "view", category: "view",
implementation: AdaptedViewPolicy, implementation: AdaptedViewPolicy,
@ -168,6 +163,12 @@ define([
"types[]", "types[]",
"openmct" "openmct"
] ]
},
{
implementation: legacyCompositionPolicyAdapter.default,
depends: [
"openmct"
]
} }
], ],
licenses: [ licenses: [

View File

@ -20,32 +20,23 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define( export default function legacyCompositionPolicyAdapter(openmct) {
[], const instantiate = this.openmct.$injector.get('instantiate');
function () { const policyService = this.openmct.$injector.get('policyService');
/** openmct.composition.addPolicy((parent, child) => {
* Defines composition policy for Display Layout objects.
* They cannot contain folders.
* @constructor
* @memberof platform/features/layout
* @implements {Policy.<View, DomainObject>}
*/
function LayoutCompositionPolicy() {
}
LayoutCompositionPolicy.prototype.allow = function (parent, child) { let parentId = this.openmct.objects.makeKeyString(parent.identifier);
var parentType = parent.getCapability('type'); let childId = this.openmct.objects.makeKeyString(child.identifier);
if (parentType.instanceOf('layout') &&
child.getCapability('type').instanceOf('folder')) {
return false; let legacyParent = instantiate(parent, parentId);
} let legacyChild = instantiate(child, childId);
let result = policyService.allow(
return true; 'composition',
}; legacyParent,
legacyChild
return LayoutCompositionPolicy; );
}
);
return result;
});
}

View File

@ -36,6 +36,9 @@ export default class Editor extends EventEmitter {
* or finish() are called. * or finish() are called.
*/ */
edit() { edit() {
if (this.editing === true) {
throw "Already editing";
}
this.editing = true; this.editing = true;
this.getTransactionService().startTransaction(); this.getTransactionService().startTransaction();
this.emit('isEditing', true); this.emit('isEditing', true);

View File

@ -25,8 +25,6 @@ define([
'./objects/ObjectAPI', './objects/ObjectAPI',
'./composition/CompositionAPI', './composition/CompositionAPI',
'./types/TypeRegistry', './types/TypeRegistry',
'./ui/Dialog',
'./ui/GestureAPI',
'./telemetry/TelemetryAPI', './telemetry/TelemetryAPI',
'./indicators/IndicatorAPI', './indicators/IndicatorAPI',
'./notifications/NotificationAPI', './notifications/NotificationAPI',
@ -37,8 +35,6 @@ define([
ObjectAPI, ObjectAPI,
CompositionAPI, CompositionAPI,
TypeRegistry, TypeRegistry,
Dialog,
GestureAPI,
TelemetryAPI, TelemetryAPI,
IndicatorAPI, IndicatorAPI,
NotificationAPI, NotificationAPI,
@ -48,9 +44,7 @@ define([
TimeAPI: TimeAPI, TimeAPI: TimeAPI,
ObjectAPI: ObjectAPI, ObjectAPI: ObjectAPI,
CompositionAPI: CompositionAPI, CompositionAPI: CompositionAPI,
Dialog: Dialog,
TypeRegistry: TypeRegistry, TypeRegistry: TypeRegistry,
GestureAPI: GestureAPI,
TelemetryAPI: TelemetryAPI, TelemetryAPI: TelemetryAPI,
IndicatorAPI: IndicatorAPI, IndicatorAPI: IndicatorAPI,
NotificationAPI: NotificationAPI.default, NotificationAPI: NotificationAPI.default,

View File

@ -44,7 +44,7 @@ define([
function CompositionAPI(publicAPI) { function CompositionAPI(publicAPI) {
this.registry = []; this.registry = [];
this.policies = []; this.policies = [];
this.addProvider(new DefaultCompositionProvider(publicAPI)); this.addProvider(new DefaultCompositionProvider(publicAPI, this));
this.publicAPI = publicAPI; this.publicAPI = publicAPI;
} }

View File

@ -43,9 +43,34 @@ define([
* @memberof module:openmct * @memberof module:openmct
*/ */
function DefaultCompositionProvider(publicAPI) { function DefaultCompositionProvider(publicAPI, compositionAPI) {
this.publicAPI = publicAPI; this.publicAPI = publicAPI;
this.listeningTo = {}; this.listeningTo = {};
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
this.cannotContainItself = this.cannotContainItself.bind(this);
compositionAPI.addPolicy(this.cannotContainDuplicates);
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
*/
DefaultCompositionProvider.prototype.cannotContainItself = function (parent, child) {
return !(parent.identifier.namespace === child.identifier.namespace &&
parent.identifier.key === child.identifier.key);
} }
/** /**
@ -203,7 +228,7 @@ define([
} }
var oldComposition = listeners.composition.map(objectUtils.makeKeyString); var oldComposition = listeners.composition.map(objectUtils.makeKeyString);
var newComposition = oldDomainObject.getModel().composition; var newComposition = oldDomainObject.getModel().composition.map(objectUtils.makeKeyString);
var added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString); var added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
var removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString); var removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);

View File

@ -32,7 +32,6 @@
*/ */
import moment from 'moment'; import moment from 'moment';
import EventEmitter from 'EventEmitter'; import EventEmitter from 'EventEmitter';
import MCTNotification from './MCTNotification.js';
/** /**
* A representation of a banner notification. Banner notifications * A representation of a banner notification. Banner notifications
@ -42,20 +41,11 @@ import MCTNotification from './MCTNotification.js';
* and then minimized to a banner notification if needed, or vice-versa. * and then minimized to a banner notification if needed, or vice-versa.
* *
* @typedef {object} NotificationModel * @typedef {object} NotificationModel
* @property {string} title The title of the message * @property {string} message The message to be displayed by the notification
* @property {string} severity The importance of the message (one of * @property {number | 'unknown'} [progress] The progres of some ongoing task. Should be a number between 0 and 100, or
* 'info', 'alert', or 'error' where info < alert <error) * with the string literal 'unknown'.
* @property {number} [progress] The completion status of a task * @property {string} [progressText] A message conveying progress of some ongoing task.
* represented numerically
* @property {boolean} [unknownProgress] a boolean indicating that the
* progress of the underlying task is unknown. This will result in a
* visually distinct progress bar.
* @property {boolean} [autoDismiss] If truthy, dialog will
* be automatically minimized or dismissed (depending on severity).
* Additionally, if the provided value is a number, it will be used
* as the delay period before being dismissed.
* @property {boolean} [dismissable=true] If true, notification will
* include an option to dismiss it completely.
* @see DialogModel * @see DialogModel
*/ */
@ -65,14 +55,9 @@ const MINIMIZE_ANIMATION_TIMEOUT = 300;
/** /**
* The notification service is responsible for informing the user of * The notification service is responsible for informing the user of
* events via the use of banner notifications. * events via the use of banner notifications.
* @memberof platform/commonUI/notification * @memberof ui/notification
* @constructor * @constructor */
* @param defaultAutoDismissTimeout The period of time that an
* auto-dismissed message will be displayed for.
* @param minimizeAnimationTimeout When notifications are minimized, a brief
* animation is shown. This animation requires some time to execute,
* so a timeout is required before the notification is hidden
*/
export default class NotificationAPI extends EventEmitter { export default class NotificationAPI extends EventEmitter {
constructor() { constructor() {
super(); super();
@ -86,16 +71,72 @@ export default class NotificationAPI extends EventEmitter {
this.activeNotification = undefined; this.activeNotification = undefined;
} }
/**
* Info notifications are low priority informational messages for the user. They will be auto-destroy after a brief
* period of time.
* @param {string} message The message to display to the user
* @returns {InfoNotification}
*/
info(message) {
let notificationModel = {
message: message,
autoDismiss: true,
severity: "info"
}
return this._notify(notificationModel);
}
/**
* Present an alert to the user.
* @param {string} message The message to display to the user.
* @returns {Notification}
*/
alert(message) {
let notificationModel = {
message: message,
severity: "alert"
}
return this._notify(notificationModel);
}
/**
* Present an error message to the user
* @param {string} message
* @returns {Notification}
*/
error(message) {
let notificationModel = {
message: message,
severity: "error"
}
return this._notify(notificationModel);
}
/**
* Create a new progress notification. These notifications will contain a progress bar.
* @param {string} message
* @param {number | 'unknown'} progressPerc A value between 0 and 100, or the string 'unknown'.
* @param {string} [progressText] Text description of progress (eg. "10 of 20 objects copied").
*/
progress(message, progressPerc, progressText) {
let notificationModel = {
message: message,
progressPerc: progressPerc,
progressText: progressText,
severity: "info"
}
return this._notify(notificationModel);
}
/** /**
* Minimize a notification. The notification will still be available * Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a * from the notification list. Typically notifications with a
* severity of 'info' should not be minimized, but rather * severity of 'info' should not be minimized, but rather
* dismissed. If you're not sure which is appropriate, * dismissed.
* use {@link Notification#dismissOrMinimize}
* *
* @private * @private
*/ */
minimize(notification) { _minimize(notification) {
//Check this is a known notification //Check this is a known notification
let index = this.notifications.indexOf(notification); let index = this.notifications.indexOf(notification);
@ -114,11 +155,12 @@ export default class NotificationAPI extends EventEmitter {
if (index >= 0) { if (index >= 0) {
notification.model.minimized = true; notification.model.minimized = true;
notification.emit('minimized');
//Add a brief timeout before showing the next notification //Add a brief timeout before showing the next notification
// in order to allow the minimize animation to run through. // in order to allow the minimize animation to run through.
setTimeout(() => { setTimeout(() => {
notification.emit('destroy'); notification.emit('destroy');
this.setActiveNotification(this.selectNextNotification()); this._setActiveNotification(this._selectNextNotification());
}, MINIMIZE_ANIMATION_TIMEOUT); }, MINIMIZE_ANIMATION_TIMEOUT);
} }
} }
@ -133,7 +175,7 @@ export default class NotificationAPI extends EventEmitter {
* *
* @private * @private
*/ */
dismiss(notification) { _dismiss(notification) {
//Check this is a known notification //Check this is a known notification
let index = this.notifications.indexOf(notification); let index = this.notifications.indexOf(notification);
@ -153,8 +195,8 @@ export default class NotificationAPI extends EventEmitter {
if (index >= 0) { if (index >= 0) {
this.notifications.splice(index, 1); this.notifications.splice(index, 1);
} }
this.setActiveNotification(this.selectNextNotification()); this._setActiveNotification(this._selectNextNotification());
this.setHighestSeverity(); this._setHighestSeverity();
notification.emit('destroy'); notification.emit('destroy');
} }
@ -164,81 +206,19 @@ export default class NotificationAPI extends EventEmitter {
* *
* @private * @private
*/ */
dismissOrMinimize(notification) { _dismissOrMinimize(notification) {
let model = notification.model; let model = notification.model;
if (model.severity === "info") { if (model.severity === "info") {
if (model.autoDismiss === false) { this._dismiss(notification);
this.minimize(notification);
} else {
this.dismiss(notification);
}
} else { } else {
this.minimize(notification); this._minimize(notification);
} }
} }
/**
* Returns the notification that is currently visible in the banner area
* @returns {Notification}
*/
getActiveNotification() {
return this.activeNotification;
}
/**
* A convenience method for info notifications. Notifications
* created via this method will be auto-destroy after a default
* wait period unless explicitly forbidden by the caller through
* the {autoDismiss} property on the {NotificationModel}, in which
* case the notification will be minimized after the wait.
* @param {NotificationModel | string} message either a string for
* the title of the notification message, or a {@link NotificationModel}
* defining the options notification to display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
info(message) {
let notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "info";
return this.notify(notificationModel);
}
/**
* A convenience method for alert notifications. Notifications
* created via this method will will have severity of "alert" enforced
* @param {NotificationModel | string} message either a string for
* the title of the alert message with default options, or a
* {@link NotificationModel} defining the options notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
alert(message) {
let notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "alert";
return this.notify(notificationModel);
}
/**
* A convenience method for error notifications. Notifications
* created via this method will will have severity of "error" enforced
* @param {NotificationModel | string} message either a string for
* the title of the error message with default options, or a
* {@link NotificationModel} defining the options of the notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
error(message) {
let notificationModel = typeof message === "string" ? {title: message} : message;
notificationModel.severity = "error";
return this.notify(notificationModel);
}
/** /**
* @private * @private
*/ */
setHighestSeverity() { _setHighestSeverity() {
let severity = { let severity = {
"info": 1, "info": 1,
"alert": 2, "alert": 2,
@ -263,23 +243,23 @@ export default class NotificationAPI extends EventEmitter {
* @returns {Notification} the provided notification decorated with * @returns {Notification} the provided notification decorated with
* functions to {@link Notification#dismiss} or {@link Notification#minimize} * functions to {@link Notification#dismiss} or {@link Notification#minimize}
*/ */
notify(notificationModel) { _notify(notificationModel) {
let notification; let notification;
let activeNotification = this.activeNotification; let activeNotification = this.activeNotification;
notificationModel.severity = notificationModel.severity || "info"; notificationModel.severity = notificationModel.severity || "info";
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms'); notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
notification = new MCTNotification(notificationModel, this); notification = this._createNotification(notificationModel);
this.notifications.push(notification); this.notifications.push(notification);
this.setHighestSeverity(); this._setHighestSeverity();
/* /*
Check if there is already an active (ie. visible) notification Check if there is already an active (ie. visible) notification
*/ */
if (!this.activeNotification) { if (!this.activeNotification) {
this.setActiveNotification(notification); this._setActiveNotification(notification);
} else if (!this.activeTimeout) { } else if (!this.activeTimeout) {
/* /*
If there is already an active notification, time it out. If it's If there is already an active notification, time it out. If it's
@ -292,19 +272,38 @@ export default class NotificationAPI extends EventEmitter {
serviced as soon as possible. serviced as soon as possible.
*/ */
this.activeTimeout = setTimeout(() => { this.activeTimeout = setTimeout(() => {
this.dismissOrMinimize(activeNotification); this._dismissOrMinimize(activeNotification);
}, DEFAULT_AUTO_DISMISS_TIMEOUT); }, DEFAULT_AUTO_DISMISS_TIMEOUT);
} }
return notification; return notification;
} }
/** /**
* Used internally by the NotificationService
* @private * @private
*/ */
setActiveNotification(notification) { _createNotification(notificationModel) {
let shouldAutoDismiss; let notification = new EventEmitter();
notification.model = notificationModel;
notification.dismiss = () => {
this._dismiss(notification);
};
if (notificationModel.hasOwnProperty('progressPerc')) {
notification.progress = (progressPerc, progressText) => {
notification.model.progressPerc = progressPerc;
notification.model.progressText = progressText;
notification.emit('progress', progressPerc, progressText);
}
}
return notification;
}
/**
* @private
*/
_setActiveNotification(notification) {
this.activeNotification = notification; this.activeNotification = notification;
if (!notification) { if (!notification) {
@ -313,15 +312,9 @@ export default class NotificationAPI extends EventEmitter {
} }
this.emit('notification', notification); this.emit('notification', notification);
if (notification.model.severity === "info") { if (notification.model.autoDismiss || this._selectNextNotification()) {
shouldAutoDismiss = true;
} else {
shouldAutoDismiss = notification.model.autoDismiss;
}
if (shouldAutoDismiss || this.selectNextNotification()) {
this.activeTimeout = setTimeout(() => { this.activeTimeout = setTimeout(() => {
this.dismissOrMinimize(notification); this._dismissOrMinimize(notification);
}, DEFAULT_AUTO_DISMISS_TIMEOUT); }, DEFAULT_AUTO_DISMISS_TIMEOUT);
} else { } else {
delete this.activeTimeout; delete this.activeTimeout;
@ -333,7 +326,7 @@ export default class NotificationAPI extends EventEmitter {
* *
* @private * @private
*/ */
selectNextNotification() { _selectNextNotification() {
let notification; let notification;
let i = 0; let i = 0;

View File

@ -0,0 +1,33 @@
import DialogComponent from './components/DialogComponent.vue';
import Overlay from './Overlay';
import Vue from 'vue';
class Dialog extends Overlay {
constructor({iconClass, message, title, ...options}) {
let component = new Vue({
provide: {
iconClass,
message,
title
},
components: {
DialogComponent: DialogComponent
},
template: '<dialog-component></dialog-component>'
}).$mount();
super({
element: component.$el,
size: 'fit',
notDismissable: true,
...options
});
this.once('destroy', () => {
component.$destroy();
});
}
}
export default Dialog;

View File

@ -0,0 +1,51 @@
import OverlayComponent from './components/OverlayComponent.vue';
import EventEmitter from 'EventEmitter';
import Vue from 'vue';
const cssClasses = {
large: 'l-overlay-large',
small: 'l-overlay-small',
fit: 'l-overlay-fit'
};
class Overlay extends EventEmitter {
constructor(options) {
super();
this.container = document.createElement('div');
this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]);
this.component = new Vue({
provide: {
dismiss: this.dismiss.bind(this),
element: options.element,
buttons: options.buttons,
notDismissable: options.notDismissable ? true : false
},
components: {
OverlayComponent: OverlayComponent
},
template: '<overlay-component></overlay-component>'
});
if (options.onDestroy) {
this.once('destroy', options.onDestroy);
}
}
dismiss() {
this.emit('destroy');
this.component.$destroy();
document.body.removeChild(this.container);
}
/**
* @private
**/
show() {
document.body.appendChild(this.container);
this.container.appendChild(this.component.$mount().$el);
}
}
export default Overlay;

View File

@ -0,0 +1,115 @@
import Overlay from './Overlay';
import Dialog from './Dialog';
import ProgressDialog from './ProgressDialog';
/**
* The OverlayAPI is responsible for pre-pending templates to
* the body of the document, which is useful for displaying templates
* which need to block the full screen.
*
* @memberof api/overlays
* @constructor
*/
class OverlayAPI {
constructor() {
this.activeOverlays = [];
}
/**
* private
*/
showOverlay(overlay) {
if (this.activeOverlays.length) {
this.activeOverlays[this.activeOverlays.length - 1].container.classList.add('invisible');
}
this.activeOverlays.push(overlay);
overlay.once('destroy', () => {
this.activeOverlays.splice(this.activeOverlays.indexOf(overlay), 1);
if (this.activeOverlays.length) {
this.activeOverlays[this.activeOverlays.length - 1].container.classList.remove('invisible');
}
});
overlay.show();
}
/**
] * A description of option properties that can be passed into the overlay
* @typedef options
* @property {object} element DOMElement that is to be inserted/shown on the overlay
* @property {string} size prefered size of the overlay (large, small, fit)
* @property {array} buttons optional button objects with label and callback properties
* @property {function} onDestroy callback to be called when overlay is destroyed
* @property {boolean} notDismissable to prevent user from dismissing the overlay, calling code
* will need to explicitly dismiss the overlay.
*/
overlay(options) {
let overlay = new Overlay(options);
this.showOverlay(overlay);
return overlay;
}
/**
* Displays a blocking (modal) dialog. This dialog can be used for
* displaying messages that require the user's
* immediate attention.
* @param {model} options defines options for the dialog
* @returns {object} with an object with a dismiss function that can be called from the calling code
* to dismiss/destroy the dialog
*
* A description of the model options that may be passed to the
* dialog method. Note that the DialogModel described
* here is shared with the Notifications framework.
* @see NotificationService
*
* @typedef options
* @property {string} title the title to use for the dialog
* @property {string} iconClass class to apply to icon that is shown on dialog
* @property {string} message text that indicates a current message,
* @property {buttons[]} buttons a list of buttons with title and callback properties that will
* be added to the dialog.
*/
dialog(options) {
let dialog = new Dialog(options);
this.showOverlay(dialog);
return dialog;
}
/**
* Displays a blocking (modal) progress dialog. This dialog can be used for
* displaying messages that require the user's attention, and show progress
* @param {model} options defines options for the dialog
* @returns {object} with an object with a dismiss function that can be called from the calling code
* to dismiss/destroy the dialog and an updateProgress function that takes progressPercentage(Number 0-100)
* and progressText (string)
*
* A description of the model options that may be passed to the
* dialog method. Note that the DialogModel described
* here is shared with the Notifications framework.
* @see NotificationService
*
* @typedef options
* @property {number} progressPerc the initial progress value (0-100) or {string} 'unknown' for anonymous progress
* @property {string} progressText the initial text to be shown under the progress bar
* @property {buttons[]} buttons a list of buttons with title and callback properties that will
* be added to the dialog.
*/
progressDialog(options) {
let progressDialog = new ProgressDialog(options);
this.showOverlay(progressDialog);
return progressDialog;
}
}
export default OverlayAPI;

View File

@ -0,0 +1,43 @@
import ProgressComponent from '../../ui/components/layout/ProgressBar.vue';
import Overlay from './Overlay';
import Vue from 'vue';
var component;
class ProgressDialog extends Overlay {
constructor({progressPerc, progressText, ...options}) {
component = new Vue({
components: {
ProgressComponent: ProgressComponent
},
data() {
return {
model: {
progressPerc: progressPerc || 0,
progressText: progressText
}
}
},
template: '<progress-component :model="model"></progress-component>'
}).$mount();
super({
element: component.$el,
size: 'fit',
notDismissable: true,
...options
});
this.once('destroy', () => {
component.$destroy();
});
}
updateProgress(progressPerc, progressText) {
component.model.progressPerc = progressPerc;
component.model.progressText = progressText;
}
}
export default ProgressDialog;

View File

@ -0,0 +1,71 @@
<template>
<div class="c-message">
<!--Uses flex-row -->
<div class="c-message__icon"
:class="['u-icon-bg-color-' + iconClass]"></div>
<div class="c-message__text">
<!-- Uses flex-column -->
<div class="c-message__title"
v-if="title">
{{title}}
</div>
<div class="c-message__hint"
v-if="hint">
{{hint}}
<span v-if="timestamp">[{{timestamp}}]</span>
</div>
<div class="c-message__action-text"
v-if="message">
{{message}}
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-message {
display: flex;
align-items: center;
padding: $interiorMarginLg;
> * + * {
margin-left: $interiorMarginLg;
}
&__icon {
// Holds a background SVG graphic
$s: 50px;
flex: 0 0 auto;
min-width: $s;
min-height: $s;
}
&__text {
display: flex;
flex-direction: column;
flex: 1 1 auto;
> * + * {
@include test();
margin-top: $interiorMargin;
}
}
// __text elements
&__title,
&__action-text {
font-size: 1.2em; // TEMP
}
}
</style>
<script>
export default {
inject:['iconClass', 'title', 'hint', 'timestamp', 'message']
}
</script>

View File

@ -0,0 +1,148 @@
<template>
<div class="c-overlay">
<div class="c-overlay__blocker"
@click="destroy">
</div>
<div class="c-overlay__outer">
<button class="c-click-icon c-overlay__close-button icon-x-in-circle"
v-if="!notDismissable"
@click="destroy">
</button>
<div class="c-overlay__contents" ref="element"></div>
<div class="c-overlay__button-bar" v-if="buttons">
<button class="c-button"
v-for="(button, index) in buttons"
:key="index"
:class="{'c-button--major': button.emphasis}"
@click="buttonClickHandler(button.callback)">
{{button.label}}
</button>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
@mixin overlaySizing($marginTB: 5%, $marginLR: $marginTB, $width: auto, $height: auto) {
position: absolute;
top: $marginTB; right: $marginLR; bottom: $marginTB; left: $marginLR;
width: $width;
height: $height;
}
.l-overlay-wrapper {
// Created by overlayService.js, contains this template.
// Acts as an anchor for one or more overlays.
display: contents;
}
.c-overlay {
@include abs();
z-index: 100;
&__blocker {
display: none; // Mobile-first
}
&__outer {
@include abs();
background: $overlayColorBg;
color: $overlayColorFg;
display: flex;
flex-direction: column;
padding: $overlayInnerMargin;
}
&__close-button {
$p: $interiorMarginSm;
border-radius: 100% !important;
display: inline-block;
position: absolute;
top: $p; right: $p;
}
&__contents {
flex: 1 1 auto;
overflow: auto;
}
&__button-bar {
flex: 0 0 auto;
display: flex;
justify-content: flex-end;
margin-top: $interiorMargin;
> * + * {
margin-left: $interiorMargin;
}
}
.c-button,
.c-click-icon {
filter: $overlayBrightnessAdjust;
}
}
body.desktop {
.c-overlay {
&__blocker {
@include abs();
background: rgba(black, 0.7);
cursor: pointer;
display: block;
}
&__outer {
border-radius: $overlayCr;
box-shadow: rgba(black, 0.5) 0 2px 25px;
}
}
// Overlay types, styling for desktop. Appended to .l-overlay-wrapper element.
.l-overlay-large {
// Default
.c-overlay__outer {
@include overlaySizing($overlayOuterMarginLg);
}
}
.l-overlay-small {
.c-overlay__outer {
@include overlaySizing($overlayOuterMarginDialog);
}
}
.l-overlay-fit {
.c-overlay__outer {
@include overlaySizing(auto);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
min-width: 20%;
}
}
}
</style>
<script>
export default {
inject: ['dismiss', 'element', 'buttons', 'notDismissable'],
mounted() {
this.$refs.element.appendChild(this.element);
},
methods: {
destroy: function () {
if (!this.notDismissable) {
this.dismiss();
}
},
buttonClickHandler: function (method) {
method();
this.destroy();
}
}
}
</script>

View File

@ -1,107 +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(['./dialog.html', 'zepto'], function (dialogTemplate, $) {
/**
* A dialog may be displayed to show blocking content to users.
* @param {module:openmct.View} view the view to show in the dialog
* @param {string} [title] the title for this dialog
* @constructor
* @memberof module:openmct
*/
function Dialog(view, title) {
this.view = view;
this.title = title;
this.showing = false;
this.enabledState = true;
}
/**
* Display this dialog.
* @returns {Promise} a promise that will be resolved if the user
* chooses "OK", an rejected if the user chooses "cancel"
* @method show
* @memberof module:openmct.Dialog#
*/
Dialog.prototype.show = function () {
if (this.showing) {
throw new Error("Dialog already showing.");
}
var $body = $('body');
var $dialog = $(dialogTemplate);
var $contents = $dialog.find('.contents .editor');
var $close = $dialog.find('.close');
var $ok = $dialog.find('.ok');
var $cancel = $dialog.find('.cancel');
if (this.title) {
$dialog.find('.title').text(this.title);
}
$body.append($dialog);
this.view.show($contents[0]);
this.$dialog = $dialog;
this.$ok = $ok;
this.showing = true;
[$ok, $cancel, $close].forEach(function ($button) {
$button.on('click', this.hide.bind(this));
}.bind(this));
return new Promise(function (resolve, reject) {
$ok.on('click', resolve);
$cancel.on('click', reject);
$close.on('click', reject);
});
};
Dialog.prototype.hide = function () {
if (!this.showing) {
return;
}
this.$dialog.remove();
this.view.destroy();
this.showing = false;
};
/**
* Get or set the "enabled" state of the OK button for this dialog.
* @param {boolean} [state] true to enable, false to disable
* @returns {boolean} true if enabled, false if disabled
* @method enabled
* @memberof module:openmct.Dialog#
*/
Dialog.prototype.enabled = function (state) {
if (state !== undefined) {
this.enabledState = state;
if (this.showing) {
this.$ok.toggleClass('disabled', !state);
}
}
return this.enabledState;
};
return Dialog;
});

View File

@ -1,68 +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 () {
/**
* Allows support for common user actions to be attached to views.
* @interface GestureAPI
* @memberof module:openmct
*/
function GestureAPI(selectGesture, contextMenuGesture) {
this.selectGesture = selectGesture;
this.contextMenuGesture = contextMenuGesture;
}
/**
* Designate an HTML element as selectable, and associated with a
* particular object.
*
* @param {HTMLElement} htmlElement the element to make selectable
* @param {*} item the object which should become selected when this
* element is clicked.
* @returns {Function} a function to remove selectability from this
* HTML element.
* @method selectable
* @memberof module:openmct.GestureAPI#
*/
GestureAPI.prototype.selectable = function (htmlElement, item) {
return this.selectGesture.apply(htmlElement, item);
};
/**
* Designate an HTML element as having a context menu associated with
* the provided item.
*
* @private
* @param {HTMLElement} htmlElement the element to make selectable
* @param {*} item the object for which a context menu should appear
* @returns {Function} a function to remove this geture from this
* HTML element.
* @method selectable
* @memberof module:openmct.GestureAPI#
*/
GestureAPI.prototype.contextMenu = function (htmlElement, item) {
return this.contextMenuGesture.apply(htmlElement, item);
};
return GestureAPI;
});

View File

@ -1,18 +0,0 @@
<div class="abs overlay l-dialog">
<div class="abs blocker"></div>
<div class="abs outer-holder">
<a class="close icon-x-in-circle"></a>
<div class="abs inner-holder contents">
<div class="abs top-bar">
<div class="dialog-title"></div>
<div class="hint"></div>
</div>
<div class='abs editor'>
</div>
<div class="abs bottom-bar">
<a class='s-button major'>OK</a>
<a class='s-button'>Cancel</a>
</div>
</div>
</div>
</div>

View File

@ -54,7 +54,6 @@ define([
'../platform/execution/bundle', '../platform/execution/bundle',
'../platform/exporters/bundle', '../platform/exporters/bundle',
'../platform/features/clock/bundle', '../platform/features/clock/bundle',
'../platform/features/fixed/bundle',
'../platform/features/imagery/bundle', '../platform/features/imagery/bundle',
'../platform/features/my-items/bundle', '../platform/features/my-items/bundle',
'../platform/features/pages/bundle', '../platform/features/pages/bundle',
@ -96,7 +95,6 @@ define([
'platform/exporters', 'platform/exporters',
'platform/telemetry', 'platform/telemetry',
'platform/features/clock', 'platform/features/clock',
'platform/features/fixed',
'platform/features/imagery', 'platform/features/imagery',
'platform/features/pages', 'platform/features/pages',
'platform/features/hyperlink', 'platform/features/hyperlink',

View File

@ -53,14 +53,8 @@
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base"; @import "~styles/sass-base";
.l-layout,
.c-grid,
.c-grid__x,
.c-grid__y {
@include abs();
}
.l-layout { .l-layout {
@include abs();
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -78,42 +72,23 @@
} }
} }
.c-grid { .l-shell__main-container {
pointer-events: none; > .l-layout {
[s-selected] {
&__x { @include bgTicks($colorGridLines, 'x'); } border: $browseBorderSelected;
&__y { @include bgTicks($colorGridLines, 'y'); }
}
.is-editing {
.l-shell__main-container > .l-layout {
// Target the top-most layout container and color its background
background: rgba($editColor, 0.1);
}
.s-selected,
.s-selected-parent {
.l-layout {
// Show the layout grid for the top-most child of the current selection,
// and hide the grid for deeper nested levels.
[class*="__grid-holder"] {
display: block;
}
.l-layout [class*="__grid-holder"] {
display: none;
}
} }
} }
} }
// Styles moved to _global.scss;
</style> </style>
<script> <script>
import LayoutFrame from './LayoutFrame.vue'; import LayoutFrame from './LayoutFrame.vue';
const DEFAULT_GRID_SIZE = [32, 32], const DEFAULT_GRID_SIZE = [5, 5],
DEFAULT_DIMENSIONS = [12, 8], DEFAULT_DIMENSIONS = [50, 50],
DEFAULT_POSITION = [0, 0], DEFAULT_POSITION = [0, 0],
MINIMUM_FRAME_SIZE = [320, 180], MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_HIDDEN_FRAME_TYPES = [ DEFAULT_HIDDEN_FRAME_TYPES = [
@ -128,7 +103,6 @@
frames: [], frames: [],
frameStyles: [], frameStyles: [],
rawPositions: {}, rawPositions: {},
initSelect: true,
drilledIn: undefined drilledIn: undefined
} }
}, },
@ -141,6 +115,7 @@
this.newDomainObject = this.domainObject; this.newDomainObject = this.domainObject;
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE; this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
this.composition = this.openmct.composition.get(this.newDomainObject); this.composition = this.openmct.composition.get(this.newDomainObject);
this.Listeners = [];
let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {}); let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {});
if (this.composition !== undefined) { if (this.composition !== undefined) {
@ -237,8 +212,27 @@
return; return;
} }
this.removeListeners();
let domainObject = selection[0].context.item;
if (selection[1] && domainObject) {
this.attachSelectionListeners(domainObject.identifier);
}
this.updateDrilledInState(); this.updateDrilledInState();
}, },
attachSelectionListeners(identifier) {
let id = this.openmct.objects.makeKeyString(identifier);
let path = "configuration.layout.panels[" + id + "]";
this.listeners.push(this.openmct.objects.observe(this.newDomainObject, path + ".hasFrame", function (newValue) {
this.frameItems.forEach(function (item) {
if (item.id === id) {
item.hasFrame = newValue;
}
});
this.frames[id] = newValue;
}.bind(this)));
},
updateDrilledInState(id) { updateDrilledInState(id) {
this.drilledIn = id; this.drilledIn = id;
this.frameItems.forEach(function (item) { this.frameItems.forEach(function (item) {
@ -284,33 +278,23 @@
$event.preventDefault(); $event.preventDefault();
let child = JSON.parse($event.dataTransfer.getData('domainObject')); let child = JSON.parse($event.dataTransfer.getData('domainObject'));
let duplicates = [];
let composition = this.newDomainObject.composition;
composition.forEach((object) => {
if (this.openmct.objects.makeKeyString(JSON.parse(JSON.stringify(object))) ===
this.openmct.objects.makeKeyString(child.identifier)) {
duplicates.push(object);
}
});
// Disallow adding a duplicate object to the composition
if (duplicates.length !== 0) {
return;
}
let elementRect = this.$el.getBoundingClientRect(); let elementRect = this.$el.getBoundingClientRect();
this.droppedObjectPosition = { this.droppedObjectPosition = {
x: $event.pageX - elementRect.left, x: $event.pageX - elementRect.left,
y: $event.pageY - elementRect.top y: $event.pageY - elementRect.top
} }
// TODO: use the composition API to add child once the default composition
// provider supports it instead of mutating the composition directly.
// this.composition.add(child).
composition.push(child.identifier);
this.mutate('composition', composition);
}, },
handleDragOver($event){ handleDragOver($event){
$event.preventDefault(); $event.preventDefault();
},
removeListeners() {
if (this.listeners) {
this.listeners.forEach(function (l) {
l();
})
}
this.listeners = [];
} }
}, },
mounted() { mounted() {
@ -321,6 +305,7 @@
this.composition.off('remove', this.onRemoveComposition); this.composition.off('remove', this.onRemoveComposition);
this.openmct.off('change', this.selection); this.openmct.off('change', this.selection);
this.unlisten(); this.unlisten();
this.removeListeners();
} }
} }

View File

@ -41,13 +41,13 @@
<div class="c-frame-edit"> <div class="c-frame-edit">
<div class="c-frame-edit__move" <div class="c-frame-edit__move"
@mousedown="startDrag([1,1], [0,0], $event)"></div> @mousedown="startDrag([1,1], [0,0], $event)"></div>
<div class="c-frame-edit__handle --nw" <div class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startDrag([1,1], [-1,-1], $event)"></div> @mousedown="startDrag([1,1], [-1,-1], $event)"></div>
<div class="c-frame-edit__handle --ne" <div class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startDrag([0,1], [1,-1], $event)"></div> @mousedown="startDrag([0,1], [1,-1], $event)"></div>
<div class="c-frame-edit__handle --sw" <div class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startDrag([1,0], [-1,1], $event)"></div> @mousedown="startDrag([1,0], [-1,1], $event)"></div>
<div class="c-frame-edit__handle --se" <div class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startDrag([0,0], [1,1], $event)"></div> @mousedown="startDrag([0,0], [1,1], $event)"></div>
</div> </div>
</div> </div>
@ -97,7 +97,7 @@
&__name { &__name {
@include ellipsize(); @include ellipsize();
flex: 0 1 auto; flex: 0 1 auto;
font-size: 1.2em; @include headerFont($size: 1.2em);
&:before { &:before {
// Object type icon // Object type icon
@ -132,110 +132,8 @@
padding: $interiorMargin; padding: $interiorMargin;
} }
/*************************** SELECTION */ // Styles moved to _global.scss;
&.is-selectable {
&:hover {
box-shadow: $browseShdwSelectableHov;
}
}
&.s-selected, // LEGACY
&.is-selected {
border: $browseBorderSelected;
}
} }
/*************************** EDITING */
.is-editing {
.c-frame {
&:not(.is-drilled-in).is-selectable {
border: $editBorderSelectable;
&:hover {
border: $editBorderSelectableHov;
}
&.s-selected,
&.is-selected {
border: $editBorderSelected;
> .c-frame-edit {
display: block; // Show the editing rect and handles
}
}
}
&.is-drilled-in {
border: $editBorderDrilledIn;
}
.u-links {
// Applied in markup to objects that provide links. Disable while editing.
pointer-events: none;
}
}
}
.c-frame-edit {
// The editing rect and handles
$z: 10;
@include abs();
box-shadow: rgba($editColor, 0.5) 0 0 10px;
display: none;
&__move {
@include abs();
cursor: move;
z-index: $z;
}
&__handle {
$d: 8px;
$o: floor($d * -0.5);
background: rgba($editColor, 0.3);
border: 1px solid $editColor;
position: absolute;
width: $d; height: $d;
top: auto; right: auto; bottom: auto; left: auto;
z-index: $z + 1;
&:before {
// Extended hit area
$m: -5px;
content: '';
display: block;
position: absolute;
top: $m; right: $m; bottom: $m; left: $m;
z-index: -1;
}
&:hover {
background: $editColor;
}
&.--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> </style>

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