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/EditableMovePolicy",
|
||||||
"./src/policies/EditContextualActionPolicy",
|
"./src/policies/EditContextualActionPolicy",
|
||||||
"./src/representers/EditRepresenter",
|
"./src/representers/EditRepresenter",
|
||||||
"./src/representers/EditToolbarRepresenter",
|
|
||||||
"./src/capabilities/EditorCapability",
|
"./src/capabilities/EditorCapability",
|
||||||
"./src/capabilities/TransactionCapabilityDecorator",
|
"./src/capabilities/TransactionCapabilityDecorator",
|
||||||
"./src/services/TransactionManager",
|
"./src/services/TransactionManager",
|
||||||
@ -78,7 +77,6 @@ define([
|
|||||||
EditableMovePolicy,
|
EditableMovePolicy,
|
||||||
EditContextualActionPolicy,
|
EditContextualActionPolicy,
|
||||||
EditRepresenter,
|
EditRepresenter,
|
||||||
EditToolbarRepresenter,
|
|
||||||
EditorCapability,
|
EditorCapability,
|
||||||
TransactionCapabilityDecorator,
|
TransactionCapabilityDecorator,
|
||||||
TransactionManager,
|
TransactionManager,
|
||||||
@ -381,12 +379,6 @@ define([
|
|||||||
"depends": [
|
"depends": [
|
||||||
"$log"
|
"$log"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"implementation": EditToolbarRepresenter,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
@ -424,6 +416,17 @@ define([
|
|||||||
"transactionService"
|
"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">
|
<div class="items-select left flex-elem l-flex-row grows">
|
||||||
<mct-representation key="'back-arrow'"
|
<mct-representation key="'back-arrow'"
|
||||||
mct-object="domainObject"
|
mct-object="domainObject"
|
||||||
class="flex-elem l-back"></mct-representation>
|
class="flex-elem l-back">
|
||||||
|
</mct-representation>
|
||||||
<mct-representation key="'object-header'"
|
<mct-representation key="'object-header'"
|
||||||
mct-object="domainObject"
|
mct-object="domainObject"
|
||||||
class="l-flex-row flex-elem grows object-header">
|
class="l-flex-row flex-elem grows object-header">
|
||||||
@ -48,8 +49,8 @@
|
|||||||
<!-- Toolbar and Save/Cancel buttons -->
|
<!-- Toolbar and Save/Cancel buttons -->
|
||||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||||
<mct-toolbar name="mctToolbar"
|
<mct-toolbar name="mctToolbar"
|
||||||
structure="toolbar.structure"
|
structure="editToolbar.structure"
|
||||||
ng-model="toolbar.state"
|
ng-model="editToolbar.state"
|
||||||
class="flex-elem grows">
|
class="flex-elem grows">
|
||||||
</mct-toolbar>
|
</mct-toolbar>
|
||||||
<mct-representation key="'edit-action-buttons'"
|
<mct-representation key="'edit-action-buttons'"
|
||||||
@ -61,7 +62,6 @@
|
|||||||
<mct-representation key="representation.selected.key"
|
<mct-representation key="representation.selected.key"
|
||||||
mct-object="representation.selected.key && domainObject"
|
mct-object="representation.selected.key && domainObject"
|
||||||
class="abs flex-elem grows object-holder-main scroll"
|
class="abs flex-elem grows object-holder-main scroll"
|
||||||
toolbar="toolbar"
|
|
||||||
mct-selectable="{
|
mct-selectable="{
|
||||||
item: domainObject.useCapability('adapter'),
|
item: domainObject.useCapability('adapter'),
|
||||||
oldItem: domainObject
|
oldItem: domainObject
|
||||||
|
@ -20,192 +20,110 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
define(
|
define(
|
||||||
[],
|
[
|
||||||
function () {
|
'../../../../../src/api/objects/object-utils',
|
||||||
|
'lodash'
|
||||||
// Utility functions for reducing truth arrays
|
],
|
||||||
function and(a, b) {
|
function (
|
||||||
return a && b;
|
objectUtils,
|
||||||
}
|
_
|
||||||
function or(a, b) {
|
) {
|
||||||
return a || b;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides initial structure and state (as suitable for provision
|
* Provides initial structure and state (as suitable for provision
|
||||||
* to the `mct-toolbar` directive) for a view's tool bar, based on
|
* to the `mct-toolbar` directive) for a view's toolbar, based on
|
||||||
* that view's declaration of what belongs in its tool bar and on
|
* that view's declaration of what belongs in its toolbar and on
|
||||||
* the current selection.
|
* the current selection.
|
||||||
*
|
*
|
||||||
* @param structure toolbar structure, as provided by view definition
|
* @param $scope the Angular scope
|
||||||
* @param {Function} commit callback to invoke after changes
|
* @param {Object} openmct the openmct object
|
||||||
|
* @param structure the toolbar structure
|
||||||
* @memberof platform/commonUI/edit
|
* @memberof platform/commonUI/edit
|
||||||
* @constructor
|
* @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;
|
var self = this;
|
||||||
|
|
||||||
// Generate a new key for an item's property
|
function addKey(item) {
|
||||||
function addKey(property) {
|
self.stateTracker.push({
|
||||||
self.properties.push(property);
|
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
|
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) {
|
function convertItem(item) {
|
||||||
var converted = Object.create(item || {});
|
var converted = Object.create(item || {});
|
||||||
|
|
||||||
if (item.property) {
|
if (item.property) {
|
||||||
converted.key = addKey(item.property);
|
converted.key = addKey(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.method) {
|
if (item.method) {
|
||||||
converted.click = function (v) {
|
converted.click = function (value) {
|
||||||
invoke(item.method, v);
|
item.method(value);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return converted;
|
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
|
// Get initial value for a given property
|
||||||
function initializeState(property) {
|
function initializeState(property) {
|
||||||
var result;
|
var result;
|
||||||
// Look through all selections for this property;
|
structure.forEach(function (item) {
|
||||||
// values should all match by the time we perform
|
if (item.property === property) {
|
||||||
// this lookup anyway.
|
result = _.get(item.domainObject, item.property);
|
||||||
self.selection.forEach(function (selected) {
|
}
|
||||||
result = (selected[property] !== undefined) ?
|
|
||||||
self.lookupState(property, selected) :
|
|
||||||
result;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selection = s;
|
// Tracks the domain object and property for every element in the state array
|
||||||
this.toolbarStructure.sections.forEach(refreshSectionApplicability);
|
this.stateTracker = [];
|
||||||
|
this.toolbarStructure = structure.map(convertItem);
|
||||||
this.toolbarState = this.properties.map(initializeState);
|
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`.
|
* pass to `mct-toolbar`.
|
||||||
* @returns the toolbar structure
|
*
|
||||||
|
* @returns {Array} the toolbar structure
|
||||||
*/
|
*/
|
||||||
EditToolbar.prototype.getStructure = function () {
|
EditToolbar.prototype.getStructure = function () {
|
||||||
return this.toolbarStructure;
|
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`.
|
* to two-way bind to the state handled by `mct-toolbar`.
|
||||||
|
*
|
||||||
* @returns {Array} state of the toolbar
|
* @returns {Array} state of the toolbar
|
||||||
*/
|
*/
|
||||||
EditToolbar.prototype.getState = function () {
|
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
|
* @param {number} index the index of the corresponding
|
||||||
* element in the state array
|
* 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) {
|
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;
|
var self = this;
|
||||||
|
|
||||||
// Update value for this property in all elements of the
|
function observeObject(domainObject, id) {
|
||||||
// selection which have this property.
|
var unobserveObject = self.openmct.objects.observe(domainObject, '*', function (newObject) {
|
||||||
function updateProperties(property, val) {
|
self.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject));
|
||||||
var changed = false;
|
self.scheduleStateUpdate();
|
||||||
|
});
|
||||||
// Update property in a selected element
|
self.unobserveObjects.push(unobserveObject);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update property in all selected elements
|
|
||||||
self.selection.forEach(updateProperty);
|
|
||||||
|
|
||||||
// Return whether or not anything changed
|
|
||||||
return changed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateProperties(this.properties[index], value);
|
structure.forEach(function (item) {
|
||||||
|
var domainObject = item.domainObject;
|
||||||
|
var id = objectUtils.makeKeyString(domainObject.identifier);
|
||||||
|
|
||||||
|
if (!self.domainObjectsById[id]) {
|
||||||
|
self.domainObjectsById[id] = {
|
||||||
|
domainObject: domainObject,
|
||||||
|
properties: []
|
||||||
|
};
|
||||||
|
observeObject(domainObject, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.domainObjectsById[id].properties.push(item.property);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delays updating the state.
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.scheduleStateUpdate = function () {
|
||||||
|
if (this.stateUpdateScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stateUpdateScheduled = true;
|
||||||
|
setTimeout(this.updateStateAfterMutation.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
EditToolbar.prototype.updateStateAfterMutation = function () {
|
||||||
|
this.stateTracker.forEach(function (state, index) {
|
||||||
|
if (!this.domainObjectsById[state.id].newObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var domainObject = this.domainObjectsById[state.id].domainObject;
|
||||||
|
var newObject = this.domainObjectsById[state.id].newObject;
|
||||||
|
var currentValue = _.get(domainObject, state.property);
|
||||||
|
var newValue = _.get(newObject, state.property);
|
||||||
|
|
||||||
|
state.domainObject = newObject;
|
||||||
|
|
||||||
|
if (currentValue !== newValue) {
|
||||||
|
this.updateState(index, newValue);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
Object.values(this.domainObjectsById).forEach(function (tracker) {
|
||||||
|
if (tracker.newObject) {
|
||||||
|
tracker.domainObject = tracker.newObject;
|
||||||
|
}
|
||||||
|
delete tracker.newObject;
|
||||||
|
});
|
||||||
|
this.stateUpdateScheduled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listeners.
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.deregisterListeners = function () {
|
||||||
|
this.unobserveObjects.forEach(function (unobserveObject) {
|
||||||
|
unobserveObject();
|
||||||
|
});
|
||||||
|
this.unobserveObjects = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
EditToolbar.prototype.handleStateChanges = function (state) {
|
||||||
|
(state || []).map(function (newValue, index) {
|
||||||
|
var domainObject = this.stateTracker[index].domainObject;
|
||||||
|
var property = this.stateTracker[index].property;
|
||||||
|
var currentValue = _.get(domainObject, property);
|
||||||
|
|
||||||
|
if (currentValue !== newValue) {
|
||||||
|
this.updateDomainObject(domainObject, property, newValue);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
EditToolbar.prototype.destroy = function () {
|
||||||
|
this.deregisterListeners();
|
||||||
};
|
};
|
||||||
|
|
||||||
return EditToolbar;
|
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) {
|
function (EditToolbar) {
|
||||||
|
|
||||||
describe("An Edit mode toolbar", function () {
|
describe("An Edit mode toolbar", function () {
|
||||||
var mockCommit,
|
var mockOpenMCT,
|
||||||
|
mockScope,
|
||||||
|
mockObjects,
|
||||||
|
mockDomainObject,
|
||||||
testStructure,
|
testStructure,
|
||||||
testAB,
|
testAB,
|
||||||
testABC,
|
testABC,
|
||||||
@ -35,35 +38,30 @@ define(
|
|||||||
testM,
|
testM,
|
||||||
toolbar;
|
toolbar;
|
||||||
|
|
||||||
function getVisibility(obj) {
|
|
||||||
return !obj.hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockCommit = jasmine.createSpy('commit');
|
mockOpenMCT = jasmine.createSpy('openmct', ['objects']);
|
||||||
testStructure = {
|
mockObjects = jasmine.createSpyObj('objects', ['observe']);
|
||||||
sections: [
|
mockObjects.observe.andReturn();
|
||||||
{
|
mockOpenMCT.objects = mockObjects;
|
||||||
items: [
|
mockScope = jasmine.createSpyObj("$scope", [
|
||||||
{ name: "A", property: "a", exclusive: true },
|
"$watchCollection",
|
||||||
{ name: "B", property: "b", exclusive: true },
|
"$on"
|
||||||
{ name: "C", property: "c", exclusive: true }
|
]);
|
||||||
]
|
mockScope.$watchCollection.andReturn();
|
||||||
},
|
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
||||||
{
|
'identifier'
|
||||||
items: [
|
]);
|
||||||
{ name: "X", property: "x" },
|
|
||||||
{ name: "Y", property: "y", exclusive: true },
|
testStructure = [
|
||||||
{ name: "Z", property: "z", exclusive: true }
|
{ name: "A", property: "a", domainObject: mockDomainObject },
|
||||||
]
|
{ name: "B", property: "b", domainObject: mockDomainObject },
|
||||||
},
|
{ name: "C", property: "c", domainObject: mockDomainObject },
|
||||||
{
|
{ name: "X", property: "x", domainObject: mockDomainObject },
|
||||||
items: [
|
{ name: "Y", property: "y", domainObject: mockDomainObject },
|
||||||
{ name: "M", method: "m", exclusive: true }
|
{ name: "Z", property: "z", domainObject: mockDomainObject },
|
||||||
]
|
{ name: "M", method: "m", domainObject: mockDomainObject }
|
||||||
}
|
];
|
||||||
]
|
|
||||||
};
|
|
||||||
testAB = { a: 0, b: 1 };
|
testAB = { a: 0, b: 1 };
|
||||||
testABC = { a: 0, b: 1, c: 2 };
|
testABC = { a: 0, b: 1, c: 2 };
|
||||||
testABC2 = { a: 4, b: 1, c: 2 }; // For inconsistent-state checking
|
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!' };
|
testABCYZ = { a: 0, b: 1, c: 2, y: 'Y!', z: 'Z!' };
|
||||||
testM = { m: jasmine.createSpy("method") };
|
testM = { m: jasmine.createSpy("method") };
|
||||||
|
|
||||||
toolbar = new EditToolbar(testStructure, mockCommit);
|
toolbar = new EditToolbar(mockScope, mockOpenMCT, testStructure);
|
||||||
});
|
|
||||||
|
|
||||||
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]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds click functions when a method is specified", function () {
|
it("adds click functions when a method is specified", function () {
|
||||||
toolbar.setSelection([testM]);
|
var structure = toolbar.getStructure();
|
||||||
// Verify precondition
|
expect(structure[6].click).toBeDefined();
|
||||||
expect(testM.m).not.toHaveBeenCalled();
|
});
|
||||||
// Click!
|
|
||||||
toolbar.getStructure().sections[2].items[0].click();
|
it("adds key for controls that define a property", function () {
|
||||||
// Should have called the underlying function
|
var structure = toolbar.getStructure();
|
||||||
expect(testM.m).toHaveBeenCalled();
|
expect(structure[0].key).toEqual(0);
|
||||||
// Should also have committed the change
|
|
||||||
expect(mockCommit).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,16 @@ define(
|
|||||||
* The mct-selectable directive allows selection functionality
|
* The mct-selectable directive allows selection functionality
|
||||||
* (click) to be attached to specific elements.
|
* (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
|
* @memberof platform/commonUI/general
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
|
@ -39,240 +39,309 @@ define([
|
|||||||
"cssClass": "icon-box-with-dashed-lines",
|
"cssClass": "icon-box-with-dashed-lines",
|
||||||
"type": "telemetry.fixed",
|
"type": "telemetry.fixed",
|
||||||
"template": fixedTemplate,
|
"template": fixedTemplate,
|
||||||
"uses": [
|
"uses": [],
|
||||||
"composition"
|
"editable": true
|
||||||
],
|
}
|
||||||
"editable": true,
|
],
|
||||||
"toolbar": {
|
"toolbars": [
|
||||||
"sections": [
|
{
|
||||||
|
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 [
|
||||||
{
|
{
|
||||||
"items": [
|
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: [
|
||||||
{
|
{
|
||||||
"method": "add",
|
"name": "Box",
|
||||||
"cssClass": "icon-plus",
|
"cssClass": "icon-box",
|
||||||
"control": "menu-button",
|
"key": "fixed.box"
|
||||||
"text": "Add",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "Box",
|
|
||||||
"cssClass": "icon-box",
|
|
||||||
"key": "fixed.box"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Line",
|
|
||||||
"cssClass": "icon-line-horz",
|
|
||||||
"key": "fixed.line"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Text",
|
|
||||||
"cssClass": "icon-T",
|
|
||||||
"key": "fixed.text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Image",
|
|
||||||
"cssClass": "icon-image",
|
|
||||||
"key": "fixed.image"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"method": "order",
|
|
||||||
"cssClass": "icon-layers",
|
|
||||||
"control": "menu-button",
|
|
||||||
"title": "Layering",
|
|
||||||
"description": "Move the selected object above or below other objects",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "Move to Top",
|
|
||||||
"cssClass": "icon-arrow-double-up",
|
|
||||||
"key": "top"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Move Up",
|
|
||||||
"cssClass": "icon-arrow-up",
|
|
||||||
"key": "up"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Move Down",
|
|
||||||
"cssClass": "icon-arrow-down",
|
|
||||||
"key": "down"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Move to Bottom",
|
|
||||||
"cssClass": "icon-arrow-double-down",
|
|
||||||
"key": "bottom"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "fill",
|
"name": "Line",
|
||||||
"cssClass": "icon-paint-bucket",
|
|
||||||
"title": "Fill color",
|
|
||||||
"description": "Set fill color",
|
|
||||||
"control": "color"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"property": "stroke",
|
|
||||||
"cssClass": "icon-line-horz",
|
"cssClass": "icon-line-horz",
|
||||||
"title": "Border color",
|
"key": "fixed.line"
|
||||||
"description": "Set border color",
|
|
||||||
"control": "color"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "url",
|
"name": "Text",
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"property": "color",
|
|
||||||
"cssClass": "icon-T",
|
"cssClass": "icon-T",
|
||||||
"title": "Text color",
|
"key": "fixed.text"
|
||||||
"description": "Set text color",
|
|
||||||
"mandatory": true,
|
|
||||||
"control": "color"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "size",
|
"name": "Image",
|
||||||
"title": "Text size",
|
"cssClass": "icon-image",
|
||||||
"description": "Set text size",
|
"key": "fixed.image"
|
||||||
"control": "select",
|
|
||||||
"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" };
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"items": [
|
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: [
|
||||||
{
|
{
|
||||||
"property": "editX",
|
"name": "Move to Top",
|
||||||
"text": "X",
|
"cssClass": "icon-arrow-double-up",
|
||||||
"name": "X",
|
"key": "top"
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control": "numberfield",
|
|
||||||
"min": "0"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "editY",
|
"name": "Move Up",
|
||||||
"text": "Y",
|
"cssClass": "icon-arrow-up",
|
||||||
"name": "Y",
|
"key": "up"
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control": "numberfield",
|
|
||||||
"min": "0"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "editX1",
|
"name": "Move Down",
|
||||||
"text": "X1",
|
"cssClass": "icon-arrow-down",
|
||||||
"name": "X1",
|
"key": "down"
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control" : "numberfield",
|
|
||||||
"min": "0"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"property": "editY1",
|
"name": "Move to Bottom",
|
||||||
"text": "Y1",
|
"cssClass": "icon-arrow-double-down",
|
||||||
"name": "Y1",
|
"key": "bottom"
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control" : "numberfield",
|
|
||||||
"min": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"property": "editX2",
|
|
||||||
"text": "X2",
|
|
||||||
"name": "X2",
|
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control" : "numberfield",
|
|
||||||
"min": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"property": "editY2",
|
|
||||||
"text": "Y2",
|
|
||||||
"name": "Y2",
|
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control" : "numberfield",
|
|
||||||
"min": "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"property": "editHeight",
|
|
||||||
"text": "H",
|
|
||||||
"name": "H",
|
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control": "numberfield",
|
|
||||||
"description": "Resize object height",
|
|
||||||
"min": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"property": "editWidth",
|
|
||||||
"text": "W",
|
|
||||||
"name": "W",
|
|
||||||
"cssClass": "l-input-sm",
|
|
||||||
"control": "numberfield",
|
|
||||||
"description": "Resize object width",
|
|
||||||
"min": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"property": "useGrid",
|
|
||||||
"name": "Snap to Grid",
|
|
||||||
"control": "checkbox"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"items": [
|
control: "color",
|
||||||
{
|
domainObject: domainObject,
|
||||||
"property": "text",
|
property: path + ".fill",
|
||||||
"cssClass": "icon-gear",
|
cssClass: "icon-paint-bucket",
|
||||||
"control": "dialog-button",
|
title: "Fill color",
|
||||||
"title": "Text Properties",
|
description: "Set fill color",
|
||||||
"description": "Edit text properties",
|
key: 'fill'
|
||||||
"dialog": {
|
|
||||||
"control": "textfield",
|
|
||||||
"name": "Text",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "showTitle",
|
|
||||||
"cssClass": "icon-two-parts-both",
|
|
||||||
"control": "button",
|
|
||||||
"title": "Show title",
|
|
||||||
"description": "Show telemetry element title"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "hideTitle",
|
|
||||||
"cssClass": "icon-two-parts-one-only",
|
|
||||||
"control": "button",
|
|
||||||
"title": "Hide title",
|
|
||||||
"description": "Hide telemetry element title"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"items": [
|
control: "color",
|
||||||
{
|
domainObject: domainObject,
|
||||||
"method": "remove",
|
property: path + ".stroke",
|
||||||
"control": "button",
|
cssClass: "icon-line-horz",
|
||||||
"cssClass": "icon-trash"
|
title: "Border color",
|
||||||
}
|
description: "Set border color",
|
||||||
]
|
key: 'stroke'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "dialog-button",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".url",
|
||||||
|
cssClass: "icon-image",
|
||||||
|
title: "Image Properties",
|
||||||
|
description: "Edit image properties",
|
||||||
|
key: 'url',
|
||||||
|
dialog: {
|
||||||
|
control: "textfield",
|
||||||
|
name: "Image URL",
|
||||||
|
cssClass: "l-input-lg",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "color",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".color",
|
||||||
|
cssClass: "icon-T",
|
||||||
|
title: "Text color",
|
||||||
|
mandatory: true,
|
||||||
|
description: "Set text color",
|
||||||
|
key: 'color'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "select",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".size",
|
||||||
|
title: "Text size",
|
||||||
|
description: "Set text size",
|
||||||
|
"options": [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
|
||||||
|
return { "name": size + " px", "value": size + "px" };
|
||||||
|
}),
|
||||||
|
key: 'size'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".x",
|
||||||
|
text: "X",
|
||||||
|
name: "X",
|
||||||
|
key: "x",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".y",
|
||||||
|
text: "Y",
|
||||||
|
name: "Y",
|
||||||
|
key: "y",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".x",
|
||||||
|
text: "X1",
|
||||||
|
name: "X1",
|
||||||
|
key: "x1",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".y",
|
||||||
|
text: "Y1",
|
||||||
|
name: "Y1",
|
||||||
|
key: "y1",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".x2",
|
||||||
|
text: "X2",
|
||||||
|
name: "X2",
|
||||||
|
key: "x2",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".y2",
|
||||||
|
text: "Y2",
|
||||||
|
name: "Y2",
|
||||||
|
key: "y2",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".height",
|
||||||
|
text: "H",
|
||||||
|
name: "H",
|
||||||
|
key: "height",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
description: "Resize object height",
|
||||||
|
min: "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".width",
|
||||||
|
text: "W",
|
||||||
|
name: "W",
|
||||||
|
key: "width",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
description: "Resize object width",
|
||||||
|
min: "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "checkbox",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".useGrid",
|
||||||
|
name: "Snap to Grid",
|
||||||
|
key: "useGrid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "dialog-button",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".text",
|
||||||
|
cssClass: "icon-gear",
|
||||||
|
title: "Text Properties",
|
||||||
|
description: "Edit text properties",
|
||||||
|
key: "text",
|
||||||
|
dialog: {
|
||||||
|
control: "textfield",
|
||||||
|
name: "Text",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "checkbox",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".titled",
|
||||||
|
name: "Show Title",
|
||||||
|
key: "titled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "button",
|
||||||
|
domainObject: domainObject,
|
||||||
|
method: function () {
|
||||||
|
selection[0].context.fixedController.remove(
|
||||||
|
selection[0].context.elementProxy
|
||||||
|
);
|
||||||
|
},
|
||||||
|
key: "remove",
|
||||||
|
cssClass: "icon-trash"
|
||||||
}
|
}
|
||||||
]
|
].filter(function (item) {
|
||||||
|
var filtered;
|
||||||
|
|
||||||
|
properties.forEach(function (property) {
|
||||||
|
if (item.property && item.key === property ||
|
||||||
|
item.method && item.key === property) {
|
||||||
|
filtered = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -62,29 +62,7 @@ define([
|
|||||||
"type": "layout",
|
"type": "layout",
|
||||||
"template": layoutTemplate,
|
"template": layoutTemplate,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"uses": [],
|
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "fixed",
|
"key": "fixed",
|
||||||
@ -305,6 +283,27 @@ define([
|
|||||||
"implementation": LayoutCompositionPolicy
|
"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": [
|
"types": [
|
||||||
{
|
{
|
||||||
"key": "layout",
|
"key": "layout",
|
||||||
@ -314,7 +313,14 @@ define([
|
|||||||
"priority": 900,
|
"priority": 900,
|
||||||
"features": "creation",
|
"features": "creation",
|
||||||
"model": {
|
"model": {
|
||||||
"composition": []
|
"composition": [],
|
||||||
|
configuration: {
|
||||||
|
layout: {
|
||||||
|
panels: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
<mct-include key="element.template"
|
<mct-include key="element.template"
|
||||||
parameters="{ gridSize: controller.getGridSize() }"
|
parameters="{ gridSize: controller.getGridSize() }"
|
||||||
ng-model="element">
|
ng-model="element">
|
||||||
</mct-include>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
<!-- Selection highlight, handles -->
|
<!-- Selection highlight, handles -->
|
||||||
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
|
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
||||||
ng-repeat="childObject in composition"
|
ng-repeat="childObject in composition"
|
||||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
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-dblclick="controller.drill($event, childObject)"
|
||||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
ng-style="controller.getFrameStyle(childObject.getId())">
|
||||||
|
|
||||||
|
@ -38,6 +38,24 @@ define(
|
|||||||
|
|
||||||
var DEFAULT_DIMENSIONS = [2, 1];
|
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
|
* The FixedController is responsible for supporting the
|
||||||
* Fixed Position view. It arranges frames according to saved
|
* Fixed Position view. It arranges frames according to saved
|
||||||
@ -51,14 +69,14 @@ define(
|
|||||||
this.names = {}; // Cache names by ID
|
this.names = {}; // Cache names by ID
|
||||||
this.values = {}; // Cache values by ID
|
this.values = {}; // Cache values by ID
|
||||||
this.elementProxiesById = {};
|
this.elementProxiesById = {};
|
||||||
|
this.telemetryObjects = {};
|
||||||
this.telemetryObjects = [];
|
this.subscriptions = {};
|
||||||
this.subscriptions = [];
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.$element = $element;
|
this.$element = $element;
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
|
this.dialogService = dialogService;
|
||||||
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
|
this.$q = $q;
|
||||||
|
this.newDomainObject = $scope.domainObject.useCapability('adapter');
|
||||||
this.fixedViewSelectable = false;
|
this.fixedViewSelectable = false;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -67,59 +85,13 @@ define(
|
|||||||
'fetchHistoricalData',
|
'fetchHistoricalData',
|
||||||
'getTelemetry',
|
'getTelemetry',
|
||||||
'setDisplayedValue',
|
'setDisplayedValue',
|
||||||
'subscribeToObjects',
|
'subscribeToObject',
|
||||||
'unsubscribe',
|
'unsubscribe',
|
||||||
'updateView'
|
'updateView'
|
||||||
].forEach(function (name) {
|
].forEach(function (name) {
|
||||||
self[name] = self[name].bind(self);
|
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
|
// Decorate an element for display
|
||||||
function makeProxyElement(element, index, elements) {
|
function makeProxyElement(element, index, elements) {
|
||||||
var ElementProxy = ElementProxies[element.type],
|
var ElementProxy = ElementProxies[element.type],
|
||||||
@ -137,14 +109,14 @@ define(
|
|||||||
|
|
||||||
// Decorate elements in the current configuration
|
// Decorate elements in the current configuration
|
||||||
function refreshElements() {
|
function refreshElements() {
|
||||||
var elements = (($scope.configuration || {}).elements || []);
|
var elements = (((self.newDomainObject.configuration || {})['fixed-display'] || {}).elements || []);
|
||||||
|
|
||||||
// Create the new proxies...
|
// Create the new proxies...
|
||||||
self.elementProxies = elements.map(makeProxyElement);
|
self.elementProxies = elements.map(makeProxyElement);
|
||||||
|
|
||||||
// If selection is not in array, select parent.
|
|
||||||
// Otherwise, set the element to select after refresh.
|
|
||||||
if (self.selectedElementProxy) {
|
if (self.selectedElementProxy) {
|
||||||
|
// If selection is not in array, select parent.
|
||||||
|
// Otherwise, set the element to select after refresh.
|
||||||
var index = elements.indexOf(self.selectedElementProxy.element);
|
var index = elements.indexOf(self.selectedElementProxy.element);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
self.$element[0].click();
|
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
|
// Trigger a new query for telemetry data
|
||||||
function updateDisplayBounds(bounds, isTick) {
|
function updateDisplayBounds(bounds, isTick) {
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
//Reset values
|
//Reset values
|
||||||
self.values = {};
|
self.values = {};
|
||||||
refreshElements();
|
refreshElements();
|
||||||
|
|
||||||
//Fetch new data
|
//Fetch new data
|
||||||
self.fetchHistoricalData(self.telemetryObjects);
|
Object.values(self.telemetryObjects).forEach(function (object) {
|
||||||
|
self.fetchHistoricalData(object);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an element to this view
|
// Add an element to this view
|
||||||
function addElement(element) {
|
function addElement(element) {
|
||||||
// Ensure that configuration field is populated
|
var index;
|
||||||
$scope.configuration = $scope.configuration || {};
|
var elements = (((self.newDomainObject.configuration || {})['fixed-display'] || {}).elements || []);
|
||||||
// Make sure there is a "elements" field in the
|
elements.push(element);
|
||||||
// view configuration.
|
|
||||||
$scope.configuration.elements =
|
|
||||||
$scope.configuration.elements || [];
|
|
||||||
// Store the position of this element.
|
|
||||||
$scope.configuration.elements.push(element);
|
|
||||||
|
|
||||||
self.elementToSelectAfterRefresh = element;
|
if (self.selectedElementProxy) {
|
||||||
|
index = elements.indexOf(self.selectedElementProxy.element);
|
||||||
// Refresh displayed elements
|
|
||||||
refreshElements();
|
|
||||||
|
|
||||||
// Mark change as persistable
|
|
||||||
if ($scope.commit) {
|
|
||||||
$scope.commit("Dropped an 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
|
// Position a panel after a drop event
|
||||||
function handleDrop(e, id, position) {
|
function handleDrop(e, id, position) {
|
||||||
// Don't handle this event if it has already been handled
|
// 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) {
|
if (e.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Store the position of this element.
|
// Store the position of this element.
|
||||||
|
// color is set to "" to let the CSS theme determine the default color
|
||||||
addElement({
|
addElement({
|
||||||
type: "fixed.telemetry",
|
type: "fixed.telemetry",
|
||||||
x: Math.floor(position.x / self.gridSize[0]),
|
x: Math.floor(position.x / self.gridSize[0]),
|
||||||
@ -254,71 +200,229 @@ define(
|
|||||||
useGrid: true
|
useGrid: true
|
||||||
});
|
});
|
||||||
|
|
||||||
//Re-initialize objects, and subscribe to new object
|
// Subscribe to the new object to get telemetry
|
||||||
self.getTelemetry($scope.domainObject);
|
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) {
|
|
||||||
var selection = selectable[0];
|
|
||||||
|
|
||||||
if (!selection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection.context.elementProxy) {
|
|
||||||
self.selectedElementProxy = selection.context.elementProxy;
|
|
||||||
self.mvHandle = self.generateDragHandle(self.selectedElementProxy);
|
|
||||||
self.resizeHandles = self.generateDragHandles(self.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.resizeHandles = [];
|
|
||||||
self.mvHandle = undefined;
|
|
||||||
self.selectedElementProxy = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.elementProxies = [];
|
this.elementProxies = [];
|
||||||
this.generateDragHandle = generateDragHandle;
|
this.addElement = addElement;
|
||||||
this.generateDragHandles = generateDragHandles;
|
this.refreshElements = refreshElements;
|
||||||
this.updateSelectionStyle = updateSelectionStyle;
|
this.fixedProxy = new FixedProxy(this.addElement, this.$q, this.dialogService);
|
||||||
|
|
||||||
// Detect changes to grid size
|
this.composition = this.openmct.composition.get(this.newDomainObject);
|
||||||
$scope.$watch("model.layoutGrid", updateElementPositions);
|
this.composition.on('add', this.onCompositionAdd, this);
|
||||||
|
this.composition.on('remove', this.onCompositionRemove, this);
|
||||||
|
this.composition.load();
|
||||||
|
|
||||||
// Position panes where they are dropped
|
// Position panes where they are dropped
|
||||||
$scope.$on("mctDrop", handleDrop);
|
$scope.$on("mctDrop", handleDrop);
|
||||||
|
|
||||||
// Position panes when the model field changes
|
$scope.$on("$destroy", this.destroy.bind(this));
|
||||||
$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
|
// Respond to external bounds changes
|
||||||
this.openmct.time.on("bounds", updateDisplayBounds);
|
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());
|
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) {
|
||||||
|
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 (!this.fixedViewSelectable && selectable.length === 1) {
|
||||||
|
this.fixedViewSelectable = true;
|
||||||
|
selection.context.fixedController = this;
|
||||||
|
this.openmct.selection.select(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
* A rate-limited digest function. Caps digests at 60Hz
|
||||||
* @private
|
* @private
|
||||||
@ -340,31 +444,30 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.unsubscribe = function () {
|
FixedController.prototype.unsubscribe = function () {
|
||||||
this.subscriptions.forEach(function (unsubscribeFunc) {
|
Object.values(this.subscriptions).forEach(function (unsubscribeFunc) {
|
||||||
unsubscribeFunc();
|
unsubscribeFunc();
|
||||||
});
|
});
|
||||||
this.subscriptions = [];
|
this.subscriptions = {};
|
||||||
this.telemetryObjects = [];
|
this.telemetryObjects = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to all given domain objects
|
* Subscribe to the given domain object
|
||||||
* @private
|
* @private
|
||||||
* @param {object[]} objects Domain objects to subscribe to
|
* @param {object} object Domain object to subscribe to
|
||||||
* @returns {object[]} The provided objects, for chaining.
|
* @returns {object} The provided object, for chaining.
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.subscribeToObjects = function (objects) {
|
FixedController.prototype.subscribeToObject = function (object) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var timeAPI = this.openmct.time;
|
var timeAPI = this.openmct.time;
|
||||||
|
var id = objectUtils.makeKeyString(object.identifier);
|
||||||
|
this.subscriptions[id] = self.openmct.telemetry.subscribe(object, function (datum) {
|
||||||
|
if (timeAPI.clock() !== undefined) {
|
||||||
|
self.updateView(object, datum);
|
||||||
|
}
|
||||||
|
}, {});
|
||||||
|
|
||||||
this.subscriptions = objects.map(function (object) {
|
return object;
|
||||||
return self.openmct.telemetry.subscribe(object, function (datum) {
|
|
||||||
if (timeAPI.clock() !== undefined) {
|
|
||||||
self.updateView(object, datum);
|
|
||||||
}
|
|
||||||
}, {});
|
|
||||||
});
|
|
||||||
return objects;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -416,23 +519,22 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the last historical data point for the given domain objects
|
* Request the last historical data point for the given domain object
|
||||||
* @param {object[]} objects
|
* @param {object} object
|
||||||
* @returns {object[]} the provided objects for chaining.
|
* @returns {object} the provided object for chaining.
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.fetchHistoricalData = function (objects) {
|
FixedController.prototype.fetchHistoricalData = function (object) {
|
||||||
var bounds = this.openmct.time.bounds();
|
var bounds = this.openmct.time.bounds();
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
objects.forEach(function (object) {
|
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
|
||||||
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
|
.then(function (data) {
|
||||||
.then(function (data) {
|
if (data.length > 0) {
|
||||||
if (data.length > 0) {
|
self.updateView(object, data[data.length - 1]);
|
||||||
self.updateView(object, data[data.length - 1]);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
});
|
return object;
|
||||||
return objects;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -457,33 +559,25 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
FixedController.prototype.getTelemetry = function (domainObject) {
|
FixedController.prototype.getTelemetry = function (domainObject) {
|
||||||
var newObject = domainObject.useCapability('adapter');
|
var id = objectUtils.makeKeyString(domainObject.identifier);
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.subscriptions.length > 0) {
|
if (this.subscriptions[id]) {
|
||||||
this.unsubscribe();
|
this.subscriptions[id]();
|
||||||
|
delete this.subscriptions[id];
|
||||||
|
}
|
||||||
|
delete this.telemetryObjects[id];
|
||||||
|
|
||||||
|
if (!this.openmct.telemetry.isTelemetryObject(domainObject)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterForTelemetryObjects(objects) {
|
// Initialize display
|
||||||
return objects.filter(function (object) {
|
this.telemetryObjects[id] = domainObject;
|
||||||
return self.openmct.telemetry.isTelemetryObject(object);
|
this.setDisplayedValue(domainObject, "");
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeDisplay(objects) {
|
return Promise.resolve(domainObject)
|
||||||
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)
|
|
||||||
.then(this.fetchHistoricalData)
|
.then(this.fetchHistoricalData)
|
||||||
.then(this.subscribeToObjects);
|
.then(this.subscribeToObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -580,12 +674,12 @@ define(
|
|||||||
* Gets the selection context.
|
* Gets the selection context.
|
||||||
*
|
*
|
||||||
* @param elementProxy the element proxy
|
* @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) {
|
FixedController.prototype.getContext = function (elementProxy) {
|
||||||
return {
|
return {
|
||||||
elementProxy: elementProxy,
|
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;
|
return FixedController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -24,30 +24,34 @@ define(
|
|||||||
[],
|
[],
|
||||||
function () {
|
function () {
|
||||||
|
|
||||||
|
|
||||||
// Drag handle dimensions
|
// Drag handle dimensions
|
||||||
var DRAG_HANDLE_SIZE = [6, 6];
|
var DRAG_HANDLE_SIZE = [6, 6];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Template-displayable drag handle for an element in fixed
|
* Template-displayable drag handle for an element in fixed
|
||||||
* position mode.
|
* 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
|
* @memberof platform/features/layout
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function FixedDragHandle(elementHandle, gridSize, update, commit) {
|
function FixedDragHandle(elementHandle, configPath, fixedControl) {
|
||||||
this.elementHandle = elementHandle;
|
this.elementHandle = elementHandle;
|
||||||
this.gridSize = gridSize;
|
this.configPath = configPath;
|
||||||
this.update = update;
|
this.fixedControl = fixedControl;
|
||||||
this.commit = commit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a CSS style to position this drag handle.
|
* Get a CSS style to position this drag handle.
|
||||||
|
*
|
||||||
* @returns CSS style object (for `ng-style`)
|
* @returns CSS style object (for `ng-style`)
|
||||||
* @memberof platform/features/layout.FixedDragHandle#
|
* @memberof platform/features/layout.FixedDragHandle#
|
||||||
*/
|
*/
|
||||||
FixedDragHandle.prototype.style = function () {
|
FixedDragHandle.prototype.style = function () {
|
||||||
var gridSize = this.elementHandle.getGridSize();
|
var gridSize = this.elementHandle.getGridSize();
|
||||||
|
|
||||||
// Adjust from grid to pixel coordinates
|
// Adjust from grid to pixel coordinates
|
||||||
var x = this.elementHandle.x() * gridSize[0],
|
var x = this.elementHandle.x() * gridSize[0],
|
||||||
y = this.elementHandle.y() * gridSize[1];
|
y = this.elementHandle.y() * gridSize[1];
|
||||||
@ -75,23 +79,20 @@ define(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Continue a drag gesture; update x/y positions.
|
* 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) {
|
FixedDragHandle.prototype.continueDrag = function (delta) {
|
||||||
var gridSize = this.elementHandle.getGridSize();
|
var gridSize = this.elementHandle.getGridSize();
|
||||||
|
|
||||||
if (this.dragging) {
|
if (this.dragging) {
|
||||||
// Update x/y positions (snapping to grid)
|
// Update x/y positions (snapping to grid)
|
||||||
this.elementHandle.x(
|
var newX = this.dragging.x + Math.round(delta[0] / gridSize[0]);
|
||||||
this.dragging.x + Math.round(delta[0] / gridSize[0])
|
var newY = this.dragging.y + Math.round(delta[1] / gridSize[1]);
|
||||||
);
|
|
||||||
this.elementHandle.y(
|
this.elementHandle.x(Math.max(0, newX));
|
||||||
this.dragging.y + Math.round(delta[1] / gridSize[1])
|
this.elementHandle.y(Math.max(0, newY));
|
||||||
);
|
this.fixedControl.updateSelectionStyle();
|
||||||
// Invoke update callback
|
|
||||||
if (this.update) {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,12 +101,8 @@ define(
|
|||||||
* concludes to trigger commit of changes.
|
* concludes to trigger commit of changes.
|
||||||
*/
|
*/
|
||||||
FixedDragHandle.prototype.endDrag = function () {
|
FixedDragHandle.prototype.endDrag = function () {
|
||||||
// Clear cached state
|
|
||||||
this.dragging = undefined;
|
this.dragging = undefined;
|
||||||
// Mark change as complete
|
this.fixedControl.mutate(this.configPath, this.elementHandle.element);
|
||||||
if (this.commit) {
|
|
||||||
this.commit("Dragged handle.");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return FixedDragHandle;
|
return FixedDragHandle;
|
||||||
|
@ -78,29 +78,30 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.configuration = $scope.configuration || {};
|
$scope.configuration = $scope.configuration || {};
|
||||||
$scope.configuration.panels =
|
$scope.configuration.panels = $scope.configuration.panels || {};
|
||||||
$scope.configuration.panels || {};
|
|
||||||
|
|
||||||
$scope.configuration.panels[id] = {
|
self.openmct.objects.get(id).then(function (object) {
|
||||||
position: [
|
$scope.configuration.panels[id] = {
|
||||||
Math.floor(position.x / self.gridSize[0]),
|
position: [
|
||||||
Math.floor(position.y / self.gridSize[1])
|
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
|
// Store the id so that the newly-dropped object
|
||||||
// gets selected during refresh composition
|
// gets selected during refresh composition
|
||||||
self.droppedIdToSelectAfterRefresh = id;
|
self.droppedIdToSelectAfterRefresh = id;
|
||||||
|
|
||||||
|
self.commit();
|
||||||
|
|
||||||
|
// Populate template-facing position for this id
|
||||||
|
self.rawPositions[id] = $scope.configuration.panels[id];
|
||||||
|
self.populatePosition(id);
|
||||||
|
refreshComposition();
|
||||||
|
});
|
||||||
|
|
||||||
// Mark change as persistable
|
|
||||||
if ($scope.commit) {
|
|
||||||
$scope.commit("Dropped a frame.");
|
|
||||||
}
|
|
||||||
// Populate template-facing position for this id
|
|
||||||
self.rawPositions[id] =
|
|
||||||
$scope.configuration.panels[id];
|
|
||||||
self.populatePosition(id);
|
|
||||||
// Layout may contain embedded views which will
|
// Layout may contain embedded views which will
|
||||||
// listen for drops, so call preventDefault() so
|
// listen for drops, so call preventDefault() so
|
||||||
// that they can recognize that this event is handled.
|
// that they can recognize that this event is handled.
|
||||||
@ -157,10 +158,7 @@ define(
|
|||||||
$scope.configuration.panels[self.activeDragId].dimensions =
|
$scope.configuration.panels[self.activeDragId].dimensions =
|
||||||
self.rawPositions[self.activeDragId].dimensions;
|
self.rawPositions[self.activeDragId].dimensions;
|
||||||
|
|
||||||
// Mark this object as dirty to encourage persistence
|
self.commit();
|
||||||
if ($scope.commit) {
|
|
||||||
$scope.commit("Moved frame.");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sets the selectable object in response to the selection change event.
|
// Sets the selectable object in response to the selection change event.
|
||||||
@ -194,9 +192,22 @@ define(
|
|||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
$scope.$on("$destroy", function () {
|
||||||
openmct.selection.off("change", setSelection);
|
openmct.selection.off("change", setSelection);
|
||||||
|
self.unlisten();
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.$on("mctDrop", handleDrop);
|
$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,
|
// Utility function to copy raw positions from configuration,
|
||||||
@ -220,7 +231,6 @@ define(
|
|||||||
*/
|
*/
|
||||||
LayoutController.prototype.setFrames = function (ids) {
|
LayoutController.prototype.setFrames = function (ids) {
|
||||||
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
|
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
|
||||||
|
|
||||||
this.frames = {};
|
this.frames = {};
|
||||||
|
|
||||||
this.$scope.composition.forEach(function (object) {
|
this.$scope.composition.forEach(function (object) {
|
||||||
@ -230,11 +240,22 @@ define(
|
|||||||
if (panels[id].hasOwnProperty('hasFrame')) {
|
if (panels[id].hasOwnProperty('hasFrame')) {
|
||||||
this.frames[id] = panels[id].hasFrame;
|
this.frames[id] = panels[id].hasFrame;
|
||||||
} else {
|
} else {
|
||||||
this.frames[id] = DEFAULT_HIDDEN_FRAME_TYPES.indexOf(object.getModel().type) === -1;
|
this.frames[id] = this.getDefaultFrame(object.getModel().type);
|
||||||
}
|
}
|
||||||
}, this);
|
}, 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
|
// Convert from { positions: ..., dimensions: ... } to an
|
||||||
// appropriate ng-style argument, to position frames.
|
// appropriate ng-style argument, to position frames.
|
||||||
LayoutController.prototype.convertPosition = function (raw) {
|
LayoutController.prototype.convertPosition = function (raw) {
|
||||||
@ -389,40 +410,6 @@ define(
|
|||||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
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.
|
* Bypasses selection if drag is in progress.
|
||||||
*
|
*
|
||||||
@ -497,17 +484,25 @@ define(
|
|||||||
* Gets the selection context.
|
* Gets the selection context.
|
||||||
*
|
*
|
||||||
* @param domainObject the domain object
|
* @param domainObject the domain object
|
||||||
* @returns {object} the context object which includes
|
* @returns {object} the context object which includes item and oldItem
|
||||||
* item, oldItem and toolbar
|
|
||||||
*/
|
*/
|
||||||
LayoutController.prototype.getContext = function (domainObject, toolbar) {
|
LayoutController.prototype.getContext = function (domainObject) {
|
||||||
return {
|
return {
|
||||||
item: domainObject.useCapability('adapter'),
|
item: domainObject.useCapability('adapter'),
|
||||||
oldItem: domainObject,
|
oldItem: domainObject
|
||||||
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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.
|
* Selects a newly-dropped object.
|
||||||
*
|
*
|
||||||
|
@ -53,12 +53,6 @@ define(
|
|||||||
*/
|
*/
|
||||||
proxy.fill = new AccessorMutator(element, 'fill');
|
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;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,13 +71,6 @@ define(
|
|||||||
*/
|
*/
|
||||||
this.gridSize = gridSize || [1,1]; //Ensure a reasonable default
|
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.
|
* Get and/or set the x position of this element.
|
||||||
* Units are in fixed position grid space.
|
* Units are in fixed position grid space.
|
||||||
@ -123,15 +116,16 @@ define(
|
|||||||
this.height = new AccessorMutator(element, 'height');
|
this.height = new AccessorMutator(element, 'height');
|
||||||
|
|
||||||
this.useGrid = new UnitAccessorMutator(this);
|
this.useGrid = new UnitAccessorMutator(this);
|
||||||
|
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.elements = elements;
|
this.elements = elements;
|
||||||
|
this.resizeHandles = [new ResizeHandle(this, this.element)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the display order of this element.
|
* Change the display order of this element.
|
||||||
* @param {string} o where to move this element;
|
* @param {string} o where to move this element;
|
||||||
* one of "top", "up", "down", or "bottom"
|
* one of "top", "up", "down", or "bottom"
|
||||||
|
* @return {Array} the full array of elements
|
||||||
*/
|
*/
|
||||||
ElementProxy.prototype.order = function (o) {
|
ElementProxy.prototype.order = function (o) {
|
||||||
var index = this.index,
|
var index = this.index,
|
||||||
@ -152,16 +146,8 @@ define(
|
|||||||
// anyway, but be consistent)
|
// anyway, but be consistent)
|
||||||
this.index = desired;
|
this.index = desired;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
return elements;
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,7 +194,6 @@ define(
|
|||||||
*/
|
*/
|
||||||
ElementProxy.prototype.getMinWidth = function () {
|
ElementProxy.prototype.getMinWidth = function () {
|
||||||
return Math.ceil(MIN_WIDTH / this.getGridSize()[0]);
|
return Math.ceil(MIN_WIDTH / this.getGridSize()[0]);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,12 +50,6 @@ define(
|
|||||||
*/
|
*/
|
||||||
proxy.url = new AccessorMutator(element, 'url');
|
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;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,19 +32,18 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param element the line element
|
* @param element the line element
|
||||||
* @param {string} xProperty field which stores x position
|
* @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} xOther field which stores x of other end
|
||||||
* @param {string} yOther field which stores y 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}
|
* @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.element = element;
|
||||||
this.xProperty = xProperty;
|
this.xProperty = xProperty;
|
||||||
this.yProperty = yProperty;
|
this.yProperty = yProperty;
|
||||||
this.xOther = xOther;
|
this.xOther = xOther;
|
||||||
this.yOther = yOther;
|
this.yOther = yOther;
|
||||||
this.gridSize = gridSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LineHandle.prototype.x = function (value) {
|
LineHandle.prototype.x = function (value) {
|
||||||
@ -86,7 +85,7 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
LineHandle.prototype.getGridSize = function () {
|
LineHandle.prototype.getGridSize = function () {
|
||||||
return this.gridSize;
|
return this.elementProxy.getGridSize();
|
||||||
};
|
};
|
||||||
|
|
||||||
return LineHandle;
|
return LineHandle;
|
||||||
|
@ -39,10 +39,24 @@ define(
|
|||||||
function LineProxy(element, index, elements, gridSize) {
|
function LineProxy(element, index, elements, gridSize) {
|
||||||
var proxy = new ElementProxy(element, index, elements, gridSize),
|
var proxy = new ElementProxy(element, index, elements, gridSize),
|
||||||
handles = [
|
handles = [
|
||||||
new LineHandle(element, 'x', 'y', 'x2', 'y2', proxy.getGridSize()),
|
new LineHandle(element, proxy, 'x', 'y', 'x2', 'y2'),
|
||||||
new LineHandle(element, 'x2', 'y2', 'x', 'y', proxy.getGridSize())
|
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
|
* Get the top-left x coordinate, in grid space, of
|
||||||
* this line's bounding box.
|
* this line's bounding box.
|
||||||
@ -149,12 +163,6 @@ define(
|
|||||||
return handles;
|
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;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,21 +35,16 @@ define(
|
|||||||
* @memberof platform/features/layout
|
* @memberof platform/features/layout
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ResizeHandle(element, minWidth, minHeight, gridSize) {
|
function ResizeHandle(elementProxy, element) {
|
||||||
|
this.elementProxy = elementProxy;
|
||||||
this.element = element;
|
this.element = element;
|
||||||
|
|
||||||
// Ensure reasonable defaults
|
|
||||||
this.minWidth = minWidth || 0;
|
|
||||||
this.minHeight = minHeight || 0;
|
|
||||||
|
|
||||||
this.gridSize = gridSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResizeHandle.prototype.x = function (value) {
|
ResizeHandle.prototype.x = function (value) {
|
||||||
var element = this.element;
|
var element = this.element;
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
element.width = Math.max(
|
element.width = Math.max(
|
||||||
this.minWidth,
|
this.elementProxy.getMinWidth(),
|
||||||
value - element.x
|
value - element.x
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -60,7 +55,7 @@ define(
|
|||||||
var element = this.element;
|
var element = this.element;
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
element.height = Math.max(
|
element.height = Math.max(
|
||||||
this.minHeight,
|
this.elementProxy.getMinHeight(),
|
||||||
value - element.y
|
value - element.y
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -68,7 +63,7 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
ResizeHandle.prototype.getGridSize = function () {
|
ResizeHandle.prototype.getGridSize = function () {
|
||||||
return this.gridSize;
|
return this.elementProxy.getGridSize();
|
||||||
};
|
};
|
||||||
|
|
||||||
return ResizeHandle;
|
return ResizeHandle;
|
||||||
|
@ -24,9 +24,6 @@ define(
|
|||||||
['./TextProxy'],
|
['./TextProxy'],
|
||||||
function (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.
|
* Selection proxy for telemetry elements in a fixed position view.
|
||||||
*
|
*
|
||||||
@ -45,24 +42,9 @@ define(
|
|||||||
function TelemetryProxy(element, index, elements, gridSize) {
|
function TelemetryProxy(element, index, elements, gridSize) {
|
||||||
var proxy = new TextProxy(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
|
// Expose the domain object identifier
|
||||||
proxy.id = element.id;
|
proxy.id = element.id;
|
||||||
|
|
||||||
// Expose initial toggle
|
|
||||||
proxy[element.titled ? HIDE : SHOW] = toggle;
|
|
||||||
|
|
||||||
// Don't expose text configuration
|
// Don't expose text configuration
|
||||||
delete proxy.text;
|
delete proxy.text;
|
||||||
|
|
||||||
|
@ -53,22 +53,14 @@ define(
|
|||||||
mockTimeSystem,
|
mockTimeSystem,
|
||||||
mockLimitEvaluator,
|
mockLimitEvaluator,
|
||||||
mockSelection,
|
mockSelection,
|
||||||
|
mockObjects,
|
||||||
|
mockNewDomainObject,
|
||||||
|
unlistenFunc,
|
||||||
$element = [],
|
$element = [],
|
||||||
selectable = [],
|
selectable = [],
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
// Utility function; find a watch for a given expression
|
// Utility function; find a $on calls 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
|
|
||||||
function findOn(expr) {
|
function findOn(expr) {
|
||||||
var on;
|
var on;
|
||||||
mockScope.$on.calls.forEach(function (call) {
|
mockScope.$on.calls.forEach(function (call) {
|
||||||
@ -82,7 +74,8 @@ define(
|
|||||||
function makeMockDomainObject(id) {
|
function makeMockDomainObject(id) {
|
||||||
return {
|
return {
|
||||||
identifier: {
|
identifier: {
|
||||||
key: "domainObject-" + id
|
key: "domainObject-" + id,
|
||||||
|
namespace: ""
|
||||||
},
|
},
|
||||||
name: "Point " + id
|
name: "Point " + id
|
||||||
};
|
};
|
||||||
@ -110,11 +103,6 @@ define(
|
|||||||
return "Formatted " + valueMetadata.value;
|
return "Formatted " + valueMetadata.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
mockDomainObject = jasmine.createSpyObj(
|
|
||||||
'domainObject',
|
|
||||||
['getId', 'getModel', 'getCapability', 'useCapability']
|
|
||||||
);
|
|
||||||
|
|
||||||
mockHandle = jasmine.createSpyObj(
|
mockHandle = jasmine.createSpyObj(
|
||||||
'subscription',
|
'subscription',
|
||||||
[
|
[
|
||||||
@ -172,16 +160,14 @@ define(
|
|||||||
]};
|
]};
|
||||||
|
|
||||||
mockChildren = testModel.composition.map(makeMockDomainObject);
|
mockChildren = testModel.composition.map(makeMockDomainObject);
|
||||||
mockCompositionCollection = jasmine.createSpyObj('compositionCollection',
|
mockCompositionCollection = jasmine.createSpyObj('compositionCollection', [
|
||||||
[
|
'load',
|
||||||
'load'
|
'on',
|
||||||
]
|
'off'
|
||||||
);
|
]);
|
||||||
mockCompositionAPI = jasmine.createSpyObj('composition',
|
mockCompositionAPI = jasmine.createSpyObj('composition', [
|
||||||
[
|
'get'
|
||||||
'get'
|
]);
|
||||||
]
|
|
||||||
);
|
|
||||||
mockCompositionAPI.get.andReturn(mockCompositionCollection);
|
mockCompositionAPI.get.andReturn(mockCompositionCollection);
|
||||||
mockCompositionCollection.load.andReturn(
|
mockCompositionCollection.load.andReturn(
|
||||||
Promise.resolve(mockChildren)
|
Promise.resolve(mockChildren)
|
||||||
@ -190,6 +176,24 @@ define(
|
|||||||
mockScope.model = testModel;
|
mockScope.model = testModel;
|
||||||
mockScope.configuration = testConfiguration;
|
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] = {
|
selectable[0] = {
|
||||||
context: {
|
context: {
|
||||||
oldItem: mockDomainObject
|
oldItem: mockDomainObject
|
||||||
@ -203,11 +207,19 @@ define(
|
|||||||
]);
|
]);
|
||||||
mockSelection.get.andReturn([]);
|
mockSelection.get.andReturn([]);
|
||||||
|
|
||||||
|
unlistenFunc = jasmine.createSpy("unlisten");
|
||||||
|
mockObjects = jasmine.createSpyObj('objects', [
|
||||||
|
'observe',
|
||||||
|
'get'
|
||||||
|
]);
|
||||||
|
mockObjects.observe.andReturn(unlistenFunc);
|
||||||
|
|
||||||
mockOpenMCT = {
|
mockOpenMCT = {
|
||||||
time: mockConductor,
|
time: mockConductor,
|
||||||
telemetry: mockTelemetryAPI,
|
telemetry: mockTelemetryAPI,
|
||||||
composition: mockCompositionAPI,
|
composition: mockCompositionAPI,
|
||||||
selection: mockSelection
|
selection: mockSelection,
|
||||||
|
objects: mockObjects
|
||||||
};
|
};
|
||||||
|
|
||||||
$element = $('<div></div>');
|
$element = $('<div></div>');
|
||||||
@ -251,76 +263,60 @@ define(
|
|||||||
mockOpenMCT,
|
mockOpenMCT,
|
||||||
$element
|
$element
|
||||||
);
|
);
|
||||||
|
spyOn(controller, "mutate");
|
||||||
findWatch("model.layoutGrid")(testModel.layoutGrid);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("subscribes when a domain object is available", function () {
|
it("subscribes a domain object", function () {
|
||||||
var dunzo = false;
|
var object = makeMockDomainObject("mock");
|
||||||
|
var done = false;
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
controller.getTelemetry(object).then(function () {
|
||||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
done = true;
|
||||||
dunzo = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return dunzo;
|
return done;
|
||||||
}, "Telemetry fetched", 200);
|
});
|
||||||
|
|
||||||
runs(function () {
|
runs(function () {
|
||||||
mockChildren.forEach(function (child) {
|
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(
|
||||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(
|
object,
|
||||||
child,
|
jasmine.any(Function),
|
||||||
jasmine.any(Function),
|
jasmine.any(Object)
|
||||||
jasmine.any(Object)
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("releases subscriptions when domain objects change", function () {
|
it("releases subscription when a domain objects is removed", function () {
|
||||||
var dunzo = false;
|
var done = false;
|
||||||
var unsubscribe = jasmine.createSpy('unsubscribe');
|
var unsubscribe = jasmine.createSpy('unsubscribe');
|
||||||
|
var object = makeMockDomainObject("mock");
|
||||||
|
|
||||||
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
||||||
|
controller.getTelemetry(object).then(function () {
|
||||||
mockScope.domainObject = mockDomainObject;
|
done = true;
|
||||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
|
||||||
dunzo = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return dunzo;
|
return done;
|
||||||
}, "Telemetry fetched", 200);
|
});
|
||||||
|
|
||||||
runs(function () {
|
runs(function () {
|
||||||
expect(unsubscribe).not.toHaveBeenCalled();
|
controller.onCompositionRemove(object.identifier);
|
||||||
|
|
||||||
dunzo = false;
|
|
||||||
|
|
||||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
|
||||||
dunzo = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return dunzo;
|
return unsubscribe.calls.length > 0;
|
||||||
}, "Telemetry fetched", 200);
|
|
||||||
|
|
||||||
runs(function () {
|
|
||||||
expect(unsubscribe.calls.length).toBe(mockChildren.length);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(unsubscribe).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes visible elements based on configuration", function () {
|
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.length).toEqual(3);
|
||||||
expect(elements[0].id).toEqual('a');
|
expect(elements[0].id).toEqual('a');
|
||||||
expect(elements[1].id).toEqual('b');
|
expect(elements[1].id).toEqual('b');
|
||||||
@ -328,9 +324,6 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows elements to be selected", function () {
|
it("allows elements to be selected", function () {
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||||
|
|
||||||
@ -338,12 +331,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows selection retrieval", function () {
|
it("allows selection retrieval", function () {
|
||||||
var elements;
|
var elements = controller.getElements();
|
||||||
|
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
elements = controller.getElements();
|
|
||||||
selectable[0].context.elementProxy = elements[1];
|
selectable[0].context.elementProxy = elements[1];
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||||
|
|
||||||
@ -351,16 +339,10 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("selects the parent view when selected element is removed", function () {
|
it("selects the parent view when selected element is removed", function () {
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
var elements = controller.getElements();
|
var elements = controller.getElements();
|
||||||
selectable[0].context.elementProxy = elements[1];
|
selectable[0].context.elementProxy = elements[1];
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||||
|
controller.remove(elements[1]);
|
||||||
elements[1].remove();
|
|
||||||
testModel.modified = 2;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
expect($element[0].click).toHaveBeenCalled();
|
expect($element[0].click).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -368,21 +350,13 @@ define(
|
|||||||
it("retains selections during refresh", function () {
|
it("retains selections during refresh", function () {
|
||||||
// Get elements; remove one of them; trigger refresh.
|
// Get elements; remove one of them; trigger refresh.
|
||||||
// Same element (at least by index) should still be selected.
|
// Same element (at least by index) should still be selected.
|
||||||
var elements;
|
var elements = controller.getElements();
|
||||||
|
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
elements = controller.getElements();
|
|
||||||
selectable[0].context.elementProxy = elements[1];
|
selectable[0].context.elementProxy = elements[1];
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||||
|
|
||||||
expect(controller.getSelectedElement()).toEqual(elements[1]);
|
expect(controller.getSelectedElement()).toEqual(elements[1]);
|
||||||
|
|
||||||
elements[2].remove();
|
controller.remove(elements[2]);
|
||||||
testModel.modified = 2;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
|
|
||||||
// Verify removal, as test assumes this
|
// Verify removal, as test assumes this
|
||||||
@ -408,7 +382,7 @@ define(
|
|||||||
controller.elementProxiesById['12345'] = [testElement];
|
controller.elementProxiesById['12345'] = [testElement];
|
||||||
controller.elementProxies = [testElement];
|
controller.elementProxies = [testElement];
|
||||||
|
|
||||||
controller.subscribeToObjects([telemetryObject]);
|
controller.subscribeToObject(telemetryObject);
|
||||||
mockTelemetryAPI.subscribe.mostRecentCall.args[1](mockTelemetry);
|
mockTelemetryAPI.subscribe.mostRecentCall.args[1](mockTelemetry);
|
||||||
|
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
@ -426,18 +400,13 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates elements styles when grid size changes", function () {
|
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;
|
// Change the grid size
|
||||||
mockScope.model = testModel;
|
controller.updateElementPositions([20, 20]);
|
||||||
findWatch("domainObject")(mockDomainObject);
|
|
||||||
findWatch("model.modified")(1);
|
expect(controller.getElements()[0].style.left).not.toEqual(originalLeft);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("listens for drop events", function () {
|
it("listens for drop events", function () {
|
||||||
@ -457,6 +426,9 @@ define(
|
|||||||
|
|
||||||
// Notify that a drop occurred
|
// Notify that a drop occurred
|
||||||
testModel.composition.push('d');
|
testModel.composition.push('d');
|
||||||
|
|
||||||
|
mockObjects.get.andReturn(Promise.resolve([]));
|
||||||
|
|
||||||
findOn('mctDrop')(
|
findOn('mctDrop')(
|
||||||
mockEvent,
|
mockEvent,
|
||||||
'd',
|
'd',
|
||||||
@ -468,11 +440,6 @@ define(
|
|||||||
|
|
||||||
// ...and prevented default...
|
// ...and prevented default...
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
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 () {
|
it("ignores drops when default has been prevented", function () {
|
||||||
@ -492,52 +459,35 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("unsubscribes when destroyed", function () {
|
it("unsubscribes when destroyed", function () {
|
||||||
|
var done = false;
|
||||||
var dunzo = false;
|
|
||||||
var unsubscribe = jasmine.createSpy('unsubscribe');
|
var unsubscribe = jasmine.createSpy('unsubscribe');
|
||||||
|
var object = makeMockDomainObject("mock");
|
||||||
|
|
||||||
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
controller.getTelemetry(object).then(function () {
|
||||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
done = true;
|
||||||
dunzo = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return dunzo;
|
return done;
|
||||||
}, "Telemetry fetched", 200);
|
});
|
||||||
|
|
||||||
runs(function () {
|
runs(function () {
|
||||||
expect(unsubscribe).not.toHaveBeenCalled();
|
expect(unsubscribe).not.toHaveBeenCalled();
|
||||||
// Destroy the scope
|
// Destroy the scope
|
||||||
findOn('$destroy')();
|
findOn('$destroy')();
|
||||||
|
|
||||||
//Check that the same unsubscribe function returned by the
|
expect(unsubscribe).toHaveBeenCalled();
|
||||||
expect(unsubscribe.calls.length).toBe(mockChildren.length);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes its grid size", function () {
|
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);
|
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 () {
|
it("exposes drag handles", function () {
|
||||||
var handles;
|
var handles;
|
||||||
|
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||||
|
|
||||||
@ -556,9 +506,6 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("exposes a move handle", function () {
|
it("exposes a move handle", function () {
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||||
|
|
||||||
@ -573,10 +520,6 @@ define(
|
|||||||
|
|
||||||
it("updates selection style during drag", function () {
|
it("updates selection style during drag", function () {
|
||||||
var oldStyle;
|
var oldStyle;
|
||||||
|
|
||||||
testModel.modified = 1;
|
|
||||||
findWatch("model.modified")(testModel.modified);
|
|
||||||
|
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
selectable[0].context.elementProxy = controller.getElements()[1];
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||||
|
|
||||||
@ -677,7 +620,7 @@ define(
|
|||||||
value: testValue
|
value: testValue
|
||||||
}]));
|
}]));
|
||||||
|
|
||||||
controller.fetchHistoricalData([mockTelemetryObject]);
|
controller.fetchHistoricalData(mockTelemetryObject);
|
||||||
|
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return controller.digesting === false;
|
return controller.digesting === false;
|
||||||
|
@ -28,8 +28,8 @@ define(
|
|||||||
|
|
||||||
describe("A fixed position drag handle", function () {
|
describe("A fixed position drag handle", function () {
|
||||||
var mockElementHandle,
|
var mockElementHandle,
|
||||||
mockUpdate,
|
mockConfigPath,
|
||||||
mockCommit,
|
mockFixedControl,
|
||||||
handle;
|
handle;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
@ -37,18 +37,23 @@ define(
|
|||||||
'elementHandle',
|
'elementHandle',
|
||||||
['x', 'y','getGridSize']
|
['x', 'y','getGridSize']
|
||||||
);
|
);
|
||||||
mockUpdate = jasmine.createSpy('update');
|
|
||||||
mockCommit = jasmine.createSpy('commit');
|
|
||||||
|
|
||||||
mockElementHandle.x.andReturn(6);
|
mockElementHandle.x.andReturn(6);
|
||||||
mockElementHandle.y.andReturn(8);
|
mockElementHandle.y.andReturn(8);
|
||||||
mockElementHandle.getGridSize.andReturn(TEST_GRID_SIZE);
|
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(
|
handle = new FixedDragHandle(
|
||||||
mockElementHandle,
|
mockElementHandle,
|
||||||
TEST_GRID_SIZE,
|
mockConfigPath,
|
||||||
mockUpdate,
|
mockFixedControl
|
||||||
mockCommit
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,13 +79,12 @@ define(
|
|||||||
expect(mockElementHandle.x).toHaveBeenCalledWith(5);
|
expect(mockElementHandle.x).toHaveBeenCalledWith(5);
|
||||||
expect(mockElementHandle.y).toHaveBeenCalledWith(7);
|
expect(mockElementHandle.y).toHaveBeenCalledWith(7);
|
||||||
|
|
||||||
// Should have called update once per continueDrag
|
// Should have called updateSelectionStyle once per continueDrag
|
||||||
expect(mockUpdate.calls.length).toEqual(2);
|
expect(mockFixedControl.updateSelectionStyle.calls.length).toEqual(2);
|
||||||
|
|
||||||
// Finally, ending drag should commit
|
// Finally, ending drag should mutate
|
||||||
expect(mockCommit).not.toHaveBeenCalled();
|
|
||||||
handle.endDrag();
|
handle.endDrag();
|
||||||
expect(mockCommit).toHaveBeenCalled();
|
expect(mockFixedControl.mutate).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -42,6 +42,8 @@ define(
|
|||||||
mockOpenMCT,
|
mockOpenMCT,
|
||||||
mockSelection,
|
mockSelection,
|
||||||
mockDomainObjectCapability,
|
mockDomainObjectCapability,
|
||||||
|
mockObjects,
|
||||||
|
unlistenFunc,
|
||||||
$element = [],
|
$element = [],
|
||||||
selectable = [];
|
selectable = [];
|
||||||
|
|
||||||
@ -77,14 +79,15 @@ define(
|
|||||||
if (param === 'composition') {
|
if (param === 'composition') {
|
||||||
return id !== 'b';
|
return id !== 'b';
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
type: "testType"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockScope = jasmine.createSpyObj(
|
mockScope = jasmine.createSpyObj(
|
||||||
"$scope",
|
"$scope",
|
||||||
["$watch", "$watchCollection", "$on", "commit"]
|
["$watch", "$watchCollection", "$on"]
|
||||||
);
|
);
|
||||||
mockEvent = jasmine.createSpyObj(
|
mockEvent = jasmine.createSpyObj(
|
||||||
'event',
|
'event',
|
||||||
@ -104,9 +107,13 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
unlistenFunc = jasmine.createSpy("unlisten");
|
||||||
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
||||||
['inEditContext']
|
['inEditContext', 'listen']
|
||||||
);
|
);
|
||||||
|
mockDomainObjectCapability.listen.andReturn(unlistenFunc);
|
||||||
|
|
||||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
||||||
@ -126,8 +133,14 @@ define(
|
|||||||
'get'
|
'get'
|
||||||
]);
|
]);
|
||||||
mockSelection.get.andReturn(selectable);
|
mockSelection.get.andReturn(selectable);
|
||||||
|
|
||||||
|
mockObjects = jasmine.createSpyObj('objects', [
|
||||||
|
'get'
|
||||||
|
]);
|
||||||
|
mockObjects.get.andReturn(mockPromise(mockDomainObject("mockObject")));
|
||||||
mockOpenMCT = {
|
mockOpenMCT = {
|
||||||
selection: mockSelection
|
selection: mockSelection,
|
||||||
|
objects: mockObjects
|
||||||
};
|
};
|
||||||
|
|
||||||
$element = $('<div></div>');
|
$element = $('<div></div>');
|
||||||
@ -138,6 +151,7 @@ define(
|
|||||||
|
|
||||||
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
||||||
spyOn(controller, "layoutPanels").andCallThrough();
|
spyOn(controller, "layoutPanels").andCallThrough();
|
||||||
|
spyOn(controller, "commit");
|
||||||
|
|
||||||
jasmine.Clock.useMock();
|
jasmine.Clock.useMock();
|
||||||
});
|
});
|
||||||
@ -270,10 +284,7 @@ define(
|
|||||||
controller.continueDrag([100, 100]);
|
controller.continueDrag([100, 100]);
|
||||||
controller.endDrag();
|
controller.endDrag();
|
||||||
|
|
||||||
// Should have triggered commit (provided by
|
expect(controller.commit).toHaveBeenCalled();
|
||||||
// EditRepresenter) with some message.
|
|
||||||
expect(mockScope.commit)
|
|
||||||
.toHaveBeenCalledWith(jasmine.any(String));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("listens for drop events", function () {
|
it("listens for drop events", function () {
|
||||||
@ -296,11 +307,7 @@ define(
|
|||||||
);
|
);
|
||||||
expect(testConfiguration.panels.d).toBeDefined();
|
expect(testConfiguration.panels.d).toBeDefined();
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||||
|
expect(controller.commit).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 () {
|
it("ignores drops when default has been prevented", function () {
|
||||||
@ -340,13 +347,17 @@ define(
|
|||||||
testModel.layoutGrid = [1, 1];
|
testModel.layoutGrid = [1, 1];
|
||||||
mockScope.$watch.calls[0].args[1](testModel.layoutGrid);
|
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
|
// Notify that a drop occurred
|
||||||
mockScope.$on.mostRecentCall.args[1](
|
mockScope.$on.mostRecentCall.args[1](
|
||||||
mockEvent,
|
mockEvent,
|
||||||
'd',
|
'd',
|
||||||
{ x: 300, y: 100 }
|
{ x: 300, y: 100 }
|
||||||
);
|
);
|
||||||
mockScope.$watch.calls[0].args[1](['d']);
|
|
||||||
|
|
||||||
style = controller.getFrameStyle("d");
|
style = controller.getFrameStyle("d");
|
||||||
|
|
||||||
@ -415,30 +426,6 @@ define(
|
|||||||
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
|
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 () {
|
it("selects the parent object when selected object is removed", function () {
|
||||||
mockScope.$watchCollection.mostRecentCall.args[1]();
|
mockScope.$watchCollection.mostRecentCall.args[1]();
|
||||||
var childObj = mockCompositionObjects[0];
|
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 () {
|
it("allows order to be changed", function () {
|
||||||
proxy.order("down");
|
proxy.order("down");
|
||||||
expect(testElements).toEqual([{}, testElement, {}, {}]);
|
expect(testElements).toEqual([{}, testElement, {}, {}]);
|
||||||
|
@ -26,7 +26,9 @@ define(
|
|||||||
|
|
||||||
describe("A fixed position drag handle", function () {
|
describe("A fixed position drag handle", function () {
|
||||||
var testElement,
|
var testElement,
|
||||||
handle;
|
mockElementProxy,
|
||||||
|
handle,
|
||||||
|
TEST_GRID_SIZE = [45, 21];
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testElement = {
|
testElement = {
|
||||||
@ -36,8 +38,10 @@ define(
|
|||||||
y2: 11,
|
y2: 11,
|
||||||
useGrid: true
|
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 () {
|
it("provides x/y grid coordinates for its corner", function () {
|
||||||
@ -69,7 +73,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns the correct grid size", function () {
|
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 () {
|
it("adjusts both ends when mutating x", function () {
|
||||||
var proxy = new LineProxy(diagonal);
|
var proxy = new LineProxy(diagonal);
|
||||||
proxy.x(6);
|
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 () {
|
it("adjusts both ends when mutating y", function () {
|
||||||
var proxy = new LineProxy(diagonal);
|
var proxy = new LineProxy(diagonal);
|
||||||
proxy.y(6);
|
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 () {
|
it("provides internal positions for SVG lines", function () {
|
||||||
|
@ -25,10 +25,12 @@ define(
|
|||||||
function (ResizeHandle) {
|
function (ResizeHandle) {
|
||||||
|
|
||||||
var TEST_MIN_WIDTH = 4,
|
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 () {
|
describe("A fixed position drag handle", function () {
|
||||||
var testElement,
|
var testElement,
|
||||||
|
mockElementProxy,
|
||||||
handle;
|
handle;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
@ -39,12 +41,18 @@ define(
|
|||||||
height: 36,
|
height: 36,
|
||||||
useGrid: true
|
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(
|
handle = new ResizeHandle(
|
||||||
testElement,
|
mockElementProxy,
|
||||||
TEST_MIN_WIDTH,
|
testElement
|
||||||
TEST_MIN_HEIGHT,
|
|
||||||
[34,81]
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,7 +85,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns the correct grid size", function () {
|
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 () {
|
it("exposes the element's id", function () {
|
||||||
expect(proxy.id).toEqual('test-id');
|
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,30 +21,29 @@
|
|||||||
-->
|
-->
|
||||||
<form novalidate>
|
<form novalidate>
|
||||||
<div class="tool-bar btn-bar contents abs">
|
<div class="tool-bar btn-bar contents abs">
|
||||||
<span ng-repeat="section in structure.sections"
|
<span ng-repeat="item in structure">
|
||||||
class="l-control-group"
|
<span ng-if="item.control === 'divider'" class="l-control-group">
|
||||||
ng-if="!section.hidden"
|
</span>
|
||||||
title="{{section.description}}">
|
<ng-form ng-class="{ 'input-labeled': item.name }"
|
||||||
<ng-form ng-repeat="item in section.items"
|
ng-hide="item.hidden"
|
||||||
ng-class="{ 'input-labeled': item.name }"
|
ng-if="item.control !== 'divider'"
|
||||||
ng-hide="item.hidden"
|
class="inline"
|
||||||
class="inline"
|
title="{{item.description}}"
|
||||||
title="{{item.description}}"
|
name="mctFormInner">
|
||||||
name="mctFormInner">
|
|
||||||
|
|
||||||
<label ng-if="item.name">
|
<label ng-if="item.name">
|
||||||
{{item.name}}:
|
{{item.name}}:
|
||||||
</label>
|
</label>
|
||||||
<mct-control key="item.control"
|
<mct-control key="item.control"
|
||||||
ng-class="{ disabled: item.disabled }"
|
ng-class="{ disabled: item.disabled }"
|
||||||
ng-model="ngModel"
|
ng-model="ngModel"
|
||||||
ng-required="item.required"
|
ng-required="item.required"
|
||||||
ng-pattern="getRegExp(item.pattern)"
|
ng-pattern="getRegExp(item.pattern)"
|
||||||
options="item.options"
|
options="item.options"
|
||||||
structure="item"
|
structure="item"
|
||||||
field="item.key">
|
field="item.key">
|
||||||
</mct-control>
|
</mct-control>
|
||||||
</ng-form>
|
</ng-form>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -24,8 +24,16 @@
|
|||||||
* Module defining MCTForm. Created by vwoeltje on 11/10/14.
|
* Module defining MCTForm. Created by vwoeltje on 11/10/14.
|
||||||
*/
|
*/
|
||||||
define(
|
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
|
* The mct-toolbar directive allows generation of displayable
|
||||||
@ -35,7 +43,7 @@ define(
|
|||||||
* This directive accepts three attributes:
|
* This directive accepts three attributes:
|
||||||
*
|
*
|
||||||
* * `ng-model`: The model for the form; where user input
|
* * `ng-model`: The model for the form; where user input
|
||||||
* where be stored.
|
* will be stored.
|
||||||
* * `structure`: The declarative structure of the toolbar.
|
* * `structure`: The declarative structure of the toolbar.
|
||||||
* Describes what controls should be shown and where
|
* Describes what controls should be shown and where
|
||||||
* their values should be read/written in the model.
|
* their values should be read/written in the model.
|
||||||
@ -49,9 +57,10 @@ define(
|
|||||||
*/
|
*/
|
||||||
function MCTToolbar() {
|
function MCTToolbar() {
|
||||||
// Use Directive Definition Object from mct-form,
|
// 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();
|
var ddo = new MCTForm();
|
||||||
ddo.template = toolbarTemplate;
|
ddo.template = toolbarTemplate;
|
||||||
|
ddo.controller = ['$scope', 'openmct', ToolbarController];
|
||||||
return ddo;
|
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 () {
|
describe("The mct-toolbar directive", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
|
mockOpenMCT,
|
||||||
|
mockSelection,
|
||||||
mctToolbar;
|
mctToolbar;
|
||||||
|
|
||||||
function installController() {
|
function installController() {
|
||||||
var Controller = mctToolbar.controller[1];
|
var Controller = mctToolbar.controller[2];
|
||||||
return new Controller(mockScope);
|
return new Controller(mockScope, mockOpenMCT);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
|
mockScope = jasmine.createSpyObj("$scope", [
|
||||||
|
"$watch",
|
||||||
|
"$on"
|
||||||
|
]);
|
||||||
mockScope.$parent = {};
|
mockScope.$parent = {};
|
||||||
|
mockSelection = jasmine.createSpyObj("selection", [
|
||||||
|
'on',
|
||||||
|
'off'
|
||||||
|
]);
|
||||||
|
mockOpenMCT = {
|
||||||
|
selection: mockSelection
|
||||||
|
};
|
||||||
mctToolbar = new MCTToolbar();
|
mctToolbar = new MCTToolbar();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -43,29 +55,15 @@ define(
|
|||||||
expect(mctToolbar.restrict).toEqual("E");
|
expect(mctToolbar.restrict).toEqual("E");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("watches for changes in form by name", function () {
|
it("listens for selection change event", 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.
|
|
||||||
installController();
|
installController();
|
||||||
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
||||||
"mctForm",
|
"change",
|
||||||
jasmine.any(Function)
|
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 () {
|
it("allows strings to be converted to RegExps", function () {
|
||||||
// This is needed to support ng-pattern in the template
|
// This is needed to support ng-pattern in the template
|
||||||
installController();
|
installController();
|
||||||
|
18
src/MCT.js
18
src/MCT.js
@ -29,7 +29,8 @@ define([
|
|||||||
'./api/objects/object-utils',
|
'./api/objects/object-utils',
|
||||||
'./plugins/plugins',
|
'./plugins/plugins',
|
||||||
'./ui/ViewRegistry',
|
'./ui/ViewRegistry',
|
||||||
'./ui/InspectorViewRegistry'
|
'./ui/InspectorViewRegistry',
|
||||||
|
'./ui/ToolbarRegistry'
|
||||||
], function (
|
], function (
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
legacyRegistry,
|
legacyRegistry,
|
||||||
@ -39,7 +40,8 @@ define([
|
|||||||
objectUtils,
|
objectUtils,
|
||||||
plugins,
|
plugins,
|
||||||
ViewRegistry,
|
ViewRegistry,
|
||||||
InspectorViewRegistry
|
InspectorViewRegistry,
|
||||||
|
ToolbarRegistry
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Open MCT is an extensible web application for building mission
|
* Open MCT is an extensible web application for building mission
|
||||||
@ -76,7 +78,7 @@ define([
|
|||||||
* Tracks current selection state of the application.
|
* Tracks current selection state of the application.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.selection = new Selection();
|
this.selection = new Selection(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCT's time conductor, which may be used to synchronize view contents
|
* 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
|
* 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
|
* @type {module:openmct.ToolbarRegistry}
|
||||||
* providers should be prepared to test arbitrary objects for
|
|
||||||
* viewability.
|
|
||||||
*
|
|
||||||
* @type {module:openmct.ViewRegistry}
|
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name toolbars
|
* @name toolbars
|
||||||
*/
|
*/
|
||||||
this.toolbars = new ViewRegistry();
|
this.toolbars = new ToolbarRegistry();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for domain object types which may exist within this
|
* Registry for domain object types which may exist within this
|
||||||
|
@ -76,12 +76,20 @@ define([
|
|||||||
* @memberof module:openmct.MutableObject#
|
* @memberof module:openmct.MutableObject#
|
||||||
*/
|
*/
|
||||||
MutableObject.prototype.set = function (path, value) {
|
MutableObject.prototype.set = function (path, value) {
|
||||||
|
|
||||||
_.set(this.object, path, value);
|
_.set(this.object, path, value);
|
||||||
_.set(this.object, 'modified', Date.now());
|
_.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
|
//Emit event specific to property
|
||||||
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
|
this.eventEmitter.emit(qualifiedEventName(this.object, path), value);
|
||||||
|
|
||||||
|
this.eventEmitter.off(qualifiedEventName(this.object, '*'), handleRecursiveMutation);
|
||||||
|
|
||||||
//Emit wildcare event
|
//Emit wildcare event
|
||||||
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
this.eventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
||||||
|
|
||||||
|
@ -26,8 +26,10 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
* Manages selection state for Open MCT
|
* Manages selection state for Open MCT
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function Selection() {
|
function Selection(openmct) {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
this.openmct = openmct;
|
||||||
this.selected = [];
|
this.selected = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +101,12 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
* Attaches the click handlers to the element.
|
* Attaches the click handlers to the element.
|
||||||
*
|
*
|
||||||
* @param element an html 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
|
* @param select a flag to select the element if true
|
||||||
* @returns a function that removes the click handlers from the element
|
* @returns a function that removes the click handlers from the element
|
||||||
* @public
|
* @public
|
||||||
@ -114,6 +121,12 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
element.addEventListener('click', capture, true);
|
element.addEventListener('click', capture, true);
|
||||||
element.addEventListener('click', selectCapture);
|
element.addEventListener('click', selectCapture);
|
||||||
|
|
||||||
|
if (context.item) {
|
||||||
|
var unlisten = this.openmct.objects.observe(context.item, "*", function (newItem) {
|
||||||
|
context.item = newItem;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (select) {
|
if (select) {
|
||||||
element.click();
|
element.click();
|
||||||
}
|
}
|
||||||
@ -121,6 +134,10 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
return function () {
|
return function () {
|
||||||
element.removeEventListener('click', capture);
|
element.removeEventListener('click', capture);
|
||||||
element.removeEventListener('click', selectCapture);
|
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