mirror of
https://github.com/nasa/openmct.git
synced 2025-05-02 16:53:24 +00:00
[Toolbar] Implement a public API for adding toolbars (#1908)
* [API] Implement a toolbar registry and a plugin to allow providing a toolbar for a selected object. * Modify the mct-toolbar directive to get the toolbar structure from a provider based on selection. * Implements the layout toolbar in the layout bundle
This commit is contained in:
parent
de8f8d174d
commit
73e38f1955
@ -39,7 +39,6 @@ define([
|
||||
"./src/policies/EditableMovePolicy",
|
||||
"./src/policies/EditContextualActionPolicy",
|
||||
"./src/representers/EditRepresenter",
|
||||
"./src/representers/EditToolbarRepresenter",
|
||||
"./src/capabilities/EditorCapability",
|
||||
"./src/capabilities/TransactionCapabilityDecorator",
|
||||
"./src/services/TransactionManager",
|
||||
@ -78,7 +77,6 @@ define([
|
||||
EditableMovePolicy,
|
||||
EditContextualActionPolicy,
|
||||
EditRepresenter,
|
||||
EditToolbarRepresenter,
|
||||
EditorCapability,
|
||||
TransactionCapabilityDecorator,
|
||||
TransactionManager,
|
||||
@ -381,12 +379,6 @@ define([
|
||||
"depends": [
|
||||
"$log"
|
||||
]
|
||||
},
|
||||
{
|
||||
"implementation": EditToolbarRepresenter,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
@ -424,6 +416,17 @@ define([
|
||||
"transactionService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
depends: [
|
||||
"toolbars[]",
|
||||
"openmct"
|
||||
],
|
||||
implementation: function (toolbars, openmct) {
|
||||
toolbars.forEach(openmct.toolbars.addProvider, openmct.toolbars);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
@ -24,7 +24,8 @@
|
||||
<div class="items-select left flex-elem l-flex-row grows">
|
||||
<mct-representation key="'back-arrow'"
|
||||
mct-object="domainObject"
|
||||
class="flex-elem l-back"></mct-representation>
|
||||
class="flex-elem l-back">
|
||||
</mct-representation>
|
||||
<mct-representation key="'object-header'"
|
||||
mct-object="domainObject"
|
||||
class="l-flex-row flex-elem grows object-header">
|
||||
@ -48,8 +49,8 @@
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||
<mct-toolbar name="mctToolbar"
|
||||
structure="toolbar.structure"
|
||||
ng-model="toolbar.state"
|
||||
structure="editToolbar.structure"
|
||||
ng-model="editToolbar.state"
|
||||
class="flex-elem grows">
|
||||
</mct-toolbar>
|
||||
<mct-representation key="'edit-action-buttons'"
|
||||
@ -61,7 +62,6 @@
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
toolbar="toolbar"
|
||||
mct-selectable="{
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject
|
||||
|
@ -20,192 +20,110 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
// Utility functions for reducing truth arrays
|
||||
function and(a, b) {
|
||||
return a && b;
|
||||
}
|
||||
function or(a, b) {
|
||||
return a || b;
|
||||
}
|
||||
|
||||
[
|
||||
'../../../../../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 tool bar, based on
|
||||
* that view's declaration of what belongs in its tool bar and on
|
||||
* 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 structure toolbar structure, as provided by view definition
|
||||
* @param {Function} commit callback to invoke after changes
|
||||
* @param $scope the Angular scope
|
||||
* @param {Object} openmct the openmct object
|
||||
* @param structure the toolbar structure
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditToolbar(structure, commit) {
|
||||
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;
|
||||
|
||||
// Generate a new key for an item's property
|
||||
function addKey(property) {
|
||||
self.properties.push(property);
|
||||
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
|
||||
}
|
||||
|
||||
// Invoke all functions in selections with the given name
|
||||
function invoke(method, value) {
|
||||
if (method) {
|
||||
// Make the change in the selection
|
||||
self.selection.forEach(function (selected) {
|
||||
if (typeof selected[method] === 'function') {
|
||||
selected[method](value);
|
||||
}
|
||||
});
|
||||
// ...and commit!
|
||||
commit();
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare a toolbar item based on current selection
|
||||
function convertItem(item) {
|
||||
var converted = Object.create(item || {});
|
||||
|
||||
if (item.property) {
|
||||
converted.key = addKey(item.property);
|
||||
converted.key = addKey(item);
|
||||
}
|
||||
|
||||
if (item.method) {
|
||||
converted.click = function (v) {
|
||||
invoke(item.method, v);
|
||||
converted.click = function (value) {
|
||||
item.method(value);
|
||||
};
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
// Prepare a toolbar section
|
||||
function convertSection(section) {
|
||||
var converted = Object.create(section || {});
|
||||
converted.items =
|
||||
((section || {}).items || [])
|
||||
.map(convertItem);
|
||||
return converted;
|
||||
}
|
||||
|
||||
this.toolbarState = [];
|
||||
this.selection = undefined;
|
||||
this.properties = [];
|
||||
this.toolbarStructure = Object.create(structure || {});
|
||||
this.toolbarStructure.sections =
|
||||
((structure || {}).sections || []).map(convertSection);
|
||||
}
|
||||
|
||||
// Check if all elements of the selection which have this
|
||||
// property have the same value for this property.
|
||||
EditToolbar.prototype.isConsistent = function (property) {
|
||||
var self = this,
|
||||
consistent = true,
|
||||
observed = false,
|
||||
state;
|
||||
|
||||
// Check if a given element of the selection is consistent
|
||||
// with previously-observed elements for this property.
|
||||
function checkConsistency(selected) {
|
||||
var next;
|
||||
// Ignore selections which don't have this property
|
||||
if (selected[property] !== undefined) {
|
||||
// Look up state of this element in the selection
|
||||
next = self.lookupState(property, selected);
|
||||
// Detect inconsistency
|
||||
if (observed) {
|
||||
consistent = consistent && (next === state);
|
||||
}
|
||||
// Track state for next iteration
|
||||
state = next;
|
||||
observed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through selections
|
||||
self.selection.forEach(checkConsistency);
|
||||
|
||||
return consistent;
|
||||
};
|
||||
|
||||
// Used to filter out items which are applicable (or not)
|
||||
// to the current selection.
|
||||
EditToolbar.prototype.isApplicable = function (item) {
|
||||
var property = (item || {}).property,
|
||||
method = (item || {}).method,
|
||||
exclusive = !!(item || {}).exclusive;
|
||||
|
||||
// Check if a selected item defines this property
|
||||
function hasProperty(selected) {
|
||||
return (property && (selected[property] !== undefined)) ||
|
||||
(method && (typeof selected[method] === 'function'));
|
||||
}
|
||||
|
||||
return this.selection.map(hasProperty).reduce(
|
||||
exclusive ? and : or,
|
||||
exclusive
|
||||
) && this.isConsistent(property);
|
||||
};
|
||||
|
||||
|
||||
// Look up the current value associated with a property
|
||||
EditToolbar.prototype.lookupState = function (property, selected) {
|
||||
var value = selected[property];
|
||||
return (typeof value === 'function') ? value() : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the current selection. Visibility of sections
|
||||
* and items in the toolbar will be updated to match this.
|
||||
* @param {Array} s the new selection
|
||||
*/
|
||||
EditToolbar.prototype.setSelection = function (s) {
|
||||
var self = this;
|
||||
|
||||
// Show/hide controls in this section per applicability
|
||||
function refreshSectionApplicability(section) {
|
||||
var count = 0;
|
||||
// Show/hide each item
|
||||
(section.items || []).forEach(function (item) {
|
||||
item.hidden = !self.isApplicable(item);
|
||||
count += item.hidden ? 0 : 1;
|
||||
});
|
||||
// Hide this section if there are no applicable items
|
||||
section.hidden = !count;
|
||||
}
|
||||
|
||||
// Get initial value for a given property
|
||||
function initializeState(property) {
|
||||
var result;
|
||||
// Look through all selections for this property;
|
||||
// values should all match by the time we perform
|
||||
// this lookup anyway.
|
||||
self.selection.forEach(function (selected) {
|
||||
result = (selected[property] !== undefined) ?
|
||||
self.lookupState(property, selected) :
|
||||
result;
|
||||
structure.forEach(function (item) {
|
||||
if (item.property === property) {
|
||||
result = _.get(item.domainObject, item.property);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
this.selection = s;
|
||||
this.toolbarStructure.sections.forEach(refreshSectionApplicability);
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the structure of the toolbar, as appropriate to
|
||||
* Gets the structure of the toolbar, as appropriate to
|
||||
* pass to `mct-toolbar`.
|
||||
* @returns the toolbar structure
|
||||
*
|
||||
* @returns {Array} the toolbar structure
|
||||
*/
|
||||
EditToolbar.prototype.getStructure = function () {
|
||||
return this.toolbarStructure;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current state of the toolbar, as appropriate
|
||||
* 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 () {
|
||||
@ -213,48 +131,124 @@ define(
|
||||
};
|
||||
|
||||
/**
|
||||
* Update state within the current selection.
|
||||
* 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 convey to the selection
|
||||
* @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;
|
||||
|
||||
// Update value for this property in all elements of the
|
||||
// selection which have this property.
|
||||
function updateProperties(property, val) {
|
||||
var changed = false;
|
||||
|
||||
// Update property in a selected element
|
||||
function updateProperty(selected) {
|
||||
// Ignore selected elements which don't have this property
|
||||
if (selected[property] !== undefined) {
|
||||
// Check if this is a setter, or just assignable
|
||||
if (typeof selected[property] === 'function') {
|
||||
changed =
|
||||
changed || (selected[property]() !== val);
|
||||
selected[property](val);
|
||||
} else {
|
||||
changed =
|
||||
changed || (selected[property] !== val);
|
||||
selected[property] = val;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// Update property in all selected elements
|
||||
self.selection.forEach(updateProperty);
|
||||
structure.forEach(function (item) {
|
||||
var domainObject = item.domainObject;
|
||||
var id = objectUtils.makeKeyString(domainObject.identifier);
|
||||
|
||||
// Return whether or not anything changed
|
||||
return changed;
|
||||
if (!self.domainObjectsById[id]) {
|
||||
self.domainObjectsById[id] = {
|
||||
domainObject: domainObject,
|
||||
properties: []
|
||||
};
|
||||
observeObject(domainObject, id);
|
||||
}
|
||||
|
||||
return updateProperties(this.properties[index], value);
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
@ -1,154 +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(
|
||||
['./EditToolbar', './EditToolbarSelection'],
|
||||
function (EditToolbar, EditToolbarSelection) {
|
||||
|
||||
// No operation
|
||||
var NOOP_REPRESENTER = {
|
||||
represent: function () {},
|
||||
destroy: function () {}
|
||||
};
|
||||
|
||||
/**
|
||||
* The EditToolbarRepresenter populates the toolbar in Edit mode
|
||||
* based on a view's definition.
|
||||
* @param {Scope} scope the Angular scope of the representation
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Representer}
|
||||
*/
|
||||
function EditToolbarRepresenter(openmct, scope, element, attrs) {
|
||||
var self = this;
|
||||
|
||||
// Mark changes as ready to persist
|
||||
function commit(message) {
|
||||
if (scope.commit) {
|
||||
scope.commit(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle changes to the current selection
|
||||
function updateSelection(selection) {
|
||||
// Only update if there is a toolbar to update
|
||||
if (self.toolbar) {
|
||||
// Make sure selection is array-like
|
||||
selection = Array.isArray(selection) ?
|
||||
selection :
|
||||
(selection ? [selection] : []);
|
||||
|
||||
// Update the toolbar's selection
|
||||
self.toolbar.setSelection(selection);
|
||||
|
||||
// ...and expose its structure/state
|
||||
self.toolbarObject.structure =
|
||||
self.toolbar.getStructure();
|
||||
self.toolbarObject.state =
|
||||
self.toolbar.getState();
|
||||
}
|
||||
}
|
||||
|
||||
// Get state (to watch it)
|
||||
function getState() {
|
||||
return self.toolbarObject.state;
|
||||
}
|
||||
|
||||
// Update selection models to match changed toolbar state
|
||||
function updateState(state) {
|
||||
// Update underlying state based on toolbar changes
|
||||
var changed = (state || []).map(function (value, index) {
|
||||
return self.toolbar.updateState(index, value);
|
||||
}).reduce(function (a, b) {
|
||||
return a || b;
|
||||
}, false);
|
||||
|
||||
// Only commit if something actually changed
|
||||
if (changed) {
|
||||
// Commit the changes.
|
||||
commit("Changes from toolbar.");
|
||||
}
|
||||
}
|
||||
this.clearExposedToolbar = function () {
|
||||
// Clear exposed toolbar state (if any)
|
||||
if (attrs.toolbar) {
|
||||
delete scope.$parent[attrs.toolbar];
|
||||
}
|
||||
};
|
||||
this.exposeToolbar = function () {
|
||||
scope.$parent[self.attrs.toolbar] = self.toolbarObject;
|
||||
};
|
||||
|
||||
this.commit = commit;
|
||||
this.attrs = attrs;
|
||||
this.updateSelection = updateSelection;
|
||||
this.toolbar = undefined;
|
||||
this.toolbarObject = {};
|
||||
this.openmct = openmct;
|
||||
this.scope = scope;
|
||||
|
||||
// If this representation exposes a toolbar, set up watches
|
||||
// to synchronize with it.
|
||||
if (attrs && attrs.toolbar) {
|
||||
// Detect and handle changes to state from the toolbar
|
||||
scope.$watchCollection(getState, updateState);
|
||||
// Watch for changes in the current selection state
|
||||
scope.$watchCollection("selection.all()", updateSelection);
|
||||
// Expose toolbar state under that name
|
||||
scope.$parent[attrs.toolbar] = this.toolbarObject;
|
||||
} else {
|
||||
// No toolbar declared, so do nothing.
|
||||
return NOOP_REPRESENTER;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Represent a domain object using this definition
|
||||
EditToolbarRepresenter.prototype.represent = function (representation) {
|
||||
// Get the newest toolbar definition from the view
|
||||
var definition = (representation || {}).toolbar || {};
|
||||
|
||||
// If we have been asked to expose toolbar state...
|
||||
if (this.attrs.toolbar) {
|
||||
// Initialize toolbar object
|
||||
this.toolbar = new EditToolbar(definition, this.commit);
|
||||
// Ensure toolbar state is exposed
|
||||
this.exposeToolbar();
|
||||
}
|
||||
|
||||
// Add toolbar selection to scope.
|
||||
this.scope.selection = new EditToolbarSelection(
|
||||
this.scope,
|
||||
this.openmct
|
||||
);
|
||||
// Initialize toolbar to current selection
|
||||
this.updateSelection(this.scope.selection.all());
|
||||
};
|
||||
|
||||
// Destroy; remove toolbar object from parent scope
|
||||
EditToolbarRepresenter.prototype.destroy = function () {
|
||||
this.clearExposedToolbar();
|
||||
};
|
||||
|
||||
return EditToolbarRepresenter;
|
||||
}
|
||||
);
|
@ -1,157 +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 () {
|
||||
|
||||
/**
|
||||
* Tracks selection state for editable views. Selection is
|
||||
* implemented such that (from the toolbar's perspective)
|
||||
* up to two objects can be "selected" at any given time:
|
||||
*
|
||||
* * The view proxy (see the `proxy` method), which provides
|
||||
* an interface for interacting with the view itself (e.g.
|
||||
* for buttons like "Add")
|
||||
* * The selection, for single selected elements within the
|
||||
* view.
|
||||
*
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditToolbarSelection($scope, openmct) {
|
||||
this.selection = [{}];
|
||||
this.selecting = false;
|
||||
this.selectedObj = undefined;
|
||||
this.openmct = openmct;
|
||||
var self = this;
|
||||
|
||||
function setSelection(selection) {
|
||||
var selected = selection[0];
|
||||
|
||||
if (selected && selected.context.toolbar) {
|
||||
self.select(selected.context.toolbar);
|
||||
} else {
|
||||
self.deselect();
|
||||
}
|
||||
|
||||
if (selected && selected.context.viewProxy) {
|
||||
self.proxy(selected.context.viewProxy);
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
self.openmct.selection.off('change', setSelection);
|
||||
});
|
||||
|
||||
this.openmct.selection.on('change', setSelection);
|
||||
setSelection(this.openmct.selection.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an object is currently selected.
|
||||
* @param {*} obj the object to check for selection
|
||||
* @returns {boolean} true if selected, otherwise false
|
||||
*/
|
||||
EditToolbarSelection.prototype.selected = function (obj) {
|
||||
return (obj === this.selectedObj) || (obj === this.selection[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Select an object.
|
||||
* @param obj the object to select
|
||||
* @returns {boolean} true if selection changed
|
||||
*/
|
||||
EditToolbarSelection.prototype.select = function (obj) {
|
||||
// Proxy is always selected
|
||||
if (obj === this.selection[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear any existing selection
|
||||
this.deselect();
|
||||
|
||||
// Note the current selection state
|
||||
this.selectedObj = obj;
|
||||
this.selecting = true;
|
||||
|
||||
// Add the selection
|
||||
this.selection.push(obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the current selection.
|
||||
* @returns {boolean} true if selection changed
|
||||
*/
|
||||
EditToolbarSelection.prototype.deselect = function () {
|
||||
// Nothing to do if we don't have a selected object
|
||||
if (this.selecting) {
|
||||
// Clear state tracking
|
||||
this.selecting = false;
|
||||
this.selectedObj = undefined;
|
||||
|
||||
// Remove the selection
|
||||
this.selection.pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the currently-selected object.
|
||||
* @returns the currently selected object
|
||||
*/
|
||||
EditToolbarSelection.prototype.get = function () {
|
||||
return this.selectedObj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get/set the view proxy (for toolbar actions taken upon
|
||||
* the view itself.)
|
||||
* @param [proxy] the view proxy (if setting)
|
||||
* @returns the current view proxy
|
||||
*/
|
||||
EditToolbarSelection.prototype.proxy = function (p) {
|
||||
if (arguments.length > 0) {
|
||||
this.selection[0] = p;
|
||||
}
|
||||
return this.selection[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array containing all selections, including the
|
||||
* selection proxy. It is generally not advisable to
|
||||
* mutate this array directly.
|
||||
* @returns {Array} all selections
|
||||
*/
|
||||
EditToolbarSelection.prototype.all = function () {
|
||||
return this.selection;
|
||||
};
|
||||
|
||||
return EditToolbarSelection;
|
||||
}
|
||||
);
|
@ -1,156 +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/EditToolbarRepresenter"],
|
||||
function (EditToolbarRepresenter) {
|
||||
|
||||
describe("The Edit mode toolbar representer", function () {
|
||||
var mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockUnwatch,
|
||||
representer,
|
||||
mockOpenMCT,
|
||||
mockSelection;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
'$scope',
|
||||
['$on', '$watch', '$watchCollection', "commit", "$apply"]
|
||||
);
|
||||
mockElement = {};
|
||||
testAttrs = { toolbar: 'testToolbar' };
|
||||
mockScope.$parent = jasmine.createSpyObj(
|
||||
'$parent',
|
||||
['$watch', '$watchCollection']
|
||||
);
|
||||
mockUnwatch = jasmine.createSpy('unwatch');
|
||||
|
||||
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
|
||||
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn([]);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
|
||||
representer = new EditToolbarRepresenter(
|
||||
mockOpenMCT,
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes toolbar state under a attr-defined name", function () {
|
||||
// A structure/state object should have been added to the
|
||||
// parent scope under the name provided in the "toolbar"
|
||||
// attribute
|
||||
expect(mockScope.$parent.testToolbar).toBeDefined();
|
||||
});
|
||||
|
||||
it("is robust against lack of a toolbar definition", function () {
|
||||
expect(function () {
|
||||
representer.represent({});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("watches for toolbar state changes", function () {
|
||||
representer.represent({});
|
||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.$watchCollection.calls[0].args[0]())
|
||||
.toBe(mockScope.$parent.testToolbar.state);
|
||||
});
|
||||
|
||||
it("removes state from parent scope on destroy", function () {
|
||||
// Verify precondition
|
||||
expect(mockScope.$parent.testToolbar).toBeDefined();
|
||||
// Destroy the representer
|
||||
representer.destroy();
|
||||
// Should have removed toolbar state from view
|
||||
expect(mockScope.$parent.testToolbar).toBeUndefined();
|
||||
});
|
||||
|
||||
// Verify a simple interaction between selection state and toolbar
|
||||
// state; more complicated interactions are tested in EditToolbar.
|
||||
it("conveys state changes", function () {
|
||||
var testObject = { k: 123 };
|
||||
|
||||
// Provide a view which has a toolbar
|
||||
representer.represent({
|
||||
toolbar: { sections: [{ items: [{ property: 'k' }] }] }
|
||||
});
|
||||
|
||||
// Update the selection
|
||||
mockScope.selection.select(testObject);
|
||||
expect(mockScope.$watchCollection.mostRecentCall.args[0])
|
||||
.toEqual('selection.all()'); // Make sure we're using right watch
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]([testObject]);
|
||||
|
||||
// Update the state
|
||||
mockScope.$parent.testToolbar.state[0] = 456;
|
||||
// Invoke the first watch (assumed to be for toolbar state)
|
||||
mockScope.$watchCollection.calls[0].args[1](
|
||||
mockScope.$parent.testToolbar.state
|
||||
);
|
||||
|
||||
// Should have updated the original object
|
||||
expect(testObject.k).toEqual(456);
|
||||
|
||||
// Should have committed the change
|
||||
expect(mockScope.commit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not commit if nothing changed", function () {
|
||||
var testObject = { k: 123 };
|
||||
|
||||
// Provide a view which has a toolbar
|
||||
representer.represent({
|
||||
toolbar: { sections: [{ items: [{ property: 'k' }] }] }
|
||||
});
|
||||
|
||||
// Update the selection
|
||||
mockScope.selection.select(testObject);
|
||||
expect(mockScope.$watchCollection.mostRecentCall.args[0])
|
||||
.toEqual('selection.all()'); // Make sure we're using right watch
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]([testObject]);
|
||||
|
||||
// Invoke the first watch (assumed to be for toolbar state)
|
||||
mockScope.$watchCollection.calls[0].args[1](
|
||||
mockScope.$parent.testToolbar.state
|
||||
);
|
||||
|
||||
// Should have committed the change
|
||||
expect(mockScope.commit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -1,128 +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/EditToolbarSelection'],
|
||||
function (EditToolbarSelection) {
|
||||
|
||||
describe("The Edit mode selection manager", function () {
|
||||
var testProxy,
|
||||
testElement,
|
||||
otherElement,
|
||||
selection,
|
||||
mockSelection,
|
||||
mockOpenMCT,
|
||||
mockScope;
|
||||
|
||||
beforeEach(function () {
|
||||
testProxy = { someKey: "some value" };
|
||||
testElement = { someOtherKey: "some other value" };
|
||||
otherElement = { yetAnotherKey: 42 };
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
// 'select',
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn([]);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
mockScope = jasmine.createSpyObj('$scope', [
|
||||
'$on',
|
||||
'$apply'
|
||||
]);
|
||||
|
||||
selection = new EditToolbarSelection(mockScope, mockOpenMCT);
|
||||
selection.proxy(testProxy);
|
||||
});
|
||||
|
||||
it("adds the proxy to the selection array", function () {
|
||||
expect(selection.all()).toEqual([testProxy]);
|
||||
});
|
||||
|
||||
it("exposes view proxy", function () {
|
||||
expect(selection.proxy()).toBe(testProxy);
|
||||
});
|
||||
|
||||
it("includes selected objects alongside the proxy", function () {
|
||||
selection.select(testElement);
|
||||
expect(selection.all()).toEqual([testProxy, testElement]);
|
||||
});
|
||||
|
||||
it("allows elements to be deselected", function () {
|
||||
selection.select(testElement);
|
||||
selection.deselect();
|
||||
expect(selection.all()).toEqual([testProxy]);
|
||||
});
|
||||
|
||||
it("replaces old selections with new ones", function () {
|
||||
selection.select(testElement);
|
||||
selection.select(otherElement);
|
||||
expect(selection.all()).toEqual([testProxy, otherElement]);
|
||||
});
|
||||
|
||||
it("allows retrieval of the current selection", function () {
|
||||
selection.select(testElement);
|
||||
expect(selection.get()).toBe(testElement);
|
||||
selection.select(otherElement);
|
||||
expect(selection.get()).toBe(otherElement);
|
||||
});
|
||||
|
||||
it("can check if an element is selected", function () {
|
||||
selection.select(testElement);
|
||||
expect(selection.selected(testElement)).toBeTruthy();
|
||||
expect(selection.selected(otherElement)).toBeFalsy();
|
||||
selection.select(otherElement);
|
||||
expect(selection.selected(testElement)).toBeFalsy();
|
||||
expect(selection.selected(otherElement)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("considers the proxy to be selected", function () {
|
||||
expect(selection.selected(testProxy)).toBeTruthy();
|
||||
selection.select(testElement);
|
||||
// Even when something else is selected...
|
||||
expect(selection.selected(testProxy)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("treats selection of the proxy as a no-op", function () {
|
||||
selection.select(testProxy);
|
||||
expect(selection.all()).toEqual([testProxy]);
|
||||
});
|
||||
|
||||
it("cleans up selection on scope destroy", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
'$destroy',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
|
||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -25,7 +25,10 @@ define(
|
||||
function (EditToolbar) {
|
||||
|
||||
describe("An Edit mode toolbar", function () {
|
||||
var mockCommit,
|
||||
var mockOpenMCT,
|
||||
mockScope,
|
||||
mockObjects,
|
||||
mockDomainObject,
|
||||
testStructure,
|
||||
testAB,
|
||||
testABC,
|
||||
@ -35,35 +38,30 @@ define(
|
||||
testM,
|
||||
toolbar;
|
||||
|
||||
function getVisibility(obj) {
|
||||
return !obj.hidden;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockCommit = jasmine.createSpy('commit');
|
||||
testStructure = {
|
||||
sections: [
|
||||
{
|
||||
items: [
|
||||
{ name: "A", property: "a", exclusive: true },
|
||||
{ name: "B", property: "b", exclusive: true },
|
||||
{ name: "C", property: "c", exclusive: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{ name: "X", property: "x" },
|
||||
{ name: "Y", property: "y", exclusive: true },
|
||||
{ name: "Z", property: "z", exclusive: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{ name: "M", method: "m", exclusive: true }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
mockOpenMCT = jasmine.createSpy('openmct', ['objects']);
|
||||
mockObjects = jasmine.createSpyObj('objects', ['observe']);
|
||||
mockObjects.observe.andReturn();
|
||||
mockOpenMCT.objects = mockObjects;
|
||||
mockScope = jasmine.createSpyObj("$scope", [
|
||||
"$watchCollection",
|
||||
"$on"
|
||||
]);
|
||||
mockScope.$watchCollection.andReturn();
|
||||
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 }
|
||||
];
|
||||
|
||||
testAB = { a: 0, b: 1 };
|
||||
testABC = { a: 0, b: 1, c: 2 };
|
||||
testABC2 = { a: 4, b: 1, c: 2 }; // For inconsistent-state checking
|
||||
@ -71,151 +69,17 @@ define(
|
||||
testABCYZ = { a: 0, b: 1, c: 2, y: 'Y!', z: 'Z!' };
|
||||
testM = { m: jasmine.createSpy("method") };
|
||||
|
||||
toolbar = new EditToolbar(testStructure, mockCommit);
|
||||
});
|
||||
|
||||
it("provides properties from the original structure", function () {
|
||||
expect(
|
||||
new EditToolbar(testStructure, [testABC])
|
||||
.getStructure()
|
||||
.sections[0]
|
||||
.items[1]
|
||||
.name
|
||||
).toEqual("B");
|
||||
});
|
||||
|
||||
// This is needed by mct-toolbar
|
||||
it("adds keys to form structure", function () {
|
||||
expect(
|
||||
new EditToolbar(testStructure, [testABC])
|
||||
.getStructure()
|
||||
.sections[0]
|
||||
.items[1]
|
||||
.key
|
||||
).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it("marks empty sections as hidden", function () {
|
||||
// Verify that all sections are included when applicable...
|
||||
toolbar.setSelection([testABCXYZ]);
|
||||
expect(toolbar.getStructure().sections.map(getVisibility))
|
||||
.toEqual([true, true, false]);
|
||||
|
||||
// ...but omitted when only some are applicable
|
||||
toolbar.setSelection([testABC]);
|
||||
expect(toolbar.getStructure().sections.map(getVisibility))
|
||||
.toEqual([true, false, false]);
|
||||
});
|
||||
|
||||
it("reads properties from selections", function () {
|
||||
var structure, state;
|
||||
|
||||
toolbar.setSelection([testABC]);
|
||||
|
||||
structure = toolbar.getStructure();
|
||||
state = toolbar.getState();
|
||||
|
||||
expect(state[structure.sections[0].items[0].key])
|
||||
.toEqual(testABC.a);
|
||||
expect(state[structure.sections[0].items[1].key])
|
||||
.toEqual(testABC.b);
|
||||
expect(state[structure.sections[0].items[2].key])
|
||||
.toEqual(testABC.c);
|
||||
});
|
||||
|
||||
it("reads properties from getters", function () {
|
||||
var structure, state;
|
||||
|
||||
testABC.a = function () {
|
||||
return "from a getter!";
|
||||
};
|
||||
|
||||
toolbar.setSelection([testABC]);
|
||||
structure = toolbar.getStructure();
|
||||
state = toolbar.getState();
|
||||
|
||||
expect(state[structure.sections[0].items[0].key])
|
||||
.toEqual("from a getter!");
|
||||
});
|
||||
|
||||
it("sets properties on update", function () {
|
||||
toolbar.setSelection([testABC]);
|
||||
toolbar.updateState(
|
||||
toolbar.getStructure().sections[0].items[0].key,
|
||||
"new value"
|
||||
);
|
||||
// Should have updated the underlying object
|
||||
expect(testABC.a).toEqual("new value");
|
||||
});
|
||||
|
||||
it("invokes setters on update", function () {
|
||||
var structure;
|
||||
|
||||
testABC.a = jasmine.createSpy('a');
|
||||
|
||||
toolbar.setSelection([testABC]);
|
||||
structure = toolbar.getStructure();
|
||||
|
||||
toolbar.updateState(
|
||||
structure.sections[0].items[0].key,
|
||||
"new value"
|
||||
);
|
||||
// Should have updated the underlying object
|
||||
expect(testABC.a).toHaveBeenCalledWith("new value");
|
||||
});
|
||||
|
||||
it("provides a return value describing update status", function () {
|
||||
// Should return true if actually updated, otherwise false
|
||||
var key;
|
||||
toolbar.setSelection([testABC]);
|
||||
key = toolbar.getStructure().sections[0].items[0].key;
|
||||
expect(toolbar.updateState(key, testABC.a)).toBeFalsy();
|
||||
expect(toolbar.updateState(key, "new value")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("removes inapplicable items", function () {
|
||||
// First, verify with all items
|
||||
toolbar.setSelection([testABC]);
|
||||
expect(toolbar.getStructure().sections[0].items.map(getVisibility))
|
||||
.toEqual([true, true, true]);
|
||||
// Then, try with some items omitted
|
||||
toolbar.setSelection([testABC, testAB]);
|
||||
expect(toolbar.getStructure().sections[0].items.map(getVisibility))
|
||||
.toEqual([true, true, false]);
|
||||
});
|
||||
|
||||
it("removes inconsistent states", function () {
|
||||
// Only two of three values match among these selections
|
||||
toolbar.setSelection([testABC, testABC2]);
|
||||
expect(toolbar.getStructure().sections[0].items.map(getVisibility))
|
||||
.toEqual([false, true, true]);
|
||||
});
|
||||
|
||||
it("allows inclusive items", function () {
|
||||
// One inclusive item is in the set, property 'x' of the
|
||||
// second section; make sure items are pruned down
|
||||
// when only some of the selection has x,y,z properties
|
||||
toolbar.setSelection([testABC, testABCXYZ]);
|
||||
expect(toolbar.getStructure().sections[1].items.map(getVisibility))
|
||||
.toEqual([true, false, false]);
|
||||
});
|
||||
|
||||
it("removes inclusive items when there are no matches", function () {
|
||||
toolbar.setSelection([testABCYZ]);
|
||||
expect(toolbar.getStructure().sections[1].items.map(getVisibility))
|
||||
.toEqual([false, true, true]);
|
||||
toolbar = new EditToolbar(mockScope, mockOpenMCT, testStructure);
|
||||
});
|
||||
|
||||
it("adds click functions when a method is specified", function () {
|
||||
toolbar.setSelection([testM]);
|
||||
// Verify precondition
|
||||
expect(testM.m).not.toHaveBeenCalled();
|
||||
// Click!
|
||||
toolbar.getStructure().sections[2].items[0].click();
|
||||
// Should have called the underlying function
|
||||
expect(testM.m).toHaveBeenCalled();
|
||||
// Should also have committed the change
|
||||
expect(mockCommit).toHaveBeenCalled();
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -28,6 +28,16 @@ define(
|
||||
* The mct-selectable directive allows selection functionality
|
||||
* (click) to be attached to specific elements.
|
||||
*
|
||||
* Example of how to use the directive:
|
||||
*
|
||||
* mct-selectable="{
|
||||
* // item is an optional domain object.
|
||||
* item: domainObject,
|
||||
* // Can define other arbitrary properties.
|
||||
* elementProxy: element,
|
||||
* controller: fixedController
|
||||
* }"
|
||||
*
|
||||
* @memberof platform/commonUI/general
|
||||
* @constructor
|
||||
*/
|
||||
|
@ -39,20 +39,64 @@ define([
|
||||
"cssClass": "icon-box-with-dashed-lines",
|
||||
"type": "telemetry.fixed",
|
||||
"template": fixedTemplate,
|
||||
"uses": [
|
||||
"composition"
|
||||
"uses": [],
|
||||
"editable": true
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"toolbar": {
|
||||
"sections": [
|
||||
"toolbars": [
|
||||
{
|
||||
"items": [
|
||||
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 [
|
||||
{
|
||||
"method": "add",
|
||||
"cssClass": "icon-plus",
|
||||
"control": "menu-button",
|
||||
"text": "Add",
|
||||
"options": [
|
||||
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",
|
||||
@ -74,18 +118,21 @@ define([
|
||||
"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": [
|
||||
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",
|
||||
@ -109,170 +156,192 @@ define([
|
||||
]
|
||||
},
|
||||
{
|
||||
"property": "fill",
|
||||
"cssClass": "icon-paint-bucket",
|
||||
"title": "Fill color",
|
||||
"description": "Set fill color",
|
||||
"control": "color"
|
||||
control: "color",
|
||||
domainObject: domainObject,
|
||||
property: path + ".fill",
|
||||
cssClass: "icon-paint-bucket",
|
||||
title: "Fill color",
|
||||
description: "Set fill color",
|
||||
key: 'fill'
|
||||
},
|
||||
{
|
||||
"property": "stroke",
|
||||
"cssClass": "icon-line-horz",
|
||||
"title": "Border color",
|
||||
"description": "Set border color",
|
||||
"control": "color"
|
||||
control: "color",
|
||||
domainObject: domainObject,
|
||||
property: path + ".stroke",
|
||||
cssClass: "icon-line-horz",
|
||||
title: "Border color",
|
||||
description: "Set border color",
|
||||
key: 'stroke'
|
||||
},
|
||||
{
|
||||
"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
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"property": "color",
|
||||
"cssClass": "icon-T",
|
||||
"title": "Text color",
|
||||
"description": "Set text color",
|
||||
"mandatory": true,
|
||||
"control": "color"
|
||||
control: "color",
|
||||
domainObject: domainObject,
|
||||
property: path + ".color",
|
||||
cssClass: "icon-T",
|
||||
title: "Text color",
|
||||
mandatory: true,
|
||||
description: "Set text color",
|
||||
key: 'color'
|
||||
},
|
||||
{
|
||||
"property": "size",
|
||||
"title": "Text size",
|
||||
"description": "Set text size",
|
||||
"control": "select",
|
||||
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'
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"property": "editX",
|
||||
"text": "X",
|
||||
"name": "X",
|
||||
"cssClass": "l-input-sm",
|
||||
"control": "numberfield",
|
||||
"min": "0"
|
||||
control: "numberfield",
|
||||
domainObject: domainObject,
|
||||
property: path + ".x",
|
||||
text: "X",
|
||||
name: "X",
|
||||
key: "x",
|
||||
cssClass: "l-input-sm",
|
||||
min: "0"
|
||||
},
|
||||
{
|
||||
"property": "editY",
|
||||
"text": "Y",
|
||||
"name": "Y",
|
||||
"cssClass": "l-input-sm",
|
||||
"control": "numberfield",
|
||||
"min": "0"
|
||||
control: "numberfield",
|
||||
domainObject: domainObject,
|
||||
property: path + ".y",
|
||||
text: "Y",
|
||||
name: "Y",
|
||||
key: "y",
|
||||
cssClass: "l-input-sm",
|
||||
min: "0"
|
||||
},
|
||||
{
|
||||
"property": "editX1",
|
||||
"text": "X1",
|
||||
"name": "X1",
|
||||
"cssClass": "l-input-sm",
|
||||
"control" : "numberfield",
|
||||
"min": "0"
|
||||
control: "numberfield",
|
||||
domainObject: domainObject,
|
||||
property: path + ".x",
|
||||
text: "X1",
|
||||
name: "X1",
|
||||
key: "x1",
|
||||
cssClass: "l-input-sm",
|
||||
min: "0"
|
||||
},
|
||||
{
|
||||
"property": "editY1",
|
||||
"text": "Y1",
|
||||
"name": "Y1",
|
||||
"cssClass": "l-input-sm",
|
||||
"control" : "numberfield",
|
||||
"min": "0"
|
||||
control: "numberfield",
|
||||
domainObject: domainObject,
|
||||
property: path + ".y",
|
||||
text: "Y1",
|
||||
name: "Y1",
|
||||
key: "y1",
|
||||
cssClass: "l-input-sm",
|
||||
min: "0"
|
||||
},
|
||||
{
|
||||
"property": "editX2",
|
||||
"text": "X2",
|
||||
"name": "X2",
|
||||
"cssClass": "l-input-sm",
|
||||
"control" : "numberfield",
|
||||
"min": "0"
|
||||
control: "numberfield",
|
||||
domainObject: domainObject,
|
||||
property: path + ".x2",
|
||||
text: "X2",
|
||||
name: "X2",
|
||||
key: "x2",
|
||||
cssClass: "l-input-sm",
|
||||
min: "0"
|
||||
},
|
||||
{
|
||||
"property": "editY2",
|
||||
"text": "Y2",
|
||||
"name": "Y2",
|
||||
"cssClass": "l-input-sm",
|
||||
"control" : "numberfield",
|
||||
"min": "0"
|
||||
control: "numberfield",
|
||||
domainObject: domainObject,
|
||||
property: path + ".y2",
|
||||
text: "Y2",
|
||||
name: "Y2",
|
||||
key: "y2",
|
||||
cssClass: "l-input-sm",
|
||||
min: "0"
|
||||
},
|
||||
{
|
||||
"property": "editHeight",
|
||||
"text": "H",
|
||||
"name": "H",
|
||||
"cssClass": "l-input-sm",
|
||||
"control": "numberfield",
|
||||
"description": "Resize object height",
|
||||
"min": "1"
|
||||
control: "numberfield",
|
||||
domainObject: domainObject,
|
||||
property: path + ".height",
|
||||
text: "H",
|
||||
name: "H",
|
||||
key: "height",
|
||||
cssClass: "l-input-sm",
|
||||
description: "Resize object height",
|
||||
min: "1"
|
||||
},
|
||||
{
|
||||
"property": "editWidth",
|
||||
"text": "W",
|
||||
"name": "W",
|
||||
"cssClass": "l-input-sm",
|
||||
"control": "numberfield",
|
||||
"description": "Resize object width",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"property": "useGrid",
|
||||
"name": "Snap to Grid",
|
||||
"control": "checkbox"
|
||||
}
|
||||
]
|
||||
control: "checkbox",
|
||||
domainObject: domainObject,
|
||||
property: path + ".useGrid",
|
||||
name: "Snap to Grid",
|
||||
key: "useGrid"
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"property": "text",
|
||||
"cssClass": "icon-gear",
|
||||
"control": "dialog-button",
|
||||
"title": "Text Properties",
|
||||
"description": "Edit text properties",
|
||||
"dialog": {
|
||||
"control": "textfield",
|
||||
"name": "Text",
|
||||
"required": true
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "showTitle",
|
||||
"cssClass": "icon-two-parts-both",
|
||||
"control": "button",
|
||||
"title": "Show title",
|
||||
"description": "Show telemetry element title"
|
||||
control: "checkbox",
|
||||
domainObject: domainObject,
|
||||
property: path + ".titled",
|
||||
name: "Show Title",
|
||||
key: "titled"
|
||||
},
|
||||
{
|
||||
"method": "hideTitle",
|
||||
"cssClass": "icon-two-parts-one-only",
|
||||
"control": "button",
|
||||
"title": "Hide title",
|
||||
"description": "Hide telemetry element title"
|
||||
}
|
||||
]
|
||||
control: "button",
|
||||
domainObject: domainObject,
|
||||
method: function () {
|
||||
selection[0].context.fixedController.remove(
|
||||
selection[0].context.elementProxy
|
||||
);
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"method": "remove",
|
||||
"control": "button",
|
||||
"cssClass": "icon-trash"
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -62,29 +62,7 @@ define([
|
||||
"type": "layout",
|
||||
"template": layoutTemplate,
|
||||
"editable": true,
|
||||
"uses": [],
|
||||
"toolbar": {
|
||||
"sections": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"method": "showFrame",
|
||||
"cssClass": "icon-frame-show",
|
||||
"control": "button",
|
||||
"title": "Show frame",
|
||||
"description": "Show frame"
|
||||
},
|
||||
{
|
||||
"method": "hideFrame",
|
||||
"cssClass": "icon-frame-hide",
|
||||
"control": "button",
|
||||
"title": "Hide frame",
|
||||
"description": "Hide frame"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"uses": []
|
||||
},
|
||||
{
|
||||
"key": "fixed",
|
||||
@ -305,6 +283,27 @@ define([
|
||||
"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",
|
||||
@ -314,7 +313,14 @@ define([
|
||||
"priority": 900,
|
||||
"features": "creation",
|
||||
"model": {
|
||||
"composition": []
|
||||
"composition": [],
|
||||
configuration: {
|
||||
layout: {
|
||||
panels: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
|
@ -41,7 +41,7 @@
|
||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
||||
ng-repeat="childObject in composition"
|
||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
||||
mct-selectable="controller.getContext(childObject, true)"
|
||||
mct-selectable="controller.getContext(childObject)"
|
||||
ng-dblclick="controller.drill($event, childObject)"
|
||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
||||
|
||||
|
@ -38,6 +38,24 @@ define(
|
||||
|
||||
var DEFAULT_DIMENSIONS = [2, 1];
|
||||
|
||||
// Convert from element x/y/width/height to an
|
||||
// appropriate ng-style argument, to position elements.
|
||||
function convertPosition(elementProxy) {
|
||||
if (elementProxy.getStyle) {
|
||||
return elementProxy.getStyle();
|
||||
}
|
||||
|
||||
var gridSize = elementProxy.getGridSize();
|
||||
|
||||
// Multiply position/dimensions by grid size
|
||||
return {
|
||||
left: (gridSize[0] * elementProxy.element.x) + 'px',
|
||||
top: (gridSize[1] * elementProxy.element.y) + 'px',
|
||||
width: (gridSize[0] * elementProxy.element.width) + 'px',
|
||||
height: (gridSize[1] * elementProxy.element.height) + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The FixedController is responsible for supporting the
|
||||
* Fixed Position view. It arranges frames according to saved
|
||||
@ -51,14 +69,14 @@ define(
|
||||
this.names = {}; // Cache names by ID
|
||||
this.values = {}; // Cache values by ID
|
||||
this.elementProxiesById = {};
|
||||
|
||||
this.telemetryObjects = [];
|
||||
this.subscriptions = [];
|
||||
this.telemetryObjects = {};
|
||||
this.subscriptions = {};
|
||||
this.openmct = openmct;
|
||||
this.$element = $element;
|
||||
this.$scope = $scope;
|
||||
|
||||
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
|
||||
this.dialogService = dialogService;
|
||||
this.$q = $q;
|
||||
this.newDomainObject = $scope.domainObject.useCapability('adapter');
|
||||
this.fixedViewSelectable = false;
|
||||
|
||||
var self = this;
|
||||
@ -67,59 +85,13 @@ define(
|
||||
'fetchHistoricalData',
|
||||
'getTelemetry',
|
||||
'setDisplayedValue',
|
||||
'subscribeToObjects',
|
||||
'subscribeToObject',
|
||||
'unsubscribe',
|
||||
'updateView'
|
||||
].forEach(function (name) {
|
||||
self[name] = self[name].bind(self);
|
||||
});
|
||||
|
||||
// Convert from element x/y/width/height to an
|
||||
// appropriate ng-style argument, to position elements.
|
||||
function convertPosition(elementProxy) {
|
||||
var gridSize = elementProxy.getGridSize();
|
||||
// Multiply position/dimensions by grid size
|
||||
return {
|
||||
left: (gridSize[0] * elementProxy.x()) + 'px',
|
||||
top: (gridSize[1] * elementProxy.y()) + 'px',
|
||||
width: (gridSize[0] * elementProxy.width()) + 'px',
|
||||
height: (gridSize[1] * elementProxy.height()) + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
// Update the style for a selected element
|
||||
function updateSelectionStyle() {
|
||||
if (self.selectedElementProxy) {
|
||||
self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a specific drag handle
|
||||
function generateDragHandle(elementHandle) {
|
||||
return new FixedDragHandle(
|
||||
elementHandle,
|
||||
self.gridSize,
|
||||
updateSelectionStyle,
|
||||
$scope.commit
|
||||
);
|
||||
}
|
||||
|
||||
// Generate drag handles for an element
|
||||
function generateDragHandles(element) {
|
||||
return element.handles().map(generateDragHandle);
|
||||
}
|
||||
|
||||
// Update element positions when grid size changes
|
||||
function updateElementPositions(layoutGrid) {
|
||||
// Update grid size from model
|
||||
self.gridSize = layoutGrid;
|
||||
|
||||
self.elementProxies.forEach(function (elementProxy) {
|
||||
elementProxy.setGridSize(self.gridSize);
|
||||
elementProxy.style = convertPosition(elementProxy);
|
||||
});
|
||||
}
|
||||
|
||||
// Decorate an element for display
|
||||
function makeProxyElement(element, index, elements) {
|
||||
var ElementProxy = ElementProxies[element.type],
|
||||
@ -137,14 +109,14 @@ define(
|
||||
|
||||
// Decorate elements in the current configuration
|
||||
function refreshElements() {
|
||||
var elements = (($scope.configuration || {}).elements || []);
|
||||
var elements = (((self.newDomainObject.configuration || {})['fixed-display'] || {}).elements || []);
|
||||
|
||||
// Create the new proxies...
|
||||
self.elementProxies = elements.map(makeProxyElement);
|
||||
|
||||
if (self.selectedElementProxy) {
|
||||
// If selection is not in array, select parent.
|
||||
// Otherwise, set the element to select after refresh.
|
||||
if (self.selectedElementProxy) {
|
||||
var index = elements.indexOf(self.selectedElementProxy.element);
|
||||
if (index === -1) {
|
||||
self.$element[0].click();
|
||||
@ -168,79 +140,53 @@ define(
|
||||
});
|
||||
}
|
||||
|
||||
function removeObjects(ids) {
|
||||
var configuration = self.$scope.configuration;
|
||||
|
||||
if (configuration &&
|
||||
configuration.elements) {
|
||||
configuration.elements = configuration.elements.filter(function (proxy) {
|
||||
return ids.indexOf(proxy.id) === -1;
|
||||
});
|
||||
}
|
||||
self.getTelemetry($scope.domainObject);
|
||||
refreshElements();
|
||||
// Mark change as persistable
|
||||
if (self.$scope.commit) {
|
||||
self.$scope.commit("Objects removed.");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle changes in the object's composition
|
||||
function updateComposition(composition, previousComposition) {
|
||||
var removedIds = [];
|
||||
// Resubscribe - objects in view have changed
|
||||
if (composition !== previousComposition) {
|
||||
//remove any elements no longer in the composition
|
||||
removedIds = _.difference(previousComposition, composition);
|
||||
if (removedIds.length > 0) {
|
||||
removeObjects(removedIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger a new query for telemetry data
|
||||
function updateDisplayBounds(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
//Reset values
|
||||
self.values = {};
|
||||
refreshElements();
|
||||
|
||||
//Fetch new data
|
||||
self.fetchHistoricalData(self.telemetryObjects);
|
||||
Object.values(self.telemetryObjects).forEach(function (object) {
|
||||
self.fetchHistoricalData(object);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add an element to this view
|
||||
function addElement(element) {
|
||||
// Ensure that configuration field is populated
|
||||
$scope.configuration = $scope.configuration || {};
|
||||
// Make sure there is a "elements" field in the
|
||||
// view configuration.
|
||||
$scope.configuration.elements =
|
||||
$scope.configuration.elements || [];
|
||||
// Store the position of this element.
|
||||
$scope.configuration.elements.push(element);
|
||||
var index;
|
||||
var elements = (((self.newDomainObject.configuration || {})['fixed-display'] || {}).elements || []);
|
||||
elements.push(element);
|
||||
|
||||
self.elementToSelectAfterRefresh = element;
|
||||
|
||||
// Refresh displayed elements
|
||||
refreshElements();
|
||||
|
||||
// Mark change as persistable
|
||||
if ($scope.commit) {
|
||||
$scope.commit("Dropped an element.");
|
||||
if (self.selectedElementProxy) {
|
||||
index = elements.indexOf(self.selectedElementProxy.element);
|
||||
}
|
||||
|
||||
self.mutate("configuration['fixed-display'].elements", elements);
|
||||
elements = (self.newDomainObject.configuration)['fixed-display'].elements || [];
|
||||
self.elementToSelectAfterRefresh = elements[elements.length - 1];
|
||||
|
||||
if (self.selectedElementProxy) {
|
||||
// Update the selected element with the new
|
||||
// value since newDomainOject is mutated.
|
||||
self.selectedElementProxy.element = elements[index];
|
||||
}
|
||||
refreshElements();
|
||||
}
|
||||
|
||||
// Position a panel after a drop event
|
||||
function handleDrop(e, id, position) {
|
||||
// Don't handle this event if it has already been handled
|
||||
// color is set to "" to let the CSS theme determine the default color
|
||||
if (e.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Store the position of this element.
|
||||
// color is set to "" to let the CSS theme determine the default color
|
||||
addElement({
|
||||
type: "fixed.telemetry",
|
||||
x: Math.floor(position.x / self.gridSize[0]),
|
||||
@ -254,70 +200,228 @@ define(
|
||||
useGrid: true
|
||||
});
|
||||
|
||||
//Re-initialize objects, and subscribe to new object
|
||||
self.getTelemetry($scope.domainObject);
|
||||
// Subscribe to the new object to get telemetry
|
||||
self.openmct.objects.get(id).then(function (object) {
|
||||
self.getTelemetry(object);
|
||||
});
|
||||
}
|
||||
|
||||
// Sets the selectable object in response to the selection change event.
|
||||
function setSelection(selectable) {
|
||||
this.elementProxies = [];
|
||||
this.addElement = addElement;
|
||||
this.refreshElements = refreshElements;
|
||||
this.fixedProxy = new FixedProxy(this.addElement, this.$q, this.dialogService);
|
||||
|
||||
this.composition = this.openmct.composition.get(this.newDomainObject);
|
||||
this.composition.on('add', this.onCompositionAdd, this);
|
||||
this.composition.on('remove', this.onCompositionRemove, this);
|
||||
this.composition.load();
|
||||
|
||||
// Position panes where they are dropped
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
|
||||
$scope.$on("$destroy", this.destroy.bind(this));
|
||||
|
||||
// Respond to external bounds changes
|
||||
this.openmct.time.on("bounds", updateDisplayBounds);
|
||||
|
||||
this.openmct.selection.on('change', this.setSelection.bind(this));
|
||||
this.$element.on('click', this.bypassSelection.bind(this));
|
||||
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
|
||||
this.newDomainObject = JSON.parse(JSON.stringify(obj));
|
||||
this.updateElementPositions(this.newDomainObject.layoutGrid);
|
||||
}.bind(this));
|
||||
|
||||
this.updateElementPositions(this.newDomainObject.layoutGrid);
|
||||
refreshElements();
|
||||
}
|
||||
|
||||
FixedController.prototype.updateElementPositions = function (layoutGrid) {
|
||||
this.gridSize = layoutGrid;
|
||||
|
||||
this.elementProxies.forEach(function (elementProxy) {
|
||||
elementProxy.setGridSize(this.gridSize);
|
||||
elementProxy.style = convertPosition(elementProxy);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
FixedController.prototype.onCompositionAdd = function (object) {
|
||||
this.getTelemetry(object);
|
||||
};
|
||||
|
||||
FixedController.prototype.onCompositionRemove = function (identifier) {
|
||||
// Defer mutation of newDomainObject to prevent mutating an
|
||||
// outdated version since this is triggered by a composition change.
|
||||
setTimeout(function () {
|
||||
var id = objectUtils.makeKeyString(identifier);
|
||||
var elements = this.newDomainObject.configuration['fixed-display'].elements || [];
|
||||
var newElements = elements.filter(function (proxy) {
|
||||
return proxy.id !== id;
|
||||
});
|
||||
this.mutate("configuration['fixed-display'].elements", newElements);
|
||||
|
||||
if (this.subscriptions[id]) {
|
||||
this.subscriptions[id]();
|
||||
delete this.subscriptions[id];
|
||||
}
|
||||
|
||||
delete this.telemetryObjects[id];
|
||||
this.refreshElements();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes an element from the view.
|
||||
*
|
||||
* @param {Object} elementProxy the element proxy to remove.
|
||||
*/
|
||||
FixedController.prototype.remove = function (elementProxy) {
|
||||
var element = elementProxy.element;
|
||||
var elements = this.newDomainObject.configuration['fixed-display'].elements || [];
|
||||
elements.splice(elements.indexOf(element), 1);
|
||||
|
||||
if (element.type === 'fixed.telemetry') {
|
||||
this.newDomainObject.composition = this.newDomainObject.composition.filter(function (identifier) {
|
||||
return objectUtils.makeKeyString(identifier) !== element.id;
|
||||
});
|
||||
}
|
||||
|
||||
this.mutate("configuration['fixed-display'].elements", elements);
|
||||
this.refreshElements();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new element to the view.
|
||||
*
|
||||
* @param {string} type the type of element to add. Supported types are:
|
||||
* `fixed.image`
|
||||
* `fixed.box`
|
||||
* `fixed.text`
|
||||
* `fixed.line`
|
||||
*/
|
||||
FixedController.prototype.add = function (type) {
|
||||
this.fixedProxy.add(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the display order of the element proxy.
|
||||
*/
|
||||
FixedController.prototype.order = function (elementProxy, position) {
|
||||
var elements = elementProxy.order(position);
|
||||
|
||||
// Find the selected element index in the updated array.
|
||||
var selectedElemenetIndex = elements.indexOf(this.selectedElementProxy.element);
|
||||
|
||||
this.mutate("configuration['fixed-display'].elements", elements);
|
||||
elements = (this.newDomainObject.configuration)['fixed-display'].elements || [];
|
||||
|
||||
// Update the selected element with the new
|
||||
// value since newDomainOject is mutated.
|
||||
this.selectedElementProxy.element = elements[selectedElemenetIndex];
|
||||
this.refreshElements();
|
||||
};
|
||||
|
||||
FixedController.prototype.generateDragHandle = function (elementProxy, elementHandle) {
|
||||
var index = this.elementProxies.indexOf(elementProxy);
|
||||
|
||||
if (elementHandle) {
|
||||
elementHandle.element = elementProxy.element;
|
||||
elementProxy = elementHandle;
|
||||
}
|
||||
|
||||
return new FixedDragHandle(
|
||||
elementProxy,
|
||||
"configuration['fixed-display'].elements[" + index + "]",
|
||||
this
|
||||
);
|
||||
};
|
||||
|
||||
FixedController.prototype.generateDragHandles = function (elementProxy) {
|
||||
return elementProxy.handles().map(function (handle) {
|
||||
return this.generateDragHandle(elementProxy, handle);
|
||||
}, this);
|
||||
};
|
||||
|
||||
FixedController.prototype.updateSelectionStyle = function () {
|
||||
this.selectedElementProxy.style = convertPosition(this.selectedElementProxy);
|
||||
};
|
||||
|
||||
FixedController.prototype.setSelection = function (selectable) {
|
||||
var selection = selectable[0];
|
||||
|
||||
if (this.selectionListeners) {
|
||||
this.selectionListeners.forEach(function (l) {
|
||||
l();
|
||||
});
|
||||
}
|
||||
|
||||
this.selectionListeners = [];
|
||||
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selection.context.elementProxy) {
|
||||
self.selectedElementProxy = selection.context.elementProxy;
|
||||
self.mvHandle = self.generateDragHandle(self.selectedElementProxy);
|
||||
self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
|
||||
this.selectedElementProxy = selection.context.elementProxy;
|
||||
this.attachSelectionListeners();
|
||||
this.mvHandle = this.generateDragHandle(this.selectedElementProxy);
|
||||
this.resizeHandles = this.generateDragHandles(this.selectedElementProxy);
|
||||
} else {
|
||||
// Make fixed view selectable if it's not already.
|
||||
if (!self.fixedViewSelectable && selectable.length === 1) {
|
||||
self.fixedViewSelectable = true;
|
||||
selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
|
||||
self.openmct.selection.select(selection);
|
||||
if (!this.fixedViewSelectable && selectable.length === 1) {
|
||||
this.fixedViewSelectable = true;
|
||||
selection.context.fixedController = this;
|
||||
this.openmct.selection.select(selection);
|
||||
}
|
||||
|
||||
self.resizeHandles = [];
|
||||
self.mvHandle = undefined;
|
||||
self.selectedElementProxy = undefined;
|
||||
this.resizeHandles = [];
|
||||
this.mvHandle = undefined;
|
||||
this.selectedElementProxy = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
FixedController.prototype.attachSelectionListeners = function () {
|
||||
var index = this.elementProxies.indexOf(this.selectedElementProxy);
|
||||
var path = "configuration['fixed-display'].elements[" + index + "]";
|
||||
|
||||
this.selectionListeners.push(this.openmct.objects.observe(this.newDomainObject, path + ".useGrid", function (newValue) {
|
||||
if (this.selectedElementProxy.useGrid() !== newValue) {
|
||||
this.selectedElementProxy.useGrid(newValue);
|
||||
this.updateSelectionStyle();
|
||||
this.openmct.objects.mutate(this.newDomainObject, path, this.selectedElementProxy.element);
|
||||
}
|
||||
}.bind(this)));
|
||||
[
|
||||
"width",
|
||||
"height",
|
||||
"stroke",
|
||||
"fill",
|
||||
"x",
|
||||
"y",
|
||||
"x1",
|
||||
"y1",
|
||||
"x2",
|
||||
"y2",
|
||||
"color",
|
||||
"size",
|
||||
"text",
|
||||
"titled"
|
||||
].forEach(function (property) {
|
||||
this.selectionListeners.push(this.openmct.objects.observe(this.newDomainObject, path + "." + property, function (newValue) {
|
||||
this.selectedElementProxy.element[property] = newValue;
|
||||
this.updateSelectionStyle();
|
||||
}.bind(this)));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
this.elementProxies = [];
|
||||
this.generateDragHandle = generateDragHandle;
|
||||
this.generateDragHandles = generateDragHandles;
|
||||
this.updateSelectionStyle = updateSelectionStyle;
|
||||
|
||||
// Detect changes to grid size
|
||||
$scope.$watch("model.layoutGrid", updateElementPositions);
|
||||
|
||||
// Position panes where they are dropped
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
|
||||
// Position panes when the model field changes
|
||||
$scope.$watch("model.composition", updateComposition);
|
||||
|
||||
// Refresh list of elements whenever model changes
|
||||
$scope.$watch("model.modified", refreshElements);
|
||||
|
||||
// Subscribe to telemetry when an object is available
|
||||
$scope.$watch("domainObject", this.getTelemetry);
|
||||
|
||||
// Free up subscription on destroy
|
||||
$scope.$on("$destroy", function () {
|
||||
self.unsubscribe();
|
||||
self.openmct.time.off("bounds", updateDisplayBounds);
|
||||
self.openmct.selection.off("change", setSelection);
|
||||
});
|
||||
|
||||
// Respond to external bounds changes
|
||||
this.openmct.time.on("bounds", updateDisplayBounds);
|
||||
this.openmct.selection.on('change', setSelection);
|
||||
this.$element.on('click', this.bypassSelection.bind(this));
|
||||
|
||||
setSelection(this.openmct.selection.get());
|
||||
}
|
||||
FixedController.prototype.destroy = function () {
|
||||
this.unsubscribe();
|
||||
this.unlisten();
|
||||
this.openmct.time.off("bounds", this.updateDisplayBounds);
|
||||
this.openmct.selection.off("change", this.setSelection);
|
||||
this.composition.off('add', this.onCompositionAdd, this);
|
||||
this.composition.off('remove', this.onCompositionRemove, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* A rate-limited digest function. Caps digests at 60Hz
|
||||
@ -340,31 +444,30 @@ define(
|
||||
* @private
|
||||
*/
|
||||
FixedController.prototype.unsubscribe = function () {
|
||||
this.subscriptions.forEach(function (unsubscribeFunc) {
|
||||
Object.values(this.subscriptions).forEach(function (unsubscribeFunc) {
|
||||
unsubscribeFunc();
|
||||
});
|
||||
this.subscriptions = [];
|
||||
this.telemetryObjects = [];
|
||||
this.subscriptions = {};
|
||||
this.telemetryObjects = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribe to all given domain objects
|
||||
* Subscribe to the given domain object
|
||||
* @private
|
||||
* @param {object[]} objects Domain objects to subscribe to
|
||||
* @returns {object[]} The provided objects, for chaining.
|
||||
* @param {object} object Domain object to subscribe to
|
||||
* @returns {object} The provided object, for chaining.
|
||||
*/
|
||||
FixedController.prototype.subscribeToObjects = function (objects) {
|
||||
FixedController.prototype.subscribeToObject = function (object) {
|
||||
var self = this;
|
||||
var timeAPI = this.openmct.time;
|
||||
|
||||
this.subscriptions = objects.map(function (object) {
|
||||
return self.openmct.telemetry.subscribe(object, function (datum) {
|
||||
var id = objectUtils.makeKeyString(object.identifier);
|
||||
this.subscriptions[id] = self.openmct.telemetry.subscribe(object, function (datum) {
|
||||
if (timeAPI.clock() !== undefined) {
|
||||
self.updateView(object, datum);
|
||||
}
|
||||
}, {});
|
||||
});
|
||||
return objects;
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -416,23 +519,22 @@ define(
|
||||
};
|
||||
|
||||
/**
|
||||
* Request the last historical data point for the given domain objects
|
||||
* @param {object[]} objects
|
||||
* @returns {object[]} the provided objects for chaining.
|
||||
* Request the last historical data point for the given domain object
|
||||
* @param {object} object
|
||||
* @returns {object} the provided object for chaining.
|
||||
*/
|
||||
FixedController.prototype.fetchHistoricalData = function (objects) {
|
||||
FixedController.prototype.fetchHistoricalData = function (object) {
|
||||
var bounds = this.openmct.time.bounds();
|
||||
var self = this;
|
||||
|
||||
objects.forEach(function (object) {
|
||||
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
|
||||
.then(function (data) {
|
||||
if (data.length > 0) {
|
||||
self.updateView(object, data[data.length - 1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return objects;
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
|
||||
@ -457,33 +559,25 @@ define(
|
||||
};
|
||||
|
||||
FixedController.prototype.getTelemetry = function (domainObject) {
|
||||
var newObject = domainObject.useCapability('adapter');
|
||||
var self = this;
|
||||
var id = objectUtils.makeKeyString(domainObject.identifier);
|
||||
|
||||
if (this.subscriptions.length > 0) {
|
||||
this.unsubscribe();
|
||||
if (this.subscriptions[id]) {
|
||||
this.subscriptions[id]();
|
||||
delete this.subscriptions[id];
|
||||
}
|
||||
delete this.telemetryObjects[id];
|
||||
|
||||
if (!this.openmct.telemetry.isTelemetryObject(domainObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
function filterForTelemetryObjects(objects) {
|
||||
return objects.filter(function (object) {
|
||||
return self.openmct.telemetry.isTelemetryObject(object);
|
||||
});
|
||||
}
|
||||
// Initialize display
|
||||
this.telemetryObjects[id] = domainObject;
|
||||
this.setDisplayedValue(domainObject, "");
|
||||
|
||||
function initializeDisplay(objects) {
|
||||
self.telemetryObjects = objects;
|
||||
objects.forEach(function (object) {
|
||||
// Initialize values
|
||||
self.setDisplayedValue(object, "");
|
||||
});
|
||||
return objects;
|
||||
}
|
||||
|
||||
return this.openmct.composition.get(newObject).load()
|
||||
.then(filterForTelemetryObjects)
|
||||
.then(initializeDisplay)
|
||||
return Promise.resolve(domainObject)
|
||||
.then(this.fetchHistoricalData)
|
||||
.then(this.subscribeToObjects);
|
||||
.then(this.subscribeToObject);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -580,12 +674,12 @@ define(
|
||||
* Gets the selection context.
|
||||
*
|
||||
* @param elementProxy the element proxy
|
||||
* @returns {object} the context object which includes elementProxy and toolbar
|
||||
* @returns {object} the context object which includes elementProxy
|
||||
*/
|
||||
FixedController.prototype.getContext = function (elementProxy) {
|
||||
return {
|
||||
elementProxy: elementProxy,
|
||||
toolbar: elementProxy
|
||||
fixedController: this
|
||||
};
|
||||
};
|
||||
|
||||
@ -608,6 +702,10 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
FixedController.prototype.mutate = function (path, value) {
|
||||
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
||||
};
|
||||
|
||||
return FixedController;
|
||||
}
|
||||
);
|
||||
|
@ -24,30 +24,34 @@ define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
|
||||
// Drag handle dimensions
|
||||
var DRAG_HANDLE_SIZE = [6, 6];
|
||||
|
||||
/**
|
||||
* Template-displayable drag handle for an element in fixed
|
||||
* position mode.
|
||||
*
|
||||
* @param elementHandle the element handle
|
||||
* @param configPath the configuration path of an element
|
||||
* @param {Object} fixedControl the fixed controller
|
||||
* @memberof platform/features/layout
|
||||
* @constructor
|
||||
*/
|
||||
function FixedDragHandle(elementHandle, gridSize, update, commit) {
|
||||
function FixedDragHandle(elementHandle, configPath, fixedControl) {
|
||||
this.elementHandle = elementHandle;
|
||||
this.gridSize = gridSize;
|
||||
this.update = update;
|
||||
this.commit = commit;
|
||||
this.configPath = configPath;
|
||||
this.fixedControl = fixedControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CSS style to position this drag handle.
|
||||
*
|
||||
* @returns CSS style object (for `ng-style`)
|
||||
* @memberof platform/features/layout.FixedDragHandle#
|
||||
*/
|
||||
FixedDragHandle.prototype.style = function () {
|
||||
var gridSize = this.elementHandle.getGridSize();
|
||||
|
||||
// Adjust from grid to pixel coordinates
|
||||
var x = this.elementHandle.x() * gridSize[0],
|
||||
y = this.elementHandle.y() * gridSize[1];
|
||||
@ -75,23 +79,20 @@ define(
|
||||
|
||||
/**
|
||||
* Continue a drag gesture; update x/y positions.
|
||||
* @param {number[]} delta x/y pixel difference since drag
|
||||
* started
|
||||
*
|
||||
* @param {number[]} delta x/y pixel difference since drag started
|
||||
*/
|
||||
FixedDragHandle.prototype.continueDrag = function (delta) {
|
||||
var gridSize = this.elementHandle.getGridSize();
|
||||
|
||||
if (this.dragging) {
|
||||
// Update x/y positions (snapping to grid)
|
||||
this.elementHandle.x(
|
||||
this.dragging.x + Math.round(delta[0] / gridSize[0])
|
||||
);
|
||||
this.elementHandle.y(
|
||||
this.dragging.y + Math.round(delta[1] / gridSize[1])
|
||||
);
|
||||
// Invoke update callback
|
||||
if (this.update) {
|
||||
this.update();
|
||||
}
|
||||
var newX = this.dragging.x + Math.round(delta[0] / gridSize[0]);
|
||||
var newY = this.dragging.y + Math.round(delta[1] / gridSize[1]);
|
||||
|
||||
this.elementHandle.x(Math.max(0, newX));
|
||||
this.elementHandle.y(Math.max(0, newY));
|
||||
this.fixedControl.updateSelectionStyle();
|
||||
}
|
||||
};
|
||||
|
||||
@ -100,12 +101,8 @@ define(
|
||||
* concludes to trigger commit of changes.
|
||||
*/
|
||||
FixedDragHandle.prototype.endDrag = function () {
|
||||
// Clear cached state
|
||||
this.dragging = undefined;
|
||||
// Mark change as complete
|
||||
if (this.commit) {
|
||||
this.commit("Dragged handle.");
|
||||
}
|
||||
this.fixedControl.mutate(this.configPath, this.elementHandle.element);
|
||||
};
|
||||
|
||||
return FixedDragHandle;
|
||||
|
@ -78,29 +78,30 @@ define(
|
||||
}
|
||||
|
||||
$scope.configuration = $scope.configuration || {};
|
||||
$scope.configuration.panels =
|
||||
$scope.configuration.panels || {};
|
||||
$scope.configuration.panels = $scope.configuration.panels || {};
|
||||
|
||||
self.openmct.objects.get(id).then(function (object) {
|
||||
$scope.configuration.panels[id] = {
|
||||
position: [
|
||||
Math.floor(position.x / self.gridSize[0]),
|
||||
Math.floor(position.y / self.gridSize[1])
|
||||
],
|
||||
dimensions: self.defaultDimensions()
|
||||
dimensions: self.defaultDimensions(),
|
||||
hasFrame: self.getDefaultFrame(object.type)
|
||||
};
|
||||
|
||||
// Store the id so that the newly-dropped object
|
||||
// gets selected during refresh composition
|
||||
self.droppedIdToSelectAfterRefresh = id;
|
||||
|
||||
// Mark change as persistable
|
||||
if ($scope.commit) {
|
||||
$scope.commit("Dropped a frame.");
|
||||
}
|
||||
self.commit();
|
||||
|
||||
// Populate template-facing position for this id
|
||||
self.rawPositions[id] =
|
||||
$scope.configuration.panels[id];
|
||||
self.rawPositions[id] = $scope.configuration.panels[id];
|
||||
self.populatePosition(id);
|
||||
refreshComposition();
|
||||
});
|
||||
|
||||
// Layout may contain embedded views which will
|
||||
// listen for drops, so call preventDefault() so
|
||||
// that they can recognize that this event is handled.
|
||||
@ -157,10 +158,7 @@ define(
|
||||
$scope.configuration.panels[self.activeDragId].dimensions =
|
||||
self.rawPositions[self.activeDragId].dimensions;
|
||||
|
||||
// Mark this object as dirty to encourage persistence
|
||||
if ($scope.commit) {
|
||||
$scope.commit("Moved frame.");
|
||||
}
|
||||
self.commit();
|
||||
};
|
||||
|
||||
// Sets the selectable object in response to the selection change event.
|
||||
@ -194,9 +192,22 @@ define(
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
openmct.selection.off("change", setSelection);
|
||||
self.unlisten();
|
||||
});
|
||||
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
|
||||
self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) {
|
||||
$scope.configuration = model.configuration.layout;
|
||||
$scope.model = model;
|
||||
var panels = $scope.configuration.panels;
|
||||
|
||||
Object.keys(panels).forEach(function (key) {
|
||||
if (self.frames && self.frames.hasOwnProperty(key)) {
|
||||
self.frames[key] = panels[key].hasFrame;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to copy raw positions from configuration,
|
||||
@ -220,7 +231,6 @@ define(
|
||||
*/
|
||||
LayoutController.prototype.setFrames = function (ids) {
|
||||
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
|
||||
|
||||
this.frames = {};
|
||||
|
||||
this.$scope.composition.forEach(function (object) {
|
||||
@ -230,11 +240,22 @@ define(
|
||||
if (panels[id].hasOwnProperty('hasFrame')) {
|
||||
this.frames[id] = panels[id].hasFrame;
|
||||
} else {
|
||||
this.frames[id] = DEFAULT_HIDDEN_FRAME_TYPES.indexOf(object.getModel().type) === -1;
|
||||
this.frames[id] = this.getDefaultFrame(object.getModel().type);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the default value for frame.
|
||||
*
|
||||
* @param type the domain object type
|
||||
* @return {boolean} true if the object should have
|
||||
* frame by default, false, otherwise
|
||||
*/
|
||||
LayoutController.prototype.getDefaultFrame = function (type) {
|
||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||
};
|
||||
|
||||
// Convert from { positions: ..., dimensions: ... } to an
|
||||
// appropriate ng-style argument, to position frames.
|
||||
LayoutController.prototype.convertPosition = function (raw) {
|
||||
@ -389,40 +410,6 @@ define(
|
||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to show/hide the object frame.
|
||||
*
|
||||
* @param {string} id the object id
|
||||
* @private
|
||||
*/
|
||||
LayoutController.prototype.toggleFrame = function (id, domainObject) {
|
||||
var configuration = this.$scope.configuration;
|
||||
|
||||
if (!configuration.panels[id]) {
|
||||
configuration.panels[id] = {};
|
||||
}
|
||||
|
||||
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
|
||||
|
||||
var selection = this.openmct.selection.get();
|
||||
selection[0].context.toolbar = this.getToolbar(id, domainObject);
|
||||
this.openmct.selection.select(selection); // reselect so toolbar updates
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the toolbar object for the given domain object.
|
||||
*
|
||||
* @param id the domain object id
|
||||
* @param domainObject the domain object
|
||||
* @returns {object}
|
||||
* @private
|
||||
*/
|
||||
LayoutController.prototype.getToolbar = function (id, domainObject) {
|
||||
var toolbarObj = {};
|
||||
toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
|
||||
return toolbarObj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bypasses selection if drag is in progress.
|
||||
*
|
||||
@ -497,17 +484,25 @@ define(
|
||||
* Gets the selection context.
|
||||
*
|
||||
* @param domainObject the domain object
|
||||
* @returns {object} the context object which includes
|
||||
* item, oldItem and toolbar
|
||||
* @returns {object} the context object which includes item and oldItem
|
||||
*/
|
||||
LayoutController.prototype.getContext = function (domainObject, toolbar) {
|
||||
LayoutController.prototype.getContext = function (domainObject) {
|
||||
return {
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject,
|
||||
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
|
||||
oldItem: domainObject
|
||||
};
|
||||
};
|
||||
|
||||
LayoutController.prototype.commit = function () {
|
||||
var model = this.$scope.model;
|
||||
model.configuration = model.configuration || {};
|
||||
model.configuration.layout = this.$scope.configuration;
|
||||
|
||||
this.$scope.domainObject.useCapability('mutation', function () {
|
||||
return model;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects a newly-dropped object.
|
||||
*
|
||||
|
@ -53,12 +53,6 @@ define(
|
||||
*/
|
||||
proxy.fill = new AccessorMutator(element, 'fill');
|
||||
|
||||
//Expose x,y, width and height for editing
|
||||
proxy.editWidth = new AccessorMutator(element, 'width');
|
||||
proxy.editHeight = new AccessorMutator(element, 'height');
|
||||
proxy.editX = new AccessorMutator(element, 'x');
|
||||
proxy.editY = new AccessorMutator(element, 'y');
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
@ -71,13 +71,6 @@ define(
|
||||
*/
|
||||
this.gridSize = gridSize || [1,1]; //Ensure a reasonable default
|
||||
|
||||
this.resizeHandles = [new ResizeHandle(
|
||||
this.element,
|
||||
this.getMinWidth(),
|
||||
this.getMinHeight(),
|
||||
this.getGridSize()
|
||||
)];
|
||||
|
||||
/**
|
||||
* Get and/or set the x position of this element.
|
||||
* Units are in fixed position grid space.
|
||||
@ -123,15 +116,16 @@ define(
|
||||
this.height = new AccessorMutator(element, 'height');
|
||||
|
||||
this.useGrid = new UnitAccessorMutator(this);
|
||||
|
||||
this.index = index;
|
||||
this.elements = elements;
|
||||
this.resizeHandles = [new ResizeHandle(this, this.element)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the display order of this element.
|
||||
* @param {string} o where to move this element;
|
||||
* one of "top", "up", "down", or "bottom"
|
||||
* @return {Array} the full array of elements
|
||||
*/
|
||||
ElementProxy.prototype.order = function (o) {
|
||||
var index = this.index,
|
||||
@ -152,16 +146,8 @@ define(
|
||||
// anyway, but be consistent)
|
||||
this.index = desired;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove this element from the fixed position view.
|
||||
*/
|
||||
ElementProxy.prototype.remove = function () {
|
||||
var index = this.index;
|
||||
if (this.elements[index] === this.element) {
|
||||
this.elements.splice(index, 1);
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -208,7 +194,6 @@ define(
|
||||
*/
|
||||
ElementProxy.prototype.getMinWidth = function () {
|
||||
return Math.ceil(MIN_WIDTH / this.getGridSize()[0]);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -50,12 +50,6 @@ define(
|
||||
*/
|
||||
proxy.url = new AccessorMutator(element, 'url');
|
||||
|
||||
//Expose x,y, width and height properties for editing
|
||||
proxy.editWidth = new AccessorMutator(element, 'width');
|
||||
proxy.editHeight = new AccessorMutator(element, 'height');
|
||||
proxy.editX = new AccessorMutator(element, 'x');
|
||||
proxy.editY = new AccessorMutator(element, 'y');
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
@ -32,19 +32,18 @@ define(
|
||||
* @constructor
|
||||
* @param element the line element
|
||||
* @param {string} xProperty field which stores x position
|
||||
* @param {string} yProperty field which stores x position
|
||||
* @param {string} yProperty field which stores y position
|
||||
* @param {string} xOther field which stores x of other end
|
||||
* @param {string} yOther field which stores y of other end
|
||||
* @param {number[]} gridSize the current layout grid size in [x,y] from
|
||||
* @implements {platform/features/layout.ElementHandle}
|
||||
*/
|
||||
function LineHandle(element, xProperty, yProperty, xOther, yOther, gridSize) {
|
||||
function LineHandle(element, elementProxy, xProperty, yProperty, xOther, yOther) {
|
||||
this.elementProxy = elementProxy;
|
||||
this.element = element;
|
||||
this.xProperty = xProperty;
|
||||
this.yProperty = yProperty;
|
||||
this.xOther = xOther;
|
||||
this.yOther = yOther;
|
||||
this.gridSize = gridSize;
|
||||
}
|
||||
|
||||
LineHandle.prototype.x = function (value) {
|
||||
@ -86,7 +85,7 @@ define(
|
||||
};
|
||||
|
||||
LineHandle.prototype.getGridSize = function () {
|
||||
return this.gridSize;
|
||||
return this.elementProxy.getGridSize();
|
||||
};
|
||||
|
||||
return LineHandle;
|
||||
|
@ -39,10 +39,24 @@ define(
|
||||
function LineProxy(element, index, elements, gridSize) {
|
||||
var proxy = new ElementProxy(element, index, elements, gridSize),
|
||||
handles = [
|
||||
new LineHandle(element, 'x', 'y', 'x2', 'y2', proxy.getGridSize()),
|
||||
new LineHandle(element, 'x2', 'y2', 'x', 'y', proxy.getGridSize())
|
||||
new LineHandle(element, proxy, 'x', 'y', 'x2', 'y2'),
|
||||
new LineHandle(element, proxy, 'x2', 'y2', 'x', 'y')
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets style specific to line proxy.
|
||||
*/
|
||||
proxy.getStyle = function () {
|
||||
var layoutGridSize = proxy.getGridSize();
|
||||
|
||||
return {
|
||||
left: (layoutGridSize[0] * proxy.x()) + 'px',
|
||||
top: (layoutGridSize[1] * proxy.y()) + 'px',
|
||||
width: (layoutGridSize[0] * proxy.width()) + 'px',
|
||||
height: (layoutGridSize[1] * proxy.height()) + 'px'
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the top-left x coordinate, in grid space, of
|
||||
* this line's bounding box.
|
||||
@ -149,12 +163,6 @@ define(
|
||||
return handles;
|
||||
};
|
||||
|
||||
// Expose endpoint coordinates for editing
|
||||
proxy.editX1 = new AccessorMutator(element, 'x');
|
||||
proxy.editY1 = new AccessorMutator(element, 'y');
|
||||
proxy.editX2 = new AccessorMutator(element, 'x2');
|
||||
proxy.editY2 = new AccessorMutator(element, 'y2');
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
@ -35,21 +35,16 @@ define(
|
||||
* @memberof platform/features/layout
|
||||
* @constructor
|
||||
*/
|
||||
function ResizeHandle(element, minWidth, minHeight, gridSize) {
|
||||
function ResizeHandle(elementProxy, element) {
|
||||
this.elementProxy = elementProxy;
|
||||
this.element = element;
|
||||
|
||||
// Ensure reasonable defaults
|
||||
this.minWidth = minWidth || 0;
|
||||
this.minHeight = minHeight || 0;
|
||||
|
||||
this.gridSize = gridSize;
|
||||
}
|
||||
|
||||
ResizeHandle.prototype.x = function (value) {
|
||||
var element = this.element;
|
||||
if (arguments.length > 0) {
|
||||
element.width = Math.max(
|
||||
this.minWidth,
|
||||
this.elementProxy.getMinWidth(),
|
||||
value - element.x
|
||||
);
|
||||
}
|
||||
@ -60,7 +55,7 @@ define(
|
||||
var element = this.element;
|
||||
if (arguments.length > 0) {
|
||||
element.height = Math.max(
|
||||
this.minHeight,
|
||||
this.elementProxy.getMinHeight(),
|
||||
value - element.y
|
||||
);
|
||||
}
|
||||
@ -68,7 +63,7 @@ define(
|
||||
};
|
||||
|
||||
ResizeHandle.prototype.getGridSize = function () {
|
||||
return this.gridSize;
|
||||
return this.elementProxy.getGridSize();
|
||||
};
|
||||
|
||||
return ResizeHandle;
|
||||
|
@ -24,9 +24,6 @@ define(
|
||||
['./TextProxy'],
|
||||
function (TextProxy) {
|
||||
|
||||
// Method names to expose from this proxy
|
||||
var HIDE = 'hideTitle', SHOW = 'showTitle';
|
||||
|
||||
/**
|
||||
* Selection proxy for telemetry elements in a fixed position view.
|
||||
*
|
||||
@ -45,24 +42,9 @@ define(
|
||||
function TelemetryProxy(element, index, elements, gridSize) {
|
||||
var proxy = new TextProxy(element, index, elements, gridSize);
|
||||
|
||||
// Toggle the visibility of the title
|
||||
function toggle() {
|
||||
// Toggle the state
|
||||
element.titled = !element.titled;
|
||||
|
||||
// Change which method is exposed, to influence
|
||||
// which button is shown in the toolbar
|
||||
delete proxy[SHOW];
|
||||
delete proxy[HIDE];
|
||||
proxy[element.titled ? HIDE : SHOW] = toggle;
|
||||
}
|
||||
|
||||
// Expose the domain object identifier
|
||||
proxy.id = element.id;
|
||||
|
||||
// Expose initial toggle
|
||||
proxy[element.titled ? HIDE : SHOW] = toggle;
|
||||
|
||||
// Don't expose text configuration
|
||||
delete proxy.text;
|
||||
|
||||
|
@ -53,22 +53,14 @@ define(
|
||||
mockTimeSystem,
|
||||
mockLimitEvaluator,
|
||||
mockSelection,
|
||||
mockObjects,
|
||||
mockNewDomainObject,
|
||||
unlistenFunc,
|
||||
$element = [],
|
||||
selectable = [],
|
||||
controller;
|
||||
|
||||
// Utility function; find a watch for a given expression
|
||||
function findWatch(expr) {
|
||||
var watch;
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
watch = call.args[1];
|
||||
}
|
||||
});
|
||||
return watch;
|
||||
}
|
||||
|
||||
// As above, but for $on calls
|
||||
// Utility function; find a $on calls for a given expression.
|
||||
function findOn(expr) {
|
||||
var on;
|
||||
mockScope.$on.calls.forEach(function (call) {
|
||||
@ -82,7 +74,8 @@ define(
|
||||
function makeMockDomainObject(id) {
|
||||
return {
|
||||
identifier: {
|
||||
key: "domainObject-" + id
|
||||
key: "domainObject-" + id,
|
||||
namespace: ""
|
||||
},
|
||||
name: "Point " + id
|
||||
};
|
||||
@ -110,11 +103,6 @@ define(
|
||||
return "Formatted " + valueMetadata.value;
|
||||
});
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['getId', 'getModel', 'getCapability', 'useCapability']
|
||||
);
|
||||
|
||||
mockHandle = jasmine.createSpyObj(
|
||||
'subscription',
|
||||
[
|
||||
@ -172,16 +160,14 @@ define(
|
||||
]};
|
||||
|
||||
mockChildren = testModel.composition.map(makeMockDomainObject);
|
||||
mockCompositionCollection = jasmine.createSpyObj('compositionCollection',
|
||||
[
|
||||
'load'
|
||||
]
|
||||
);
|
||||
mockCompositionAPI = jasmine.createSpyObj('composition',
|
||||
[
|
||||
mockCompositionCollection = jasmine.createSpyObj('compositionCollection', [
|
||||
'load',
|
||||
'on',
|
||||
'off'
|
||||
]);
|
||||
mockCompositionAPI = jasmine.createSpyObj('composition', [
|
||||
'get'
|
||||
]
|
||||
);
|
||||
]);
|
||||
mockCompositionAPI.get.andReturn(mockCompositionCollection);
|
||||
mockCompositionCollection.load.andReturn(
|
||||
Promise.resolve(mockChildren)
|
||||
@ -190,6 +176,24 @@ define(
|
||||
mockScope.model = testModel;
|
||||
mockScope.configuration = testConfiguration;
|
||||
|
||||
mockNewDomainObject = jasmine.createSpyObj("newDomainObject", [
|
||||
'layoutGrid',
|
||||
'configuration',
|
||||
'composition'
|
||||
]);
|
||||
mockNewDomainObject.layoutGrid = testGrid;
|
||||
mockNewDomainObject.configuration = {
|
||||
'fixed-display': testConfiguration
|
||||
};
|
||||
mockNewDomainObject.composition = ['a', 'b', 'c'];
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['getId', 'getModel', 'getCapability', 'useCapability']
|
||||
);
|
||||
mockDomainObject.useCapability.andReturn(mockNewDomainObject);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
selectable[0] = {
|
||||
context: {
|
||||
oldItem: mockDomainObject
|
||||
@ -203,11 +207,19 @@ define(
|
||||
]);
|
||||
mockSelection.get.andReturn([]);
|
||||
|
||||
unlistenFunc = jasmine.createSpy("unlisten");
|
||||
mockObjects = jasmine.createSpyObj('objects', [
|
||||
'observe',
|
||||
'get'
|
||||
]);
|
||||
mockObjects.observe.andReturn(unlistenFunc);
|
||||
|
||||
mockOpenMCT = {
|
||||
time: mockConductor,
|
||||
telemetry: mockTelemetryAPI,
|
||||
composition: mockCompositionAPI,
|
||||
selection: mockSelection
|
||||
selection: mockSelection,
|
||||
objects: mockObjects
|
||||
};
|
||||
|
||||
$element = $('<div></div>');
|
||||
@ -251,76 +263,60 @@ define(
|
||||
mockOpenMCT,
|
||||
$element
|
||||
);
|
||||
|
||||
findWatch("model.layoutGrid")(testModel.layoutGrid);
|
||||
spyOn(controller, "mutate");
|
||||
});
|
||||
|
||||
it("subscribes when a domain object is available", function () {
|
||||
var dunzo = false;
|
||||
it("subscribes a domain object", function () {
|
||||
var object = makeMockDomainObject("mock");
|
||||
var done = false;
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
controller.getTelemetry(object).then(function () {
|
||||
done = true;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
return done;
|
||||
});
|
||||
|
||||
runs(function () {
|
||||
mockChildren.forEach(function (child) {
|
||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(
|
||||
child,
|
||||
object,
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("releases subscriptions when domain objects change", function () {
|
||||
var dunzo = false;
|
||||
it("releases subscription when a domain objects is removed", function () {
|
||||
var done = false;
|
||||
var unsubscribe = jasmine.createSpy('unsubscribe');
|
||||
var object = makeMockDomainObject("mock");
|
||||
|
||||
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
controller.getTelemetry(object).then(function () {
|
||||
done = true;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
return done;
|
||||
});
|
||||
|
||||
runs(function () {
|
||||
expect(unsubscribe).not.toHaveBeenCalled();
|
||||
|
||||
dunzo = false;
|
||||
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
});
|
||||
controller.onCompositionRemove(object.identifier);
|
||||
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
|
||||
runs(function () {
|
||||
expect(unsubscribe.calls.length).toBe(mockChildren.length);
|
||||
return unsubscribe.calls.length > 0;
|
||||
});
|
||||
|
||||
runs(function () {
|
||||
expect(unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes visible elements based on configuration", function () {
|
||||
var elements;
|
||||
var elements = controller.getElements();
|
||||
|
||||
mockScope.model = testModel;
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
elements = controller.getElements();
|
||||
expect(elements.length).toEqual(3);
|
||||
expect(elements[0].id).toEqual('a');
|
||||
expect(elements[1].id).toEqual('b');
|
||||
@ -328,9 +324,6 @@ define(
|
||||
});
|
||||
|
||||
it("allows elements to be selected", function () {
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
@ -338,12 +331,7 @@ define(
|
||||
});
|
||||
|
||||
it("allows selection retrieval", function () {
|
||||
var elements;
|
||||
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
elements = controller.getElements();
|
||||
var elements = controller.getElements();
|
||||
selectable[0].context.elementProxy = elements[1];
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
@ -351,16 +339,10 @@ define(
|
||||
});
|
||||
|
||||
it("selects the parent view when selected element is removed", function () {
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
var elements = controller.getElements();
|
||||
selectable[0].context.elementProxy = elements[1];
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
elements[1].remove();
|
||||
testModel.modified = 2;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
controller.remove(elements[1]);
|
||||
|
||||
expect($element[0].click).toHaveBeenCalled();
|
||||
});
|
||||
@ -368,21 +350,13 @@ define(
|
||||
it("retains selections during refresh", function () {
|
||||
// Get elements; remove one of them; trigger refresh.
|
||||
// Same element (at least by index) should still be selected.
|
||||
var elements;
|
||||
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
elements = controller.getElements();
|
||||
var elements = controller.getElements();
|
||||
selectable[0].context.elementProxy = elements[1];
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
expect(controller.getSelectedElement()).toEqual(elements[1]);
|
||||
|
||||
elements[2].remove();
|
||||
testModel.modified = 2;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
controller.remove(elements[2]);
|
||||
elements = controller.getElements();
|
||||
|
||||
// Verify removal, as test assumes this
|
||||
@ -408,7 +382,7 @@ define(
|
||||
controller.elementProxiesById['12345'] = [testElement];
|
||||
controller.elementProxies = [testElement];
|
||||
|
||||
controller.subscribeToObjects([telemetryObject]);
|
||||
controller.subscribeToObject(telemetryObject);
|
||||
mockTelemetryAPI.subscribe.mostRecentCall.args[1](mockTelemetry);
|
||||
|
||||
waitsFor(function () {
|
||||
@ -426,18 +400,13 @@ define(
|
||||
});
|
||||
|
||||
it("updates elements styles when grid size changes", function () {
|
||||
var originalLeft;
|
||||
// Grid size is initially set to testGrid which is [123, 456]
|
||||
var originalLeft = controller.getElements()[0].style.left;
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.model = testModel;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
findWatch("model.modified")(1);
|
||||
findWatch("model.composition")(mockScope.model.composition);
|
||||
findWatch("model.layoutGrid")([10, 10]);
|
||||
originalLeft = controller.getElements()[0].style.left;
|
||||
findWatch("model.layoutGrid")([20, 20]);
|
||||
expect(controller.getElements()[0].style.left)
|
||||
.not.toEqual(originalLeft);
|
||||
// Change the grid size
|
||||
controller.updateElementPositions([20, 20]);
|
||||
|
||||
expect(controller.getElements()[0].style.left).not.toEqual(originalLeft);
|
||||
});
|
||||
|
||||
it("listens for drop events", function () {
|
||||
@ -457,6 +426,9 @@ define(
|
||||
|
||||
// Notify that a drop occurred
|
||||
testModel.composition.push('d');
|
||||
|
||||
mockObjects.get.andReturn(Promise.resolve([]));
|
||||
|
||||
findOn('mctDrop')(
|
||||
mockEvent,
|
||||
'd',
|
||||
@ -468,11 +440,6 @@ define(
|
||||
|
||||
// ...and prevented default...
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
|
||||
// Should have triggered commit (provided by
|
||||
// EditRepresenter) with some message.
|
||||
expect(mockScope.commit)
|
||||
.toHaveBeenCalledWith(jasmine.any(String));
|
||||
});
|
||||
|
||||
it("ignores drops when default has been prevented", function () {
|
||||
@ -492,52 +459,35 @@ define(
|
||||
});
|
||||
|
||||
it("unsubscribes when destroyed", function () {
|
||||
|
||||
var dunzo = false;
|
||||
var done = false;
|
||||
var unsubscribe = jasmine.createSpy('unsubscribe');
|
||||
var object = makeMockDomainObject("mock");
|
||||
|
||||
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
controller.getTelemetry(object).then(function () {
|
||||
done = true;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
return done;
|
||||
});
|
||||
|
||||
runs(function () {
|
||||
expect(unsubscribe).not.toHaveBeenCalled();
|
||||
// Destroy the scope
|
||||
findOn('$destroy')();
|
||||
|
||||
//Check that the same unsubscribe function returned by the
|
||||
expect(unsubscribe.calls.length).toBe(mockChildren.length);
|
||||
expect(unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes its grid size", function () {
|
||||
findWatch('model.layoutGrid')(testGrid);
|
||||
// Template needs to be able to pass this into line
|
||||
// elements to size SVGs appropriately
|
||||
expect(controller.getGridSize()).toEqual(testGrid);
|
||||
});
|
||||
|
||||
it("exposes a view-level selection proxy", function () {
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
|
||||
|
||||
expect(mockOpenMCT.selection.select).toHaveBeenCalled();
|
||||
expect(selection.context.viewProxy).toBeDefined();
|
||||
});
|
||||
|
||||
it("exposes drag handles", function () {
|
||||
var handles;
|
||||
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
@ -556,9 +506,6 @@ define(
|
||||
});
|
||||
|
||||
it("exposes a move handle", function () {
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
@ -573,10 +520,6 @@ define(
|
||||
|
||||
it("updates selection style during drag", function () {
|
||||
var oldStyle;
|
||||
|
||||
testModel.modified = 1;
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
|
||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
@ -677,7 +620,7 @@ define(
|
||||
value: testValue
|
||||
}]));
|
||||
|
||||
controller.fetchHistoricalData([mockTelemetryObject]);
|
||||
controller.fetchHistoricalData(mockTelemetryObject);
|
||||
|
||||
waitsFor(function () {
|
||||
return controller.digesting === false;
|
||||
|
@ -28,8 +28,8 @@ define(
|
||||
|
||||
describe("A fixed position drag handle", function () {
|
||||
var mockElementHandle,
|
||||
mockUpdate,
|
||||
mockCommit,
|
||||
mockConfigPath,
|
||||
mockFixedControl,
|
||||
handle;
|
||||
|
||||
beforeEach(function () {
|
||||
@ -37,18 +37,23 @@ define(
|
||||
'elementHandle',
|
||||
['x', 'y','getGridSize']
|
||||
);
|
||||
mockUpdate = jasmine.createSpy('update');
|
||||
mockCommit = jasmine.createSpy('commit');
|
||||
|
||||
mockElementHandle.x.andReturn(6);
|
||||
mockElementHandle.y.andReturn(8);
|
||||
mockElementHandle.getGridSize.andReturn(TEST_GRID_SIZE);
|
||||
|
||||
mockFixedControl = jasmine.createSpyObj(
|
||||
'fixedControl',
|
||||
['updateSelectionStyle', 'mutate']
|
||||
);
|
||||
mockFixedControl.updateSelectionStyle.andReturn();
|
||||
mockFixedControl.mutate.andReturn();
|
||||
|
||||
mockConfigPath = jasmine.createSpy('configPath');
|
||||
|
||||
handle = new FixedDragHandle(
|
||||
mockElementHandle,
|
||||
TEST_GRID_SIZE,
|
||||
mockUpdate,
|
||||
mockCommit
|
||||
mockConfigPath,
|
||||
mockFixedControl
|
||||
);
|
||||
});
|
||||
|
||||
@ -74,13 +79,12 @@ define(
|
||||
expect(mockElementHandle.x).toHaveBeenCalledWith(5);
|
||||
expect(mockElementHandle.y).toHaveBeenCalledWith(7);
|
||||
|
||||
// Should have called update once per continueDrag
|
||||
expect(mockUpdate.calls.length).toEqual(2);
|
||||
// Should have called updateSelectionStyle once per continueDrag
|
||||
expect(mockFixedControl.updateSelectionStyle.calls.length).toEqual(2);
|
||||
|
||||
// Finally, ending drag should commit
|
||||
expect(mockCommit).not.toHaveBeenCalled();
|
||||
// Finally, ending drag should mutate
|
||||
handle.endDrag();
|
||||
expect(mockCommit).toHaveBeenCalled();
|
||||
expect(mockFixedControl.mutate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -42,6 +42,8 @@ define(
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
mockDomainObjectCapability,
|
||||
mockObjects,
|
||||
unlistenFunc,
|
||||
$element = [],
|
||||
selectable = [];
|
||||
|
||||
@ -77,14 +79,15 @@ define(
|
||||
if (param === 'composition') {
|
||||
return id !== 'b';
|
||||
}
|
||||
}
|
||||
},
|
||||
type: "testType"
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$watch", "$watchCollection", "$on", "commit"]
|
||||
["$watch", "$watchCollection", "$on"]
|
||||
);
|
||||
mockEvent = jasmine.createSpyObj(
|
||||
'event',
|
||||
@ -104,9 +107,13 @@ define(
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unlistenFunc = jasmine.createSpy("unlisten");
|
||||
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
||||
['inEditContext']
|
||||
['inEditContext', 'listen']
|
||||
);
|
||||
mockDomainObjectCapability.listen.andReturn(unlistenFunc);
|
||||
|
||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||
|
||||
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
||||
@ -126,8 +133,14 @@ define(
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn(selectable);
|
||||
|
||||
mockObjects = jasmine.createSpyObj('objects', [
|
||||
'get'
|
||||
]);
|
||||
mockObjects.get.andReturn(mockPromise(mockDomainObject("mockObject")));
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
selection: mockSelection,
|
||||
objects: mockObjects
|
||||
};
|
||||
|
||||
$element = $('<div></div>');
|
||||
@ -138,6 +151,7 @@ define(
|
||||
|
||||
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
||||
spyOn(controller, "layoutPanels").andCallThrough();
|
||||
spyOn(controller, "commit");
|
||||
|
||||
jasmine.Clock.useMock();
|
||||
});
|
||||
@ -270,10 +284,7 @@ define(
|
||||
controller.continueDrag([100, 100]);
|
||||
controller.endDrag();
|
||||
|
||||
// Should have triggered commit (provided by
|
||||
// EditRepresenter) with some message.
|
||||
expect(mockScope.commit)
|
||||
.toHaveBeenCalledWith(jasmine.any(String));
|
||||
expect(controller.commit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("listens for drop events", function () {
|
||||
@ -296,11 +307,7 @@ define(
|
||||
);
|
||||
expect(testConfiguration.panels.d).toBeDefined();
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
|
||||
// Should have triggered commit (provided by
|
||||
// EditRepresenter) with some message.
|
||||
expect(mockScope.commit)
|
||||
.toHaveBeenCalledWith(jasmine.any(String));
|
||||
expect(controller.commit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores drops when default has been prevented", function () {
|
||||
@ -340,13 +347,17 @@ define(
|
||||
testModel.layoutGrid = [1, 1];
|
||||
mockScope.$watch.calls[0].args[1](testModel.layoutGrid);
|
||||
|
||||
// Add a new object to the composition
|
||||
mockComposition = ["a", "b", "c", "d"];
|
||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||
|
||||
// Notify that a drop occurred
|
||||
mockScope.$on.mostRecentCall.args[1](
|
||||
mockEvent,
|
||||
'd',
|
||||
{ x: 300, y: 100 }
|
||||
);
|
||||
mockScope.$watch.calls[0].args[1](['d']);
|
||||
|
||||
style = controller.getFrameStyle("d");
|
||||
|
||||
@ -415,30 +426,6 @@ define(
|
||||
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
|
||||
});
|
||||
|
||||
it("hides frame when selected object has frame ", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
|
||||
|
||||
expect(controller.hasFrame(childObj)).toBe(true);
|
||||
expect(toolbarObj.hideFrame).toBeDefined();
|
||||
expect(toolbarObj.hideFrame).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("shows frame when selected object has no frame", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[1];
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
|
||||
|
||||
expect(controller.hasFrame(childObj)).toBe(false);
|
||||
expect(toolbarObj.showFrame).toBeDefined();
|
||||
expect(toolbarObj.showFrame).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("selects the parent object when selected object is removed", function () {
|
||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
|
@ -53,11 +53,6 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("allows elements to be removed", function () {
|
||||
proxy.remove();
|
||||
expect(testElements).toEqual([{}, {}, {}]);
|
||||
});
|
||||
|
||||
it("allows order to be changed", function () {
|
||||
proxy.order("down");
|
||||
expect(testElements).toEqual([{}, testElement, {}, {}]);
|
||||
|
@ -26,7 +26,9 @@ define(
|
||||
|
||||
describe("A fixed position drag handle", function () {
|
||||
var testElement,
|
||||
handle;
|
||||
mockElementProxy,
|
||||
handle,
|
||||
TEST_GRID_SIZE = [45, 21];
|
||||
|
||||
beforeEach(function () {
|
||||
testElement = {
|
||||
@ -36,8 +38,10 @@ define(
|
||||
y2: 11,
|
||||
useGrid: true
|
||||
};
|
||||
mockElementProxy = jasmine.createSpyObj('elementProxy', ['getGridSize']);
|
||||
mockElementProxy.getGridSize.andReturn(TEST_GRID_SIZE);
|
||||
|
||||
handle = new LineHandle(testElement, 'x', 'y', 'x2', 'y2', [45,21]);
|
||||
handle = new LineHandle(testElement, mockElementProxy, 'x', 'y', 'x2', 'y2');
|
||||
});
|
||||
|
||||
it("provides x/y grid coordinates for its corner", function () {
|
||||
@ -69,7 +73,7 @@ define(
|
||||
});
|
||||
|
||||
it("returns the correct grid size", function () {
|
||||
expect(handle.getGridSize()).toEqual([45,21]);
|
||||
expect(handle.getGridSize()).toEqual(TEST_GRID_SIZE);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -63,13 +63,13 @@ define(
|
||||
it("adjusts both ends when mutating x", function () {
|
||||
var proxy = new LineProxy(diagonal);
|
||||
proxy.x(6);
|
||||
expect(diagonal).toEqual({ x: 6, y: 8, x2: 8, y2: 11, useGrid: true });
|
||||
expect(diagonal).toEqual({ x: 6, y: 8, x2: 8, y2: 11});
|
||||
});
|
||||
|
||||
it("adjusts both ends when mutating y", function () {
|
||||
var proxy = new LineProxy(diagonal);
|
||||
proxy.y(6);
|
||||
expect(diagonal).toEqual({ x: 3, y: 6, x2: 5, y2: 9, useGrid: true });
|
||||
expect(diagonal).toEqual({ x: 3, y: 6, x2: 5, y2: 9});
|
||||
});
|
||||
|
||||
it("provides internal positions for SVG lines", function () {
|
||||
|
@ -25,10 +25,12 @@ define(
|
||||
function (ResizeHandle) {
|
||||
|
||||
var TEST_MIN_WIDTH = 4,
|
||||
TEST_MIN_HEIGHT = 2;
|
||||
TEST_MIN_HEIGHT = 2,
|
||||
TEST_GRID_SIZE = [34, 81];
|
||||
|
||||
describe("A fixed position drag handle", function () {
|
||||
var testElement,
|
||||
mockElementProxy,
|
||||
handle;
|
||||
|
||||
beforeEach(function () {
|
||||
@ -39,12 +41,18 @@ define(
|
||||
height: 36,
|
||||
useGrid: true
|
||||
};
|
||||
mockElementProxy = jasmine.createSpyObj('elementProxy', [
|
||||
'getGridSize',
|
||||
'getMinWidth',
|
||||
'getMinHeight'
|
||||
]);
|
||||
mockElementProxy.getGridSize.andReturn(TEST_GRID_SIZE);
|
||||
mockElementProxy.getMinWidth.andReturn(TEST_MIN_WIDTH);
|
||||
mockElementProxy.getMinHeight.andReturn(TEST_MIN_HEIGHT);
|
||||
|
||||
handle = new ResizeHandle(
|
||||
testElement,
|
||||
TEST_MIN_WIDTH,
|
||||
TEST_MIN_HEIGHT,
|
||||
[34,81]
|
||||
mockElementProxy,
|
||||
testElement
|
||||
);
|
||||
});
|
||||
|
||||
@ -77,7 +85,7 @@ define(
|
||||
});
|
||||
|
||||
it("returns the correct grid size", function () {
|
||||
expect(handle.getGridSize()).toEqual([34,81]);
|
||||
expect(handle.getGridSize()).toEqual(TEST_GRID_SIZE);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -49,27 +49,6 @@ define(
|
||||
it("exposes the element's id", function () {
|
||||
expect(proxy.id).toEqual('test-id');
|
||||
});
|
||||
|
||||
it("allows title to be shown/hidden", function () {
|
||||
// Initially, only showTitle and hideTitle are available
|
||||
expect(proxy.hideTitle).toBeUndefined();
|
||||
proxy.showTitle();
|
||||
|
||||
// Should have set titled state
|
||||
expect(testElement.titled).toBeTruthy();
|
||||
|
||||
// Should also have changed methods available
|
||||
expect(proxy.showTitle).toBeUndefined();
|
||||
proxy.hideTitle();
|
||||
|
||||
// Should have cleared titled state
|
||||
expect(testElement.titled).toBeFalsy();
|
||||
|
||||
// Available methods should have changed again
|
||||
expect(proxy.hideTitle).toBeUndefined();
|
||||
proxy.showTitle();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -21,13 +21,12 @@
|
||||
-->
|
||||
<form novalidate>
|
||||
<div class="tool-bar btn-bar contents abs">
|
||||
<span ng-repeat="section in structure.sections"
|
||||
class="l-control-group"
|
||||
ng-if="!section.hidden"
|
||||
title="{{section.description}}">
|
||||
<ng-form ng-repeat="item in section.items"
|
||||
ng-class="{ 'input-labeled': item.name }"
|
||||
<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">
|
||||
|
@ -24,8 +24,16 @@
|
||||
* Module defining MCTForm. Created by vwoeltje on 11/10/14.
|
||||
*/
|
||||
define(
|
||||
["./MCTForm", "text!../res/templates/toolbar.html"],
|
||||
function (MCTForm, toolbarTemplate) {
|
||||
[
|
||||
"./MCTForm",
|
||||
"text!../res/templates/toolbar.html",
|
||||
"./controllers/ToolbarController"
|
||||
],
|
||||
function (
|
||||
MCTForm,
|
||||
toolbarTemplate,
|
||||
ToolbarController
|
||||
) {
|
||||
|
||||
/**
|
||||
* The mct-toolbar directive allows generation of displayable
|
||||
@ -35,7 +43,7 @@ define(
|
||||
* This directive accepts three attributes:
|
||||
*
|
||||
* * `ng-model`: The model for the form; where user input
|
||||
* where be stored.
|
||||
* 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.
|
||||
@ -49,9 +57,10 @@ define(
|
||||
*/
|
||||
function MCTToolbar() {
|
||||
// Use Directive Definition Object from mct-form,
|
||||
// but use the toolbar's template instead.
|
||||
// but use the toolbar's template and controller instead.
|
||||
var ddo = new MCTForm();
|
||||
ddo.template = toolbarTemplate;
|
||||
ddo.controller = ['$scope', 'openmct', ToolbarController];
|
||||
return ddo;
|
||||
}
|
||||
|
||||
|
84
platform/forms/src/controllers/ToolbarController.js
Normal file
84
platform/forms/src/controllers/ToolbarController.js
Normal file
@ -0,0 +1,84 @@
|
||||
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;
|
||||
}
|
||||
);
|
@ -26,16 +26,28 @@ define(
|
||||
|
||||
describe("The mct-toolbar directive", function () {
|
||||
var mockScope,
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
mctToolbar;
|
||||
|
||||
function installController() {
|
||||
var Controller = mctToolbar.controller[1];
|
||||
return new Controller(mockScope);
|
||||
var Controller = mctToolbar.controller[2];
|
||||
return new Controller(mockScope, mockOpenMCT);
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
|
||||
mockScope = jasmine.createSpyObj("$scope", [
|
||||
"$watch",
|
||||
"$on"
|
||||
]);
|
||||
mockScope.$parent = {};
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'on',
|
||||
'off'
|
||||
]);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
mctToolbar = new MCTToolbar();
|
||||
});
|
||||
|
||||
@ -43,29 +55,15 @@ define(
|
||||
expect(mctToolbar.restrict).toEqual("E");
|
||||
});
|
||||
|
||||
it("watches for changes in form by name", function () {
|
||||
// mct-form needs to watch for the form by name
|
||||
// in order to convey changes in $valid, $dirty, etc
|
||||
// up to the parent scope.
|
||||
it("listens for selection change event", function () {
|
||||
installController();
|
||||
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"mctForm",
|
||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
||||
"change",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("conveys form status to parent scope", function () {
|
||||
var someState = { someKey: "some value" };
|
||||
mockScope.name = "someName";
|
||||
|
||||
installController();
|
||||
|
||||
mockScope.$watch.mostRecentCall.args[1](someState);
|
||||
|
||||
expect(mockScope.$parent.someName).toBe(someState);
|
||||
});
|
||||
|
||||
it("allows strings to be converted to RegExps", function () {
|
||||
// This is needed to support ng-pattern in the template
|
||||
installController();
|
||||
|
18
src/MCT.js
18
src/MCT.js
@ -29,7 +29,8 @@ define([
|
||||
'./api/objects/object-utils',
|
||||
'./plugins/plugins',
|
||||
'./ui/ViewRegistry',
|
||||
'./ui/InspectorViewRegistry'
|
||||
'./ui/InspectorViewRegistry',
|
||||
'./ui/ToolbarRegistry'
|
||||
], function (
|
||||
EventEmitter,
|
||||
legacyRegistry,
|
||||
@ -39,7 +40,8 @@ define([
|
||||
objectUtils,
|
||||
plugins,
|
||||
ViewRegistry,
|
||||
InspectorViewRegistry
|
||||
InspectorViewRegistry,
|
||||
ToolbarRegistry
|
||||
) {
|
||||
/**
|
||||
* Open MCT is an extensible web application for building mission
|
||||
@ -76,7 +78,7 @@ define([
|
||||
* Tracks current selection state of the application.
|
||||
* @private
|
||||
*/
|
||||
this.selection = new Selection();
|
||||
this.selection = new Selection(this);
|
||||
|
||||
/**
|
||||
* MCT's time conductor, which may be used to synchronize view contents
|
||||
@ -143,17 +145,13 @@ define([
|
||||
|
||||
/**
|
||||
* Registry for views which should appear in the toolbar area while
|
||||
* editing.
|
||||
* editing. These views will be chosen based on the selection state.
|
||||
*
|
||||
* These views will be chosen based on selection state, so
|
||||
* providers should be prepared to test arbitrary objects for
|
||||
* viewability.
|
||||
*
|
||||
* @type {module:openmct.ViewRegistry}
|
||||
* @type {module:openmct.ToolbarRegistry}
|
||||
* @memberof module:openmct.MCT#
|
||||
* @name toolbars
|
||||
*/
|
||||
this.toolbars = new ViewRegistry();
|
||||
this.toolbars = new ToolbarRegistry();
|
||||
|
||||
/**
|
||||
* Registry for domain object types which may exist within this
|
||||
|
@ -76,12 +76,20 @@ define([
|
||||
* @memberof module:openmct.MutableObject#
|
||||
*/
|
||||
MutableObject.prototype.set = function (path, value) {
|
||||
|
||||
_.set(this.object, path, value);
|
||||
_.set(this.object, 'modified', Date.now());
|
||||
|
||||
var handleRecursiveMutation = function (newObject) {
|
||||
this.object = newObject;
|
||||
}.bind(this);
|
||||
|
||||
this.eventEmitter.on(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
|
||||
|
||||
//Emit event specific to property
|
||||
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
|
||||
|
||||
this.eventEmitter.off(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
|
||||
|
||||
//Emit wildcare event
|
||||
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
||||
|
||||
|
@ -26,8 +26,10 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
* Manages selection state for Open MCT
|
||||
* @private
|
||||
*/
|
||||
function Selection() {
|
||||
function Selection(openmct) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.openmct = openmct;
|
||||
this.selected = [];
|
||||
}
|
||||
|
||||
@ -99,7 +101,12 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
* Attaches the click handlers to the element.
|
||||
*
|
||||
* @param element an html element
|
||||
* @param context object with oldItem, item and toolbar properties
|
||||
* @param context object which defines item or other arbitrary properties.
|
||||
* e.g. {
|
||||
* item: domainObject,
|
||||
* elementProxy: element,
|
||||
* controller: fixedController
|
||||
* }
|
||||
* @param select a flag to select the element if true
|
||||
* @returns a function that removes the click handlers from the element
|
||||
* @public
|
||||
@ -114,6 +121,12 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
element.addEventListener('click', capture, true);
|
||||
element.addEventListener('click', selectCapture);
|
||||
|
||||
if (context.item) {
|
||||
var unlisten = this.openmct.objects.observe(context.item, "*", function (newItem) {
|
||||
context.item = newItem;
|
||||
});
|
||||
}
|
||||
|
||||
if (select) {
|
||||
element.click();
|
||||
}
|
||||
@ -121,6 +134,10 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
return function () {
|
||||
element.removeEventListener('click', capture);
|
||||
element.removeEventListener('click', selectCapture);
|
||||
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
125
src/ui/ToolbarRegistry.js
Normal file
125
src/ui/ToolbarRegistry.js
Normal file
@ -0,0 +1,125 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
/*global console */
|
||||
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* A ToolbarRegistry maintains the definitions for toolbars.
|
||||
*
|
||||
* @interface ToolbarRegistry
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
function ToolbarRegistry() {
|
||||
this.providers = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets toolbar controls from providers which can provide a toolbar for this selection.
|
||||
*
|
||||
* @param {object} selection the selection object
|
||||
* @returns {Object[]} an array of objects defining controls for the toolbar
|
||||
* @private for platform-internal use
|
||||
*/
|
||||
ToolbarRegistry.prototype.get = function (selection) {
|
||||
var providers = this.getAllProviders().filter(function (provider) {
|
||||
return provider.forSelection(selection);
|
||||
});
|
||||
|
||||
var structure = [];
|
||||
|
||||
providers.map(function (provider) {
|
||||
provider.toolbar(selection).forEach(function (item) {
|
||||
structure.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
return structure;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ToolbarRegistry.prototype.getAllProviders = function () {
|
||||
return Object.values(this.providers);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ToolbarRegistry.prototype.getByProviderKey = function (key) {
|
||||
return this.providers[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new type of toolbar.
|
||||
*
|
||||
* @param {module:openmct.ToolbarRegistry} provider the provider for this toolbar
|
||||
* @method addProvider
|
||||
* @memberof module:openmct.ToolbarRegistry#
|
||||
*/
|
||||
ToolbarRegistry.prototype.addProvider = function (provider) {
|
||||
var key = provider.key;
|
||||
|
||||
if (key === undefined) {
|
||||
throw "Toolbar 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exposes types of toolbars in Open MCT.
|
||||
*
|
||||
* @interface ToolbarProvider
|
||||
* @property {string} key a unique identifier for this toolbar
|
||||
* @property {string} name the human-readable name of this toolbar
|
||||
* @property {string} [description] a longer-form description (typically
|
||||
* a single sentence or short paragraph) of this kind of toolbar
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if this provider can supply toolbar for a selection.
|
||||
*
|
||||
* @method forSelection
|
||||
* @memberof module:openmct.ToolbarProvider#
|
||||
* @param {module:openmct.selection} selection
|
||||
* @returns {boolean} 'true' if the toolbar applies to the provided selection,
|
||||
* otherwise 'false'.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides controls that comprise a toolbar.
|
||||
*
|
||||
* @method toolbar
|
||||
* @memberof module:openmct.ToolbarProvider#
|
||||
* @param {object} selection the selection object
|
||||
* @returns {Object[]} an array of objects defining controls for the toolbar.
|
||||
*/
|
||||
|
||||
return ToolbarRegistry;
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user