[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:
Pegah Sarram
2018-06-27 13:30:01 -07:00
committed by Andrew Henry
parent de8f8d174d
commit 73e38f1955
39 changed files with 1400 additions and 1844 deletions

View File

@ -39,240 +39,309 @@ define([
"cssClass": "icon-box-with-dashed-lines",
"type": "telemetry.fixed",
"template": fixedTemplate,
"uses": [
"composition"
],
"editable": true,
"toolbar": {
"sections": [
"uses": [],
"editable": true
}
],
"toolbars": [
{
name: "Fixed Position Toolbar",
key: "fixed.position",
description: "Toolbar for the selected element inside a fixed position display.",
forSelection: function (selection) {
if (!selection) {
return;
}
return (
selection[0] && selection[0].context.elementProxy &&
selection[1] && selection[1].context.item.type === 'telemetry.fixed' ||
selection[0] && selection[0].context.item.type === 'telemetry.fixed'
);
},
toolbar: function (selection) {
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"];
var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"];
var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"];
var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"];
var fixedPageProperties = ["add"];
var properties = [],
fixedItem = selection[0] && selection[0].context.item,
elementProxy = selection[0] && selection[0].context.elementProxy,
domainObject = selection[1] && selection[1].context.item,
path;
if (elementProxy) {
var type = elementProxy.element.type;
path = "configuration['fixed-display'].elements[" + elementProxy.index + "]";
properties =
type === 'fixed.image' ? imageProperties :
type === 'fixed.text' ? textProperties :
type === 'fixed.box' ? boxProperties :
type === 'fixed.line' ? lineProperties :
type === 'fixed.telemetry' ? telemetryProperties : [];
} else if (fixedItem) {
properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties;
}
return [
{
"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",
"cssClass": "icon-plus",
"control": "menu-button",
"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"
}
]
"name": "Box",
"cssClass": "icon-box",
"key": "fixed.box"
},
{
"property": "fill",
"cssClass": "icon-paint-bucket",
"title": "Fill color",
"description": "Set fill color",
"control": "color"
},
{
"property": "stroke",
"name": "Line",
"cssClass": "icon-line-horz",
"title": "Border color",
"description": "Set border color",
"control": "color"
"key": "fixed.line"
},
{
"property": "url",
"cssClass": "icon-image",
"control": "dialog-button",
"title": "Image Properties",
"description": "Edit image properties",
"dialog": {
"control": "textfield",
"name": "Image URL",
"cssClass": "l-input-lg",
"required": true
}
}
]
},
{
"items": [
{
"property": "color",
"name": "Text",
"cssClass": "icon-T",
"title": "Text color",
"description": "Set text color",
"mandatory": true,
"control": "color"
"key": "fixed.text"
},
{
"property": "size",
"title": "Text size",
"description": "Set text size",
"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" };
})
"name": "Image",
"cssClass": "icon-image",
"key": "fixed.image"
}
]
},
{
"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",
"text": "X",
"name": "X",
"cssClass": "l-input-sm",
"control": "numberfield",
"min": "0"
"name": "Move to Top",
"cssClass": "icon-arrow-double-up",
"key": "top"
},
{
"property": "editY",
"text": "Y",
"name": "Y",
"cssClass": "l-input-sm",
"control": "numberfield",
"min": "0"
"name": "Move Up",
"cssClass": "icon-arrow-up",
"key": "up"
},
{
"property": "editX1",
"text": "X1",
"name": "X1",
"cssClass": "l-input-sm",
"control" : "numberfield",
"min": "0"
"name": "Move Down",
"cssClass": "icon-arrow-down",
"key": "down"
},
{
"property": "editY1",
"text": "Y1",
"name": "Y1",
"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"
"name": "Move to Bottom",
"cssClass": "icon-arrow-double-down",
"key": "bottom"
}
]
},
{
"items": [
{
"property": "text",
"cssClass": "icon-gear",
"control": "dialog-button",
"title": "Text Properties",
"description": "Edit text properties",
"dialog": {
"control": "textfield",
"name": "Text",
"required": true
}
},
{
"method": "showTitle",
"cssClass": "icon-two-parts-both",
"control": "button",
"title": "Show title",
"description": "Show telemetry element title"
},
{
"method": "hideTitle",
"cssClass": "icon-two-parts-one-only",
"control": "button",
"title": "Hide title",
"description": "Hide telemetry element title"
}
]
control: "color",
domainObject: domainObject,
property: path + ".fill",
cssClass: "icon-paint-bucket",
title: "Fill color",
description: "Set fill color",
key: 'fill'
},
{
"items": [
{
"method": "remove",
"control": "button",
"cssClass": "icon-trash"
}
]
control: "color",
domainObject: domainObject,
property: path + ".stroke",
cssClass: "icon-line-horz",
title: "Border color",
description: "Set border color",
key: 'stroke'
},
{
control: "dialog-button",
domainObject: domainObject,
property: path + ".url",
cssClass: "icon-image",
title: "Image Properties",
description: "Edit image properties",
key: 'url',
dialog: {
control: "textfield",
name: "Image URL",
cssClass: "l-input-lg",
required: true
}
},
{
control: "color",
domainObject: domainObject,
property: path + ".color",
cssClass: "icon-T",
title: "Text color",
mandatory: true,
description: "Set text color",
key: 'color'
},
{
control: "select",
domainObject: domainObject,
property: path + ".size",
title: "Text size",
description: "Set text size",
"options": [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
return { "name": size + " px", "value": size + "px" };
}),
key: 'size'
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x",
text: "X",
name: "X",
key: "x",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y",
text: "Y",
name: "Y",
key: "y",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x",
text: "X1",
name: "X1",
key: "x1",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y",
text: "Y1",
name: "Y1",
key: "y1",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".x2",
text: "X2",
name: "X2",
key: "x2",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".y2",
text: "Y2",
name: "Y2",
key: "y2",
cssClass: "l-input-sm",
min: "0"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".height",
text: "H",
name: "H",
key: "height",
cssClass: "l-input-sm",
description: "Resize object height",
min: "1"
},
{
control: "numberfield",
domainObject: domainObject,
property: path + ".width",
text: "W",
name: "W",
key: "width",
cssClass: "l-input-sm",
description: "Resize object width",
min: "1"
},
{
control: "checkbox",
domainObject: domainObject,
property: path + ".useGrid",
name: "Snap to Grid",
key: "useGrid"
},
{
control: "dialog-button",
domainObject: domainObject,
property: path + ".text",
cssClass: "icon-gear",
title: "Text Properties",
description: "Edit text properties",
key: "text",
dialog: {
control: "textfield",
name: "Text",
required: true
}
},
{
control: "checkbox",
domainObject: domainObject,
property: path + ".titled",
name: "Show Title",
key: "titled"
},
{
control: "button",
domainObject: domainObject,
method: function () {
selection[0].context.fixedController.remove(
selection[0].context.elementProxy
);
},
key: "remove",
cssClass: "icon-trash"
}
]
].filter(function (item) {
var filtered;
properties.forEach(function (property) {
if (item.property && item.key === property ||
item.method && item.key === property) {
filtered = item;
}
});
return filtered;
});
}
}
],

