Compare commits

...

8 Commits

Author SHA1 Message Date
cd8337cec5 Styles for subobject selection
Color tweaks
2017-11-06 17:52:49 -08:00
150950b9dc Styles for subobject selection
Nested object indication on selection;
Added t-object-type* at t-frame-outer location;
2017-11-06 17:12:23 -08:00
5f875566c5 Just pass in openmct to fix the broken toolbar 2017-11-03 11:30:21 -07:00
cc2c8cf3ab [Frontend] Mods to markup and CSS for sub-object selection 2017-11-03 09:48:30 -07:00
6b711193ca Fix gulp lint errors and clean up code 2017-11-02 16:13:35 -07:00
477df159ff Stop event propagation to avoid clearSelection firing twice which would break resize. 2017-11-02 16:13:35 -07:00
5b75648090 Update the layout controller to use the selection API and the toolbar to listen for the changes in selection. 2017-11-02 16:13:34 -07:00
25c1359056 Saving work in progress
Saving work in progress

saving work

Have inspector view registry to show a view in the inspection sidebar when a inspector view provider exists.

Add inspector view registry

Get the composition for the selected object to show in the Elements pool

Show an appropriate message if compostion is empty.

Select the scope domain object when clearing a selection.
2017-11-02 16:13:34 -07:00
20 changed files with 350 additions and 110 deletions

View File

@ -67,7 +67,27 @@
}));
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
// openmct.inspectorViews.addProvider({
// key: "my-key",
// name: 'my view',
// canView: function (selection) {
// return selection[0].item.type === 'timer';
// },
// view: function (selection) {
// return {
// show: function (container) {
// container.innerHTML = '<div> Congratulations, this is a view of: ' + selection[0].item.name + '</div>';
// },
// destroy: function (container) {
// }
// }
// }
// });
openmct.start();
window.openmct = openmct;
});
</script>
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">

View File

@ -19,12 +19,19 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-controller="InspectorController">
<div ng-repeat="region in regions">
<div ng-controller="InspectorController">
<!--{{selection[0].oldItem}}-->
<mct-representation
key="region.content.key"
mct-object="domainObject"
key="'object-properties'"
mct-object="selection[0].oldItem"
ng-model="ngModel">
</mct-representation>
</div>
<div class='custom-view'>
<mct-representation
key="inspectorKey"
mct-object="selection[0].oldItem"
ng-model="ngModel">
</mct-representation>
</div>
</div>

View File

@ -38,8 +38,6 @@
ng-class="{ last:($index + 1) === contextualParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>
@ -51,8 +49,6 @@
ng-class="{ last:($index + 1) === primaryParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>

View File