View File

@ -62,29 +62,7 @@ define([
"type": "layout",
"template": layoutTemplate,
"editable": true,
"uses": [],
"toolbar": {
"sections": [
{
"items": [
{
"method": "showFrame",
"cssClass": "icon-frame-show",
"control": "button",
"title": "Show frame",
"description": "Show frame"
},
{
"method": "hideFrame",
"cssClass": "icon-frame-hide",
"control": "button",
"title": "Hide frame",
"description": "Hide frame"
}
]
}
]
}
"uses": []
},
{
"key": "fixed",
@ -305,6 +283,27 @@ define([
"implementation": LayoutCompositionPolicy
}
],
"toolbars": [
{
name: "Display Layout Toolbar",
key: "layout",
description: "A toolbar for objects inside a display layout.",
forSelection: function (selection) {
// Apply the layout toolbar if the selected object is inside a layout.
return (selection && selection[1] && selection[1].context.item.type === 'layout');
},
toolbar: function (selection) {
return [
{
control: "checkbox",
name: "Show frame",
domainObject: selection[1].context.item,
property: "configuration.layout.panels[" + selection[0].context.oldItem.id + "].hasFrame"
}
];
}
}
],
"types": [
{
"key": "layout",
@ -314,7 +313,14 @@ define([
"priority": 900,
"features": "creation",
"model": {
"composition": []
"composition": [],
configuration: {
layout: {
panels: {
}
}
}
},
"properties": [
{

View File

@ -41,7 +41,7 @@
<mct-include key="element.template"
parameters="{ gridSize: controller.getGridSize() }"
ng-model="element">
</mct-include>
</mct-include>
</div>
<!-- Selection highlight, handles -->
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">

View File

@ -41,7 +41,7 @@
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
ng-repeat="childObject in composition"
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
mct-selectable="controller.getContext(childObject, true)"
mct-selectable="controller.getContext(childObject)"
ng-dblclick="controller.drill($event, childObject)"
ng-style="controller.getFrameStyle(childObject.getId())">

View File

@ -38,6 +38,24 @@ define(
var DEFAULT_DIMENSIONS = [2, 1];
// Convert from element x/y/width/height to an
// appropriate ng-style argument, to position elements.
function convertPosition(elementProxy) {
if (elementProxy.getStyle) {
return elementProxy.getStyle();
}
var gridSize = elementProxy.getGridSize();
// Multiply position/dimensions by grid size
return {
left: (gridSize[0] * elementProxy.element.x) + 'px',
top: (gridSize[1] * elementProxy.element.y) + 'px',
width: (gridSize[0] * elementProxy.element.width) + 'px',
height: (gridSize[1] * elementProxy.element.height) + 'px'
};
}
/**
* The FixedController is responsible for supporting the
* Fixed Position view. It arranges frames according to saved
@ -51,14 +69,14 @@ define(
this.names = {}; // Cache names by ID
this.values = {}; // Cache values by ID
this.elementProxiesById = {};
this.telemetryObjects = [];
this.subscriptions = [];
this.telemetryObjects = {};
this.subscriptions = {};
this.openmct = openmct;
this.$element = $element;
this.$scope = $scope;
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
this.dialogService = dialogService;
this.$q = $q;
this.newDomainObject = $scope.domainObject.useCapability('adapter');
this.fixedViewSelectable = false;
var self = this;
@ -67,59 +85,13 @@ define(
'fetchHistoricalData',
'getTelemetry',
'setDisplayedValue',
'subscribeToObjects',
'subscribeToObject',
'unsubscribe',
'updateView'
].forEach(function (name) {
self[name] = self[name].bind(self);
});
// Convert from element x/y/width/height to an
// appropriate ng-style argument, to position elements.
function convertPosition(elementProxy) {
var gridSize = elementProxy.getGridSize();
// Multiply position/dimensions by grid size
return {
left: (gridSize[0] * elementProxy.x()) + 'px',
top: (gridSize[1] * elementProxy.y()) + 'px',
width: (gridSize[0] * elementProxy.width()) + 'px',
height: (gridSize[1] * elementProxy.height()) + 'px'
};
}
// Update the style for a selected element
function updateSelectionStyle() {
if (self.selectedElementProxy) {
self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
}
}
// Generate a specific drag handle
function generateDragHandle(elementHandle) {
return new FixedDragHandle(
elementHandle,
self.gridSize,
updateSelectionStyle,
$scope.commit
);
}
// Generate drag handles for an element
function generateDragHandles(element) {
return element.handles().map(generateDragHandle);
}
// Update element positions when grid size changes
function updateElementPositions(layoutGrid) {
// Update grid size from model
self.gridSize = layoutGrid;
self.elementProxies.forEach(function (elementProxy) {
elementProxy.setGridSize(self.gridSize);
elementProxy.style = convertPosition(elementProxy);
});
}
// Decorate an element for display
function makeProxyElement(element, index, elements) {
var ElementProxy = ElementProxies[element.type],
@ -137,14 +109,14 @@ define(
// Decorate elements in the current configuration
function refreshElements() {
var elements = (($scope.configuration || {}).elements || []);
var elements = (((self.newDomainObject.configuration || {})['fixed-display'] || {}).elements || []);
// Create the new proxies...
self.elementProxies = elements.map(makeProxyElement);
// If selection is not in array, select parent.
// Otherwise, set the element to select after refresh.
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);
if (index === -1) {
self.$element[0].click();
@ -168,79 +140,53 @@ define(
});
}
function removeObjects(ids) {
var configuration = self.$scope.configuration;
if (configuration &&
configuration.elements) {
configuration.elements = configuration.elements.filter(function (proxy) {
return ids.indexOf(proxy.id) === -1;
});
}
self.getTelemetry($scope.domainObject);
refreshElements();
// Mark change as persistable
if (self.$scope.commit) {
self.$scope.commit("Objects removed.");
}
}
// Handle changes in the object's composition
function updateComposition(composition, previousComposition) {
var removedIds = [];
// Resubscribe - objects in view have changed
if (composition !== previousComposition) {
//remove any elements no longer in the composition
removedIds = _.difference(previousComposition, composition);
if (removedIds.length > 0) {
removeObjects(removedIds);
}
}
}
// Trigger a new query for telemetry data
function updateDisplayBounds(bounds, isTick) {
if (!isTick) {
//Reset values
self.values = {};
refreshElements();
//Fetch new data
self.fetchHistoricalData(self.telemetryObjects);
Object.values(self.telemetryObjects).forEach(function (object) {
self.fetchHistoricalData(object);
});
}
}
// Add an element to this view
function addElement(element) {
// Ensure that configuration field is populated
$scope.configuration = $scope.configuration || {};
// Make sure there is a "elements" field in the
// view configuration.
$scope.configuration.elements =
$scope.configuration.elements || [];
// Store the position of this element.
$scope.configuration.elements.push(element);
var index;
var elements = (((self.newDomainObject.configuration || {})['fixed-display'] || {}).elements || []);
elements.push(element);
self.elementToSelectAfterRefresh = element;
// Refresh displayed elements
refreshElements();
// Mark change as persistable
if ($scope.commit) {
$scope.commit("Dropped an element.");
if (self.selectedElementProxy) {
index = elements.indexOf(self.selectedElementProxy.element);
}
self.mutate("configuration['fixed-display'].elements", elements);
elements = (self.newDomainObject.configuration)['fixed-display'].elements || [];
self.elementToSelectAfterRefresh = elements[elements.length - 1];
if (self.selectedElementProxy) {
// Update the selected element with the new
// value since newDomainOject is mutated.
self.selectedElementProxy.element = elements[index];
}
refreshElements();
}
// Position a panel after a drop event
function handleDrop(e, id, position) {
// Don't handle this event if it has already been handled
// color is set to "" to let the CSS theme determine the default color
if (e.defaultPrevented) {
return;
}
e.preventDefault();
// Store the position of this element.
// color is set to "" to let the CSS theme determine the default color
addElement({
type: "fixed.telemetry",
x: Math.floor(position.x / self.gridSize[0]),
@ -254,71 +200,229 @@ define(
useGrid: true
});
//Re-initialize objects, and subscribe to new object
self.getTelemetry($scope.domainObject);
}
// 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;
}
// Subscribe to the new object to get telemetry
self.openmct.objects.get(id).then(function (object) {
self.getTelemetry(object);
});
}
this.elementProxies = [];
this.generateDragHandle = generateDragHandle;
this.generateDragHandles = generateDragHandles;
this.updateSelectionStyle = updateSelectionStyle;
this.addElement = addElement;
this.refreshElements = refreshElements;
this.fixedProxy = new FixedProxy(this.addElement, this.$q, this.dialogService);
// Detect changes to grid size
$scope.$watch("model.layoutGrid", updateElementPositions);
this.composition = this.openmct.composition.get(this.newDomainObject);
this.composition.on('add', this.onCompositionAdd, this);
this.composition.on('remove', this.onCompositionRemove, this);
this.composition.load();
// Position panes where they are dropped
$scope.$on("mctDrop", handleDrop);
// Position panes when the model field changes
$scope.$watch("model.composition", updateComposition);
// Refresh list of elements whenever model changes
$scope.$watch("model.modified", refreshElements);
// Subscribe to telemetry when an object is available
$scope.$watch("domainObject", this.getTelemetry);
// Free up subscription on destroy
$scope.$on("$destroy", function () {
self.unsubscribe();
self.openmct.time.off("bounds", updateDisplayBounds);
self.openmct.selection.off("change", setSelection);
});
$scope.$on("$destroy", this.destroy.bind(this));
// Respond to external bounds changes
this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', setSelection);
this.$element.on('click', this.bypassSelection.bind(this));
setSelection(this.openmct.selection.get());
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
* @private
@ -340,31 +444,30 @@ define(
* @private
*/
FixedController.prototype.unsubscribe = function () {
this.subscriptions.forEach(function (unsubscribeFunc) {
Object.values(this.subscriptions).forEach(function (unsubscribeFunc) {
unsubscribeFunc();
});
this.subscriptions = [];
this.telemetryObjects = [];
this.subscriptions = {};
this.telemetryObjects = {};
};
/**
* Subscribe to all given domain objects
* Subscribe to the given domain object
* @private
* @param {object[]} objects Domain objects to subscribe to
* @returns {object[]} The provided objects, for chaining.
* @param {object} object Domain object to subscribe to
* @returns {object} The provided object, for chaining.
*/
FixedController.prototype.subscribeToObjects = function (objects) {
FixedController.prototype.subscribeToObject = function (object) {
var self = this;
var timeAPI = this.openmct.time;
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 self.openmct.telemetry.subscribe(object, function (datum) {
if (timeAPI.clock() !== undefined) {
self.updateView(object, datum);
}
}, {});
});
return objects;
return object;
};
/**
@ -416,23 +519,22 @@ define(
};
/**
* Request the last historical data point for the given domain objects
* @param {object[]} objects
* @returns {object[]} the provided objects for chaining.
* Request the last historical data point for the given domain object
* @param {object} object
* @returns {object} the provided object for chaining.
*/
FixedController.prototype.fetchHistoricalData = function (objects) {
FixedController.prototype.fetchHistoricalData = function (object) {
var bounds = this.openmct.time.bounds();
var self = this;
objects.forEach(function (object) {
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
.then(function (data) {
if (data.length > 0) {
self.updateView(object, data[data.length - 1]);
}
});
});
return objects;
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
.then(function (data) {
if (data.length > 0) {
self.updateView(object, data[data.length - 1]);
}
});
return object;
};
@ -457,33 +559,25 @@ define(
};
FixedController.prototype.getTelemetry = function (domainObject) {
var newObject = domainObject.useCapability('adapter');
var self = this;
var id = objectUtils.makeKeyString(domainObject.identifier);
if (this.subscriptions.length > 0) {
this.unsubscribe();
if (this.subscriptions[id]) {
this.subscriptions[id]();
delete this.subscriptions[id];
}
delete this.telemetryObjects[id];
if (!this.openmct.telemetry.isTelemetryObject(domainObject)) {
return;
}
function filterForTelemetryObjects(objects) {
return objects.filter(function (object) {
return self.openmct.telemetry.isTelemetryObject(object);
});
}
// Initialize display
this.telemetryObjects[id] = domainObject;
this.setDisplayedValue(domainObject, "");
function initializeDisplay(objects) {
self.telemetryObjects = objects;
objects.forEach(function (object) {
// Initialize values
self.setDisplayedValue(object, "");
});
return objects;
}
return this.openmct.composition.get(newObject).load()
.then(filterForTelemetryObjects)
.then(initializeDisplay)
return Promise.resolve(domainObject)
.then(this.fetchHistoricalData)
.then(this.subscribeToObjects);
.then(this.subscribeToObject);
};
/**
@ -580,12 +674,12 @@ define(
* Gets the selection context.
*
* @param elementProxy the element proxy
* @returns {object} the context object which includes elementProxy and toolbar
* @returns {object} the context object which includes elementProxy
*/
FixedController.prototype.getContext = function (elementProxy) {
return {
elementProxy: elementProxy,
toolbar: elementProxy
fixedController: this
};
};
@ -608,6 +702,10 @@ define(
}
};
FixedController.prototype.mutate = function (path, value) {
this.openmct.objects.mutate(this.newDomainObject, path, value);
};
return FixedController;
}
);

View File

@ -24,30 +24,34 @@ define(
[],
function () {
// Drag handle dimensions
var DRAG_HANDLE_SIZE = [6, 6];
/**
* Template-displayable drag handle for an element in fixed
* position mode.
*
* @param elementHandle the element handle
* @param configPath the configuration path of an element
* @param {Object} fixedControl the fixed controller
* @memberof platform/features/layout
* @constructor
*/
function FixedDragHandle(elementHandle, gridSize, update, commit) {
function FixedDragHandle(elementHandle, configPath, fixedControl) {
this.elementHandle = elementHandle;
this.gridSize = gridSize;
this.update = update;
this.commit = commit;
this.configPath = configPath;
this.fixedControl = fixedControl;
}
/**
* Get a CSS style to position this drag handle.
*
* @returns CSS style object (for `ng-style`)
* @memberof platform/features/layout.FixedDragHandle#
*/
FixedDragHandle.prototype.style = function () {
var gridSize = this.elementHandle.getGridSize();
// Adjust from grid to pixel coordinates
var x = this.elementHandle.x() * gridSize[0],
y = this.elementHandle.y() * gridSize[1];
@ -75,23 +79,20 @@ define(
/**
* Continue a drag gesture; update x/y positions.
* @param {number[]} delta x/y pixel difference since drag
* started
*
* @param {number[]} delta x/y pixel difference since drag started
*/
FixedDragHandle.prototype.continueDrag = function (delta) {
var gridSize = this.elementHandle.getGridSize();
if (this.dragging) {
// Update x/y positions (snapping to grid)
this.elementHandle.x(
this.dragging.x + Math.round(delta[0] / gridSize[0])
);
this.elementHandle.y(
this.dragging.y + Math.round(delta[1] / gridSize[1])
);
// Invoke update callback
if (this.update) {
this.update();
}
var newX = this.dragging.x + Math.round(delta[0] / gridSize[0]);
var newY = this.dragging.y + Math.round(delta[1] / gridSize[1]);
this.elementHandle.x(Math.max(0, newX));
this.elementHandle.y(Math.max(0, newY));
this.fixedControl.updateSelectionStyle();
}
};
@ -100,12 +101,8 @@ define(
* concludes to trigger commit of changes.
*/
FixedDragHandle.prototype.endDrag = function () {
// Clear cached state
this.dragging = undefined;
// Mark change as complete
if (this.commit) {
this.commit("Dragged handle.");
}
this.fixedControl.mutate(this.configPath, this.elementHandle.element);
};
return FixedDragHandle;

View File

@ -78,29 +78,30 @@ define(
}
$scope.configuration = $scope.configuration || {};
$scope.configuration.panels =
$scope.configuration.panels || {};
$scope.configuration.panels = $scope.configuration.panels || {};
$scope.configuration.panels[id] = {
position: [
Math.floor(position.x / self.gridSize[0]),
Math.floor(position.y / self.gridSize[1])
],
dimensions: self.defaultDimensions()
};
self.openmct.objects.get(id).then(function (object) {
$scope.configuration.panels[id] = {
position: [
Math.floor(position.x / self.gridSize[0]),
Math.floor(position.y / self.gridSize[1])
],
dimensions: self.defaultDimensions(),
hasFrame: self.getDefaultFrame(object.type)
};
// Store the id so that the newly-dropped object
// gets selected during refresh composition
self.droppedIdToSelectAfterRefresh = id;
// Store the id so that the newly-dropped object
// gets selected during refresh composition
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
// listen for drops, so call preventDefault() so
// that they can recognize that this event is handled.
@ -157,10 +158,7 @@ define(
$scope.configuration.panels[self.activeDragId].dimensions =
self.rawPositions[self.activeDragId].dimensions;
// Mark this object as dirty to encourage persistence
if ($scope.commit) {
$scope.commit("Moved frame.");
}
self.commit();
};
// Sets the selectable object in response to the selection change event.
@ -194,9 +192,22 @@ define(
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
self.unlisten();
});
$scope.$on("mctDrop", handleDrop);
self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) {
$scope.configuration = model.configuration.layout;
$scope.model = model;
var panels = $scope.configuration.panels;
Object.keys(panels).forEach(function (key) {
if (self.frames && self.frames.hasOwnProperty(key)) {
self.frames[key] = panels[key].hasFrame;
}
});
});
}
// Utility function to copy raw positions from configuration,
@ -220,7 +231,6 @@ define(
*/
LayoutController.prototype.setFrames = function (ids) {
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
this.frames = {};
this.$scope.composition.forEach(function (object) {
@ -230,11 +240,22 @@ define(
if (panels[id].hasOwnProperty('hasFrame')) {
this.frames[id] = panels[id].hasFrame;
} else {
this.frames[id] = DEFAULT_HIDDEN_FRAME_TYPES.indexOf(object.getModel().type) === -1;
this.frames[id] = this.getDefaultFrame(object.getModel().type);
}
}, this);
};
/**
* Gets the default value for frame.
*
* @param type the domain object type
* @return {boolean} true if the object should have
* frame by default, false, otherwise
*/
LayoutController.prototype.getDefaultFrame = function (type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
};
// Convert from { positions: ..., dimensions: ... } to an
// appropriate ng-style argument, to position frames.
LayoutController.prototype.convertPosition = function (raw) {
@ -389,40 +410,6 @@ define(
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
};
/**
* Callback to show/hide the object frame.
*
* @param {string} id the object id
* @private
*/
LayoutController.prototype.toggleFrame = function (id, domainObject) {
var configuration = this.$scope.configuration;
if (!configuration.panels[id]) {
configuration.panels[id] = {};
}
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
var selection = this.openmct.selection.get();
selection[0].context.toolbar = this.getToolbar(id, domainObject);
this.openmct.selection.select(selection); // reselect so toolbar updates
};
/**
* Gets the toolbar object for the given domain object.
*
* @param id the domain object id
* @param domainObject the domain object
* @returns {object}
* @private
*/
LayoutController.prototype.getToolbar = function (id, domainObject) {
var toolbarObj = {};
toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
return toolbarObj;
};
/**
* Bypasses selection if drag is in progress.
*
@ -497,17 +484,25 @@ define(
* Gets the selection context.
*
* @param domainObject the domain object
* @returns {object} the context object which includes
* item, oldItem and toolbar
* @returns {object} the context object which includes item and oldItem
*/
LayoutController.prototype.getContext = function (domainObject, toolbar) {
LayoutController.prototype.getContext = function (domainObject) {
return {
item: domainObject.useCapability('adapter'),
oldItem: domainObject,
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
oldItem: domainObject
};
};
LayoutController.prototype.commit = function () {
var model = this.$scope.model;
model.configuration = model.configuration || {};
model.configuration.layout = this.$scope.configuration;
this.$scope.domainObject.useCapability('mutation', function () {
return model;
});
};
/**
* Selects a newly-dropped object.
*

View File

@ -53,12 +53,6 @@ define(
*/
proxy.fill = new AccessorMutator(element, 'fill');
//Expose x,y, width and height for editing
proxy.editWidth = new AccessorMutator(element, 'width');
proxy.editHeight = new AccessorMutator(element, 'height');
proxy.editX = new AccessorMutator(element, 'x');
proxy.editY = new AccessorMutator(element, 'y');
return proxy;
}

View File

@ -71,13 +71,6 @@ define(
*/
this.gridSize = gridSize || [1,1]; //Ensure a reasonable default
this.resizeHandles = [new ResizeHandle(
this.element,
this.getMinWidth(),
this.getMinHeight(),
this.getGridSize()
)];
/**
* Get and/or set the x position of this element.
* Units are in fixed position grid space.
@ -123,15 +116,16 @@ define(
this.height = new AccessorMutator(element, 'height');
this.useGrid = new UnitAccessorMutator(this);
this.index = index;
this.elements = elements;
this.resizeHandles = [new ResizeHandle(this, this.element)];
}
/**
* Change the display order of this element.
* @param {string} o where to move this element;
* one of "top", "up", "down", or "bottom"
* @return {Array} the full array of elements
*/
ElementProxy.prototype.order = function (o) {
var index = this.index,
@ -152,16 +146,8 @@ define(
// anyway, but be consistent)
this.index = desired;
}
};
/**
* Remove this element from the fixed position view.
*/
ElementProxy.prototype.remove = function () {
var index = this.index;
if (this.elements[index] === this.element) {
this.elements.splice(index, 1);
}
return elements;
};
/**
@ -208,7 +194,6 @@ define(
*/
ElementProxy.prototype.getMinWidth = function () {
return Math.ceil(MIN_WIDTH / this.getGridSize()[0]);
};
/**

View File

@ -50,12 +50,6 @@ define(
*/
proxy.url = new AccessorMutator(element, 'url');
//Expose x,y, width and height properties for editing
proxy.editWidth = new AccessorMutator(element, 'width');
proxy.editHeight = new AccessorMutator(element, 'height');
proxy.editX = new AccessorMutator(element, 'x');
proxy.editY = new AccessorMutator(element, 'y');
return proxy;
}

View File

@ -32,19 +32,18 @@ define(
* @constructor
* @param element the line element
* @param {string} xProperty field which stores x position
* @param {string} yProperty field which stores x position
* @param {string} yProperty field which stores y position
* @param {string} xOther field which stores x of other end
* @param {string} yOther field which stores y of other end
* @param {number[]} gridSize the current layout grid size in [x,y] from
* @implements {platform/features/layout.ElementHandle}
*/
function LineHandle(element, xProperty, yProperty, xOther, yOther, gridSize) {
function LineHandle(element, elementProxy, xProperty, yProperty, xOther, yOther) {
this.elementProxy = elementProxy;
this.element = element;
this.xProperty = xProperty;
this.yProperty = yProperty;
this.xOther = xOther;
this.yOther = yOther;
this.gridSize = gridSize;
}
LineHandle.prototype.x = function (value) {
@ -86,7 +85,7 @@ define(
};
LineHandle.prototype.getGridSize = function () {
return this.gridSize;
return this.elementProxy.getGridSize();
};
return LineHandle;

View File

@ -39,10 +39,24 @@ define(
function LineProxy(element, index, elements, gridSize) {
var proxy = new ElementProxy(element, index, elements, gridSize),
handles = [
new LineHandle(element, 'x', 'y', 'x2', 'y2', proxy.getGridSize()),
new LineHandle(element, 'x2', 'y2', 'x', 'y', proxy.getGridSize())
new LineHandle(element, proxy, 'x', 'y', 'x2', 'y2'),
new LineHandle(element, proxy, 'x2', 'y2', 'x', 'y')
];
/**
* Gets style specific to line proxy.
*/
proxy.getStyle = function () {
var layoutGridSize = proxy.getGridSize();
return {
left: (layoutGridSize[0] * proxy.x()) + 'px',
top: (layoutGridSize[1] * proxy.y()) + 'px',
width: (layoutGridSize[0] * proxy.width()) + 'px',
height: (layoutGridSize[1] * proxy.height()) + 'px'
};
};
/**
* Get the top-left x coordinate, in grid space, of
* this line's bounding box.
@ -149,12 +163,6 @@ define(
return handles;
};
// Expose endpoint coordinates for editing
proxy.editX1 = new AccessorMutator(element, 'x');
proxy.editY1 = new AccessorMutator(element, 'y');
proxy.editX2 = new AccessorMutator(element, 'x2');
proxy.editY2 = new AccessorMutator(element, 'y2');
return proxy;
}

View File

@ -35,21 +35,16 @@ define(
* @memberof platform/features/layout
* @constructor
*/
function ResizeHandle(element, minWidth, minHeight, gridSize) {
function ResizeHandle(elementProxy, element) {
this.elementProxy = elementProxy;
this.element = element;
// Ensure reasonable defaults
this.minWidth = minWidth || 0;
this.minHeight = minHeight || 0;
this.gridSize = gridSize;
}
ResizeHandle.prototype.x = function (value) {
var element = this.element;
if (arguments.length > 0) {
element.width = Math.max(
this.minWidth,
this.elementProxy.getMinWidth(),
value - element.x
);
}
@ -60,7 +55,7 @@ define(
var element = this.element;
if (arguments.length > 0) {
element.height = Math.max(
this.minHeight,
this.elementProxy.getMinHeight(),
value - element.y
);
}
@ -68,7 +63,7 @@ define(
};
ResizeHandle.prototype.getGridSize = function () {
return this.gridSize;
return this.elementProxy.getGridSize();
};
return ResizeHandle;

View File

@ -24,9 +24,6 @@ define(
['./TextProxy'],
function (TextProxy) {
// Method names to expose from this proxy
var HIDE = 'hideTitle', SHOW = 'showTitle';
/**
* Selection proxy for telemetry elements in a fixed position view.
*
@ -45,24 +42,9 @@ define(
function TelemetryProxy(element, index, elements, gridSize) {
var proxy = new TextProxy(element, index, elements, gridSize);
// Toggle the visibility of the title
function toggle() {
// Toggle the state
element.titled = !element.titled;
// Change which method is exposed, to influence
// which button is shown in the toolbar
delete proxy[SHOW];
delete proxy[HIDE];
proxy[element.titled ? HIDE : SHOW] = toggle;
}
// Expose the domain object identifier
proxy.id = element.id;
// Expose initial toggle
proxy[element.titled ? HIDE : SHOW] = toggle;
// Don't expose text configuration
delete proxy.text;

View File

@ -53,22 +53,14 @@ define(
mockTimeSystem,
mockLimitEvaluator,
mockSelection,
mockObjects,
mockNewDomainObject,
unlistenFunc,
$element = [],
selectable = [],
controller;
// Utility function; find a watch for a given expression
function findWatch(expr) {
var watch;
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === expr) {
watch = call.args[1];
}
});
return watch;
}
// As above, but for $on calls
// Utility function; find a $on calls for a given expression.
function findOn(expr) {
var on;
mockScope.$on.calls.forEach(function (call) {
@ -82,7 +74,8 @@ define(
function makeMockDomainObject(id) {
return {
identifier: {
key: "domainObject-" + id
key: "domainObject-" + id,
namespace: ""
},
name: "Point " + id
};
@ -110,11 +103,6 @@ define(
return "Formatted " + valueMetadata.value;
});
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getId', 'getModel', 'getCapability', 'useCapability']
);
mockHandle = jasmine.createSpyObj(
'subscription',
[
@ -172,16 +160,14 @@ define(
]};
mockChildren = testModel.composition.map(makeMockDomainObject);
mockCompositionCollection = jasmine.createSpyObj('compositionCollection',
[
'load'
]
);
mockCompositionAPI = jasmine.createSpyObj('composition',
[
'get'
]
);
mockCompositionCollection = jasmine.createSpyObj('compositionCollection', [
'load',
'on',
'off'
]);
mockCompositionAPI = jasmine.createSpyObj('composition', [
'get'
]);
mockCompositionAPI.get.andReturn(mockCompositionCollection);
mockCompositionCollection.load.andReturn(
Promise.resolve(mockChildren)
@ -190,6 +176,24 @@ define(
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
mockNewDomainObject = jasmine.createSpyObj("newDomainObject", [
'layoutGrid',
'configuration',
'composition'
]);
mockNewDomainObject.layoutGrid = testGrid;
mockNewDomainObject.configuration = {
'fixed-display': testConfiguration
};
mockNewDomainObject.composition = ['a', 'b', 'c'];
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getId', 'getModel', 'getCapability', 'useCapability']
);
mockDomainObject.useCapability.andReturn(mockNewDomainObject);
mockScope.domainObject = mockDomainObject;
selectable[0] = {
context: {
oldItem: mockDomainObject
@ -203,11 +207,19 @@ define(
]);
mockSelection.get.andReturn([]);
unlistenFunc = jasmine.createSpy("unlisten");
mockObjects = jasmine.createSpyObj('objects', [
'observe',
'get'
]);
mockObjects.observe.andReturn(unlistenFunc);
mockOpenMCT = {
time: mockConductor,
telemetry: mockTelemetryAPI,
composition: mockCompositionAPI,
selection: mockSelection
selection: mockSelection,
objects: mockObjects
};
$element = $('<div></div>');
@ -251,76 +263,60 @@ define(
mockOpenMCT,
$element
);
findWatch("model.layoutGrid")(testModel.layoutGrid);
spyOn(controller, "mutate");
});
it("subscribes when a domain object is available", function () {
var dunzo = false;
it("subscribes a domain object", function () {
var object = makeMockDomainObject("mock");
var done = false;
mockScope.domainObject = mockDomainObject;
findWatch("domainObject")(mockDomainObject).then(function () {
dunzo = true;
controller.getTelemetry(object).then(function () {
done = true;
});
waitsFor(function () {
return dunzo;
}, "Telemetry fetched", 200);
return done;
});
runs(function () {
mockChildren.forEach(function (child) {
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(
child,
jasmine.any(Function),
jasmine.any(Object)
);
});
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(
object,
jasmine.any(Function),
jasmine.any(Object)
);
});
});
it("releases subscriptions when domain objects change", function () {
var dunzo = false;
it("releases subscription when a domain objects is removed", function () {
var done = false;
var unsubscribe = jasmine.createSpy('unsubscribe');
var object = makeMockDomainObject("mock");
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
mockScope.domainObject = mockDomainObject;
findWatch("domainObject")(mockDomainObject).then(function () {
dunzo = true;
controller.getTelemetry(object).then(function () {
done = true;
});
waitsFor(function () {
return dunzo;
}, "Telemetry fetched", 200);
return done;
});
runs(function () {
expect(unsubscribe).not.toHaveBeenCalled();
dunzo = false;
findWatch("domainObject")(mockDomainObject).then(function () {
dunzo = true;
});
controller.onCompositionRemove(object.identifier);
waitsFor(function () {
return dunzo;
}, "Telemetry fetched", 200);
runs(function () {
expect(unsubscribe.calls.length).toBe(mockChildren.length);
return unsubscribe.calls.length > 0;
});
runs(function () {
expect(unsubscribe).toHaveBeenCalled();
});
});
});
it("exposes visible elements based on configuration", function () {
var elements;
var elements = controller.getElements();
mockScope.model = testModel;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
expect(elements.length).toEqual(3);
expect(elements[0].id).toEqual('a');
expect(elements[1].id).toEqual('b');
@ -328,9 +324,6 @@ define(
});
it("allows elements to be selected", function () {
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
@ -338,12 +331,7 @@ define(
});
it("allows selection retrieval", function () {
var elements;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
var elements = controller.getElements();
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
@ -351,16 +339,10 @@ define(
});
it("selects the parent view when selected element is removed", function () {
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
var elements = controller.getElements();
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
elements[1].remove();
testModel.modified = 2;
findWatch("model.modified")(testModel.modified);
controller.remove(elements[1]);
expect($element[0].click).toHaveBeenCalled();
});
@ -368,21 +350,13 @@ define(
it("retains selections during refresh", function () {
// Get elements; remove one of them; trigger refresh.
// Same element (at least by index) should still be selected.
var elements;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
elements = controller.getElements();
var elements = controller.getElements();
selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.getSelectedElement()).toEqual(elements[1]);
elements[2].remove();
testModel.modified = 2;
findWatch("model.modified")(testModel.modified);
controller.remove(elements[2]);
elements = controller.getElements();
// Verify removal, as test assumes this
@ -408,7 +382,7 @@ define(
controller.elementProxiesById['12345'] = [testElement];
controller.elementProxies = [testElement];
controller.subscribeToObjects([telemetryObject]);
controller.subscribeToObject(telemetryObject);
mockTelemetryAPI.subscribe.mostRecentCall.args[1](mockTelemetry);
waitsFor(function () {
@ -426,18 +400,13 @@ define(
});
it("updates elements styles when grid size changes", function () {
var originalLeft;
// Grid size is initially set to testGrid which is [123, 456]
var originalLeft = controller.getElements()[0].style.left;
mockScope.domainObject = mockDomainObject;
mockScope.model = testModel;
findWatch("domainObject")(mockDomainObject);
findWatch("model.modified")(1);
findWatch("model.composition")(mockScope.model.composition);
findWatch("model.layoutGrid")([10, 10]);
originalLeft = controller.getElements()[0].style.left;
findWatch("model.layoutGrid")([20, 20]);
expect(controller.getElements()[0].style.left)
.not.toEqual(originalLeft);
// Change the grid size
controller.updateElementPositions([20, 20]);
expect(controller.getElements()[0].style.left).not.toEqual(originalLeft);
});
it("listens for drop events", function () {
@ -457,6 +426,9 @@ define(
// Notify that a drop occurred
testModel.composition.push('d');
mockObjects.get.andReturn(Promise.resolve([]));
findOn('mctDrop')(
mockEvent,
'd',
@ -468,11 +440,6 @@ define(
// ...and prevented default...
expect(mockEvent.preventDefault).toHaveBeenCalled();
// Should have triggered commit (provided by
// EditRepresenter) with some message.
expect(mockScope.commit)
.toHaveBeenCalledWith(jasmine.any(String));
});
it("ignores drops when default has been prevented", function () {
@ -492,52 +459,35 @@ define(
});
it("unsubscribes when destroyed", function () {
var dunzo = false;
var done = false;
var unsubscribe = jasmine.createSpy('unsubscribe');
var object = makeMockDomainObject("mock");
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
mockScope.domainObject = mockDomainObject;
findWatch("domainObject")(mockDomainObject).then(function () {
dunzo = true;
controller.getTelemetry(object).then(function () {
done = true;
});
waitsFor(function () {
return dunzo;
}, "Telemetry fetched", 200);
return done;
});
runs(function () {
expect(unsubscribe).not.toHaveBeenCalled();
// Destroy the scope
findOn('$destroy')();
//Check that the same unsubscribe function returned by the
expect(unsubscribe.calls.length).toBe(mockChildren.length);
expect(unsubscribe).toHaveBeenCalled();
});
});
it("exposes its grid size", function () {
findWatch('model.layoutGrid')(testGrid);
// Template needs to be able to pass this into line
// elements to size SVGs appropriately
expect(controller.getGridSize()).toEqual(testGrid);
});
it("exposes a view-level selection proxy", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
expect(mockOpenMCT.selection.select).toHaveBeenCalled();
expect(selection.context.viewProxy).toBeDefined();
});
it("exposes drag handles", function () {
var handles;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
@ -556,9 +506,6 @@ define(
});
it("exposes a move handle", function () {
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
@ -573,10 +520,6 @@ define(
it("updates selection style during drag", function () {
var oldStyle;
testModel.modified = 1;
findWatch("model.modified")(testModel.modified);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
@ -677,7 +620,7 @@ define(
value: testValue
}]));
controller.fetchHistoricalData([mockTelemetryObject]);
controller.fetchHistoricalData(mockTelemetryObject);
waitsFor(function () {
return controller.digesting === false;

View File

@ -28,8 +28,8 @@ define(
describe("A fixed position drag handle", function () {
var mockElementHandle,
mockUpdate,
mockCommit,
mockConfigPath,
mockFixedControl,
handle;
beforeEach(function () {
@ -37,18 +37,23 @@ define(
'elementHandle',
['x', 'y','getGridSize']
);
mockUpdate = jasmine.createSpy('update');
mockCommit = jasmine.createSpy('commit');
mockElementHandle.x.andReturn(6);
mockElementHandle.y.andReturn(8);
mockElementHandle.getGridSize.andReturn(TEST_GRID_SIZE);
mockFixedControl = jasmine.createSpyObj(
'fixedControl',
['updateSelectionStyle', 'mutate']
);
mockFixedControl.updateSelectionStyle.andReturn();
mockFixedControl.mutate.andReturn();
mockConfigPath = jasmine.createSpy('configPath');
handle = new FixedDragHandle(
mockElementHandle,
TEST_GRID_SIZE,
mockUpdate,
mockCommit
mockConfigPath,
mockFixedControl
);
});
@ -74,13 +79,12 @@ define(
expect(mockElementHandle.x).toHaveBeenCalledWith(5);
expect(mockElementHandle.y).toHaveBeenCalledWith(7);
// Should have called update once per continueDrag
expect(mockUpdate.calls.length).toEqual(2);
// Should have called updateSelectionStyle once per continueDrag
expect(mockFixedControl.updateSelectionStyle.calls.length).toEqual(2);
// Finally, ending drag should commit
expect(mockCommit).not.toHaveBeenCalled();
// Finally, ending drag should mutate
handle.endDrag();
expect(mockCommit).toHaveBeenCalled();
expect(mockFixedControl.mutate).toHaveBeenCalled();
});
});

View File

@ -42,6 +42,8 @@ define(
mockOpenMCT,
mockSelection,
mockDomainObjectCapability,
mockObjects,
unlistenFunc,
$element = [],
selectable = [];
@ -77,14 +79,15 @@ define(
if (param === 'composition') {
return id !== 'b';
}
}
},
type: "testType"
};
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
["$watch", "$watchCollection", "$on", "commit"]
["$watch", "$watchCollection", "$on"]
);
mockEvent = jasmine.createSpyObj(
'event',
@ -104,9 +107,13 @@ define(
}
}
};
unlistenFunc = jasmine.createSpy("unlisten");
mockDomainObjectCapability = jasmine.createSpyObj('capability',
['inEditContext']
['inEditContext', 'listen']
);
mockDomainObjectCapability.listen.andReturn(unlistenFunc);
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockScope.domainObject = mockDomainObject("mockDomainObject");
@ -126,8 +133,14 @@ define(
'get'
]);
mockSelection.get.andReturn(selectable);
mockObjects = jasmine.createSpyObj('objects', [
'get'
]);
mockObjects.get.andReturn(mockPromise(mockDomainObject("mockObject")));
mockOpenMCT = {
selection: mockSelection
selection: mockSelection,
objects: mockObjects
};
$element = $('<div></div>');
@ -138,6 +151,7 @@ define(
controller = new LayoutController(mockScope, $element, mockOpenMCT);
spyOn(controller, "layoutPanels").andCallThrough();
spyOn(controller, "commit");
jasmine.Clock.useMock();
});
@ -270,10 +284,7 @@ define(
controller.continueDrag([100, 100]);
controller.endDrag();
// Should have triggered commit (provided by
// EditRepresenter) with some message.
expect(mockScope.commit)
.toHaveBeenCalledWith(jasmine.any(String));
expect(controller.commit).toHaveBeenCalled();
});
it("listens for drop events", function () {
@ -296,11 +307,7 @@ define(
);
expect(testConfiguration.panels.d).toBeDefined();
expect(mockEvent.preventDefault).toHaveBeenCalled();
// Should have triggered commit (provided by
// EditRepresenter) with some message.
expect(mockScope.commit)
.toHaveBeenCalledWith(jasmine.any(String));
expect(controller.commit).toHaveBeenCalled();
});
it("ignores drops when default has been prevented", function () {
@ -340,13 +347,17 @@ define(
testModel.layoutGrid = [1, 1];
mockScope.$watch.calls[0].args[1](testModel.layoutGrid);
// Add a new object to the composition
mockComposition = ["a", "b", "c", "d"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
mockCompositionCapability = mockPromise(mockCompositionObjects);
// Notify that a drop occurred
mockScope.$on.mostRecentCall.args[1](
mockEvent,
'd',
{ x: 300, y: 100 }
);
mockScope.$watch.calls[0].args[1](['d']);
style = controller.getFrameStyle("d");
@ -415,30 +426,6 @@ define(
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
});
it("hides frame when selected object has frame ", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
expect(controller.hasFrame(childObj)).toBe(true);
expect(toolbarObj.hideFrame).toBeDefined();
expect(toolbarObj.hideFrame).toEqual(jasmine.any(Function));
});
it("shows frame when selected object has no frame", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[1];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
expect(controller.hasFrame(childObj)).toBe(false);
expect(toolbarObj.showFrame).toBeDefined();
expect(toolbarObj.showFrame).toEqual(jasmine.any(Function));
});
it("selects the parent object when selected object is removed", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];

View File

@ -53,11 +53,6 @@ define(
});
});
it("allows elements to be removed", function () {
proxy.remove();
expect(testElements).toEqual([{}, {}, {}]);
});
it("allows order to be changed", function () {
proxy.order("down");
expect(testElements).toEqual([{}, testElement, {}, {}]);

View File

@ -26,7 +26,9 @@ define(
describe("A fixed position drag handle", function () {
var testElement,
handle;
mockElementProxy,
handle,
TEST_GRID_SIZE = [45, 21];
beforeEach(function () {
testElement = {
@ -36,8 +38,10 @@ define(
y2: 11,
useGrid: true
};
mockElementProxy = jasmine.createSpyObj('elementProxy', ['getGridSize']);
mockElementProxy.getGridSize.andReturn(TEST_GRID_SIZE);
handle = new LineHandle(testElement, 'x', 'y', 'x2', 'y2', [45,21]);
handle = new LineHandle(testElement, mockElementProxy, 'x', 'y', 'x2', 'y2');
});
it("provides x/y grid coordinates for its corner", function () {
@ -69,7 +73,7 @@ define(
});
it("returns the correct grid size", function () {
expect(handle.getGridSize()).toEqual([45,21]);
expect(handle.getGridSize()).toEqual(TEST_GRID_SIZE);
});
});

View File

@ -63,13 +63,13 @@ define(
it("adjusts both ends when mutating x", function () {
var proxy = new LineProxy(diagonal);
proxy.x(6);
expect(diagonal).toEqual({ x: 6, y: 8, x2: 8, y2: 11, useGrid: true });
expect(diagonal).toEqual({ x: 6, y: 8, x2: 8, y2: 11});
});
it("adjusts both ends when mutating y", function () {
var proxy = new LineProxy(diagonal);
proxy.y(6);
expect(diagonal).toEqual({ x: 3, y: 6, x2: 5, y2: 9, useGrid: true });
expect(diagonal).toEqual({ x: 3, y: 6, x2: 5, y2: 9});
});
it("provides internal positions for SVG lines", function () {

View File

@ -25,10 +25,12 @@ define(
function (ResizeHandle) {
var TEST_MIN_WIDTH = 4,
TEST_MIN_HEIGHT = 2;
TEST_MIN_HEIGHT = 2,
TEST_GRID_SIZE = [34, 81];
describe("A fixed position drag handle", function () {
var testElement,
mockElementProxy,
handle;
beforeEach(function () {
@ -39,12 +41,18 @@ define(
height: 36,
useGrid: true
};
mockElementProxy = jasmine.createSpyObj('elementProxy', [
'getGridSize',
'getMinWidth',
'getMinHeight'
]);
mockElementProxy.getGridSize.andReturn(TEST_GRID_SIZE);
mockElementProxy.getMinWidth.andReturn(TEST_MIN_WIDTH);
mockElementProxy.getMinHeight.andReturn(TEST_MIN_HEIGHT);
handle = new ResizeHandle(
testElement,
TEST_MIN_WIDTH,
TEST_MIN_HEIGHT,
[34,81]
mockElementProxy,
testElement
);
});
@ -77,7 +85,7 @@ define(
});
it("returns the correct grid size", function () {
expect(handle.getGridSize()).toEqual([34,81]);
expect(handle.getGridSize()).toEqual(TEST_GRID_SIZE);
});
});

View File

@ -49,27 +49,6 @@ define(
it("exposes the element's id", function () {
expect(proxy.id).toEqual('test-id');
});
it("allows title to be shown/hidden", function () {
// Initially, only showTitle and hideTitle are available
expect(proxy.hideTitle).toBeUndefined();
proxy.showTitle();
// Should have set titled state
expect(testElement.titled).toBeTruthy();
// Should also have changed methods available
expect(proxy.showTitle).toBeUndefined();
proxy.hideTitle();
// Should have cleared titled state
expect(testElement.titled).toBeFalsy();
// Available methods should have changed again
expect(proxy.hideTitle).toBeUndefined();
proxy.showTitle();
});
});
}
);