@ -121,7 +121,8 @@ define([
"key": "ElementsController",
"implementation": ElementsController,
"depends": [
"$scope"
"$scope",
"openmct"
]
},
{
@ -299,9 +300,6 @@ define([
{
"key": "edit-elements",
"template": elementsTemplate,
"uses": [
"composition"
],
"gestures": [
"drop"
]

View File

@ -25,7 +25,7 @@
ng-model="filterBy">
</mct-include>
<div class="flex-elem grows vscroll">
<ul class="tree">
<ul class="tree" ng-if="composition.length > 0">
<li ng-repeat="containedObject in composition | filter:searchElements">
<span class="tree-item">
<mct-representation
@ -36,5 +36,8 @@
</span>
</li>
</ul>
<div ng-if="composition.length === 0">
No contained elements
</div>
</div>
</div>

View File

@ -29,7 +29,11 @@ define(
*
* @constructor
*/
function ElementsController($scope) {
function ElementsController($scope, openmct) {
this.scope = $scope;
this.scope.composition = [];
var self = this;
function filterBy(text) {
if (typeof text === 'undefined') {
return $scope.searchText;
@ -47,10 +51,38 @@ define(
}
}
function setSelection(selection) {
self.scope.selection = selection;
self.refreshComposition();
}
$scope.filterBy = filterBy;
$scope.searchElements = searchElements;
openmct.selection.on('change', setSelection);
setSelection(openmct.selection.get());
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
});
}
ElementsController.prototype.refreshComposition = function () {
var selection = this.scope.selection[0];
if (!selection) {
return;
}
var comp = selection.oldItem.useCapability('composition');
if (comp) {
comp.then(function (composition) {
this.scope.composition = composition;
}.bind(this));
} else {
this.scope.composition = [];
}
};
return ElementsController;
}
);

View File

@ -38,7 +38,7 @@ define(
* @constructor
* @implements {Representer}
*/
function EditToolbarRepresenter(scope, element, attrs) {
function EditToolbarRepresenter(scope, element, attrs, openmct) {
var self = this;
// Mark changes as ready to persist
@ -109,6 +109,7 @@ define(
this.updateSelection = updateSelection;
this.toolbar = undefined;
this.toolbarObject = {};
// this.openmct = openmct;
// If this representation exposes a toolbar, set up watches
// to synchronize with it.
@ -146,7 +147,7 @@ define(
// Expose the toolbar object to the parent scope
initialize(definition);
// Create a selection scope
this.setSelection(new EditToolbarSelection());
this.setSelection(new EditToolbarSelection(openmct));
// Initialize toolbar to an empty selection
this.updateSelection([]);
};

View File

@ -38,10 +38,18 @@ define(
* @memberof platform/commonUI/edit
* @constructor
*/
function EditToolbarSelection() {
function EditToolbarSelection(openmct) {
this.selection = [{}];
this.selecting = false;
this.selectedObj = undefined;
openmct.selection.on('change', function (selection) {
if (selection[0] && selection[0].toolbar) {
this.select(selection[0].toolbar);
} else {
this.deselect();
}
}.bind(this));
}
/**

View File

@ -88,15 +88,15 @@
}
// Prevent nested frames from showing their grids
.t-frame-outer .l-grid-holder { display: none !important; }
//.t-frame-outer .l-grid-holder { display: none !important; }
// Prevent nested elements from showing s-hover-border
.t-frame-outer .s-hover-border {
border: none !important;
//border: none !important;
}
// Prevent nested frames from being selectable until we have proper sub-object editing
.t-frame-outer .t-frame-outer {
pointer-events: none;
}
//.t-frame-outer .t-frame-outer {
// pointer-events: none;
//}
}

View File

@ -21,34 +21,49 @@
*****************************************************************************/
.s-hover-border {
border: 1px dotted transparent;
&:hover {
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
}
}
.s-status-editing {
// Limit to editing mode until we have sub-object selection
// Limit to editing mode
.s-hover-border {
// Show a border by default so user can see object bounds and empty objects
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
//border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
&:hover {
border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.7) !important;
}
}
.s-selected > .s-hover-border,
.s-selected.s-hover-border {
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
border-color: $colorSelectableSelectedPrimary !important;
@include boxShdwLarge();
// Show edit-corners if you got 'em
.edit-corner {
display: block;
&:hover {
background-color: rgba($colorKey, 1);
}
}
}
.s-selected > .s-moveable,
.s-selected.s-moveable {
cursor: move;
}
}
}
.s-selected > .s-hover-border,
.s-selected.s-hover-border {
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
border: 1px dotted $colorSelectableSelectedPrimary !important; //$colorSelectableSelectedPrimary
@include boxShdwLarge();
// Show edit-corners if you got 'em
.edit-corner {
display: block;
&:hover {
background-color: rgba($colorKey, 1);
}
}
}
//.s-selected > .t-rep-frame > .t-frame-inner > .l-layout > .t-object-type-layout.s-hover-border {
.s-selected {
.s-hover-border {
border: 1px dotted rgba(yellow, 0.3) !important;
}
.t-object-type-layout.s-hover-border {
border: 1px dashed rgba(yellow, 0.3) !important;
}
}

View File

@ -40,7 +40,7 @@ define(
// Gets an array of the contextual parents/ancestors of the selected object
function getContextualPath() {
var currentObj = $scope.ngModel.selectedObject,
var currentObj = $scope.domainObject,
currentParent,
parents = [];
@ -68,7 +68,7 @@ define(
// If this the the initial call of this recursive function
if (!current) {
current = $scope.ngModel.selectedObject;
current = $scope.domainObject;
$scope.primaryParents = [];
}
@ -87,16 +87,16 @@ define(
// Gets the metadata for the selected object
function getMetadata() {
$scope.metadata = $scope.ngModel.selectedObject &&
$scope.ngModel.selectedObject.hasCapability('metadata') &&
$scope.ngModel.selectedObject.useCapability('metadata');
$scope.metadata = $scope.domainObject &&
$scope.domainObject.hasCapability('metadata') &&
$scope.domainObject.useCapability('metadata');
}
// Set scope variables when the selected object changes
$scope.$watch('ngModel.selectedObject', function () {
$scope.isLink = $scope.ngModel.selectedObject &&
$scope.ngModel.selectedObject.hasCapability('location') &&
$scope.ngModel.selectedObject.getCapability('location').isLink();
$scope.$watch('domainObject', function () {
$scope.isLink = $scope.domainObject &&
$scope.domainObject.hasCapability('location') &&
$scope.domainObject.getCapability('location').isLink();
if ($scope.isLink) {
getPrimaryPath();
@ -109,7 +109,7 @@ define(
getMetadata();
});
var mutation = $scope.ngModel.selectedObject.getCapability('mutation');
var mutation = $scope.domainObject.getCapability('mutation');
var unlisten = mutation.listen(getMetadata);
$scope.$on('$destroy', unlisten);
}

View File

@ -38,7 +38,9 @@ define([
"implementation": InspectorController,
"depends": [
"$scope",
"policyService"
"policyService",
"openmct",
"$document"
]
}
],

View File

@ -30,33 +30,30 @@ define(
*
* @constructor
*/
function InspectorController($scope, policyService) {
var domainObject = $scope.domainObject,
typeCapability = domainObject.getCapability('type'),
statusListener;
function InspectorController($scope, policyService, openmct, $document) {
window.inspectorScope = $scope;
/**
* Filters region parts to only those allowed by region policies
* @param regions
* @returns {{}}
*/
function filterRegions(inspector) {
//Dupe so we're not modifying the type definition.
return inspector.regions && inspector.regions.filter(function (region) {
return policyService.allow('region', region, domainObject);
});
function setSelection(selection) {
if (selection[0]) {
var view = openmct.inspectorViews.get(selection);
if (view) {
var container = $document[0].querySelectorAll('.custom-view')[0];
view.show(container);
} else {
$scope.inspectorKey = selection[0].oldItem.getCapability("type").typeDef.inspector;
}
}
$scope.selection = selection;
}
function setRegions() {
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
}
openmct.selection.on("change", setSelection);
setSelection(openmct.selection.get());
statusListener = domainObject.getCapability("status").listen(setRegions);
$scope.$on("$destroy", function () {
statusListener();
openmct.selection.off("change", setSelection);
});
setRegions();
}
return InspectorController;

View File

@ -260,7 +260,8 @@ define([
"key": "LayoutController",
"implementation": LayoutController,
"depends": [
"$scope"
"$scope",
"openmct"
]
},
{

View File

@ -22,10 +22,10 @@
<div class="abs l-layout"
ng-controller="LayoutController as controller"
ng-click="controller.clearSelection()">
ng-click="controller.clearSelection($event)">
<!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.clearSelection()">
<div class="l-grid-holder" ng-click="controller.clearSelection($event)">
<div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
@ -34,10 +34,10 @@
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div>
<div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
<div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}'
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
ng-repeat="childObject in composition"
ng-click="controller.select($event, childObject.getId())"
ng-click="controller.select($event, childObject.getId(), childObject)"
ng-style="controller.getFrameStyle(childObject.getId())">
<mct-representation key="'frame'"
@ -45,7 +45,7 @@
mct-object="childObject">
</mct-representation>
<!-- Drag handles -->
<span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject)">
<span class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)"

View File

@ -50,10 +50,12 @@ define(
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope) {
function LayoutController($scope, openmct) {
var self = this,
callbackCount = 0;
this.openmct = openmct;
// Update grid size when it changed
function updateGridSize(layoutGrid) {
var oldSize = self.gridSize;
@ -111,11 +113,15 @@ define(
$scope.domainObject.useCapability('composition').then(function (composition) {
var ids;
var domainObject;
//Is this callback for the most recent composition
// request? If not, discard it. Prevents race condition
if (thisCount === callbackCount) {
ids = composition.map(function (object) {
if (self.droppedIdToSelectAfterRefresh && self.droppedIdToSelectAfterRefresh === object.getId()) {
domainObject = object;
}
return object.getId();
}) || [];
@ -125,9 +131,9 @@ define(
// If there is a newly-dropped object, select it.
if (self.droppedIdToSelectAfterRefresh) {
self.select(null, self.droppedIdToSelectAfterRefresh);
self.select(null, self.droppedIdToSelectAfterRefresh, domainObject);
delete self.droppedIdToSelectAfterRefresh;
} else if (composition.indexOf(self.selectedId) === -1) {
} else if (self.selectedId && composition.indexOf(self.selectedId) === -1) {
self.clearSelection();
}
}
@ -168,10 +174,6 @@ define(
// Watch for changes to the grid size in the model
$scope.$watch("model.layoutGrid", updateGridSize);
$scope.$watch("selection", function (selection) {
this.selection = selection;
}.bind(this));
// Update composed objects on screen, and position panes
$scope.$watchCollection("model.composition", refreshComposition);
@ -363,7 +365,9 @@ define(
* @returns {boolean} true if selected, otherwise false
*/
LayoutController.prototype.selected = function (obj) {
return !!this.selectedId && this.selectedId === obj.getId();
var selection = this.openmct.selection.get();
var sobj = selection[0];
return (sobj && sobj.oldItem.getId() === obj.getId()) ? true : false;
};
/**
@ -372,22 +376,21 @@ define(
* @param event the mouse event
* @param {string} id the object id
*/
LayoutController.prototype.select = function (event, id) {
LayoutController.prototype.select = function (event, id, domainObject) {
if (event) {
event.stopPropagation();
if (this.selection) {
event.preventDefault();
}
}
this.selectedId = id;
var selectedObj = {};
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] =
this.toggleFrame.bind(this, id, domainObject);
if (this.selection) {
this.selection.select(selectedObj);
}
this.openmct.selection.select({
item: domainObject.useCapability('adapter'),
oldItem: domainObject,
toolbar: selectedObj
});
};
/**
@ -396,7 +399,7 @@ define(
* @param {string} id the object id
* @private
*/
LayoutController.prototype.toggleFrame = function (id) {
LayoutController.prototype.toggleFrame = function (id, domainObject) {
var configuration = this.$scope.configuration;
if (!configuration.panels[id]) {
@ -404,21 +407,27 @@ define(
}
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
this.select(undefined, id); // reselect so toolbar updates
this.select(undefined, id, domainObject); // reselect so toolbar updates
};
/**
* Clear the current user selection.
*/
LayoutController.prototype.clearSelection = function () {
LayoutController.prototype.clearSelection = function (event) {
if (event) {
event.stopPropagation();
}
if (this.dragInProgress) {
return;
}
if (this.selection) {
this.selection.deselect();
delete this.selectedId;
}
delete this.selectedId;
this.openmct.selection.select({
item: this.$scope.domainObject.useCapability('adapter'),
oldItem: this.$scope.domainObject
});
};
/**

View File

@ -69,7 +69,7 @@ define([
"delegates": [
"telemetry"
],
"inspector": tableInspector,
"inspector": "table-options-edit",
"contains": [
{
"has": "telemetry"

View File

@ -28,7 +28,8 @@ define([
'./selection/Selection',
'./api/objects/object-utils',
'./plugins/plugins',
'./ui/ViewRegistry'
'./ui/ViewRegistry',
'./ui/InspectorViewRegistry'
], function (
EventEmitter,
legacyRegistry,
@ -37,7 +38,8 @@ define([
Selection,
objectUtils,
plugins,
ViewRegistry
ViewRegistry,
InspectorViewRegistry
) {
/**
* Open MCT is an extensible web application for building mission
@ -122,6 +124,16 @@ define([
*/
this.inspectors = new ViewRegistry();
/**
* Registry for views which should appear in the Inspector area.
* These views will be chosen based on the selection state.
*
* @type {module:openmct.InspectorViewRegistry}
* @memberof module:openmct.MCT#
* @name inspectorViews
*/
this.inspectorViews = new InspectorViewRegistry();
/**
* Registry for views which should appear in Edit Properties
* dialogs, and similar user interface elements used for
@ -196,7 +208,9 @@ define([
this.Dialog = api.Dialog;
this.on('navigation', this.selection.clear.bind(this.selection));
this.on('navigation', function(domainObject) {
this.selection.select({item: domainObject.useCapability('adapter'), oldItem: domainObject});
}.bind(this));
}
MCT.prototype = Object.create(EventEmitter.prototype);

View File

@ -54,16 +54,21 @@ define(['EventEmitter'], function (EventEmitter) {
Selection.prototype.clear = function () {
this.selected = [];
this.emit('change');
this.emit('change', this.selected);
};
Selection.prototype.primary = function () {
return this.selected[this.selected.length - 1];
};
Selection.prototype.all = function () {
Selection.prototype.get = function () {
return this.selected;
};
Selection.prototype.select = function (context) {
this.selected = [context];
this.emit('change', this.selected);
};
return Selection;
});

View File

@ -0,0 +1,132 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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 () {
function InspectorViewRegistry() {
this.providers = {};
}
InspectorViewRegistry.prototype.get = function (selection) {
var providers = this.getAllProviders().filter(function (provider) {
return provider.canView(selection);
});
if (providers && providers.length > 0) {
return providers[0].view(selection);
}
};
InspectorViewRegistry.prototype.addProvider = function (provider) {
var key = provider.key;
if (key === undefined) {
throw "View providers must have a unique 'key' property defined";
}
if (this.providers[key] !== undefined) {
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
}
this.providers[key] = provider;
};
/**
* @private
*/
InspectorViewRegistry.prototype.getProviderByKey = function (key) {
return this.providers[key];
};
/**
* @private
*/
InspectorViewRegistry.prototype.getAllProviders = function () {
return Object.values(this.providers);
};
/**
* A View is used to provide displayable content, and to react to
* associated life cycle events.
*
* @name View
* @interface
* @memberof module:openmct
*/
/**
* Populate the supplied DOM element with the contents of this view.
*
* View implementations should use this method to attach any
* listeners or acquire other resources that are necessary to keep
* the contents of this view up-to-date.
*
* @param {HTMLElement} container the DOM element to populate
* @method show
* @memberof module:openmct.View#
*/
/**
* Release any resources associated with this view.
*
* View implementations should use this method to detach any
* listeners or release other resources that are no longer necessary
* once a view is no longer used.
*
* @method destroy
* @memberof module:openmct.View#
*/
/**
* Exposes types of views in inspector.
*
* @interface InspectorViewProvider
* @property {string} key a unique identifier for this view
* @property {string} name the human-readable name of this view
* @property {string} [description] a longer-form description (typically
* a single sentence or short paragraph) of this kind of view
* @property {string} [cssClass] the CSS class to apply to labels for this
* view (to add icons, for instance)
* @memberof module:openmct
*/
/**
* Checks if this provider can supply views for a selection.
*
* @method canView
* @memberof module:openmct.InspectorViewProvider#
* @param {module:openmct.selection} selection
* @returns {boolean} true if the selected item can be viewed using
* this provider
*/
/**
* Provide an inspector view of this selection object.
*
* @method view
* @memberof module:openmct.InspectorViewProvider#
* @param {module:openmct.selection} selection the selection object
* @returns {module:openmct.View} a view for this selection
*/
return InspectorViewRegistry;
});