\ No newline at end of file
diff --git a/platform/features/layout/res/templates/elements/image.html b/platform/features/layout/res/templates/elements/image.html
new file mode 100644
index 0000000000..041147df27
--- /dev/null
+++ b/platform/features/layout/res/templates/elements/image.html
@@ -0,0 +1,3 @@
+
+
\ No newline at end of file
diff --git a/platform/features/layout/res/templates/elements/line.html b/platform/features/layout/res/templates/elements/line.html
new file mode 100644
index 0000000000..524cd451ca
--- /dev/null
+++ b/platform/features/layout/res/templates/elements/line.html
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/platform/features/layout/res/templates/elements/text.html b/platform/features/layout/res/templates/elements/text.html
new file mode 100644
index 0000000000..2b52f04598
--- /dev/null
+++ b/platform/features/layout/res/templates/elements/text.html
@@ -0,0 +1,4 @@
+
+ {{ngModel.element.text}}
+
\ No newline at end of file
diff --git a/platform/features/layout/res/templates/fixed.html b/platform/features/layout/res/templates/fixed.html
index 0b7e1ef056..9d6d741a62 100644
--- a/platform/features/layout/res/templates/fixed.html
+++ b/platform/features/layout/res/templates/fixed.html
@@ -14,6 +14,7 @@
0 && delta) {
+ element.x += delta;
+ element.x2 += delta;
+ }
+ return x;
+ };
+
+ /**
+ * Get the top-left y coordinate, in grid space, of
+ * this line's bounding box.
+ * @returns {number} the y coordinate
+ */
+ proxy.y = function (v) {
+ var y = Math.min(element.y, element.y2),
+ delta = v - y;
+ if (arguments.length > 0 && delta) {
+ element.y += delta;
+ element.y2 += delta;
+ }
+ return y;
+ };
+
+ /**
+ * Get the width, in grid space, of
+ * this line's bounding box.
+ * @returns {number} the width
+ */
+ proxy.width = function () {
+ return Math.max(Math.abs(element.x - element.x2), 1);
+ };
+
+ /**
+ * Get the height, in grid space, of
+ * this line's bounding box.
+ * @returns {number} the height
+ */
+ proxy.height = function () {
+ return Math.max(Math.abs(element.y - element.y2), 1);
+ };
+
+ /**
+ * Get the x position, in grid units relative to
+ * the top-left corner, of the first point in this line
+ * segment.
+ * @returns {number} the x position of the first point
+ */
+ proxy.x1 = function () {
+ return element.x - proxy.x();
+ };
+
+ /**
+ * Get the y position, in grid units relative to
+ * the top-left corner, of the first point in this line
+ * segment.
+ * @returns {number} the y position of the first point
+ */
+ proxy.y1 = function () {
+ return element.y - proxy.y();
+ };
+
+ /**
+ * Get the x position, in grid units relative to
+ * the top-left corner, of the second point in this line
+ * segment.
+ * @returns {number} the x position of the second point
+ */
+ proxy.x2 = function () {
+ return element.x2 - proxy.x();
+ };
+
+ /**
+ * Get the y position, in grid units relative to
+ * the top-left corner, of the second point in this line
+ * segment.
+ * @returns {number} the y position of the second point
+ */
+ proxy.y2 = function () {
+ return element.y2 - proxy.y();
+ };
+
+ return proxy;
+ }
+
+ return LineProxy;
+ }
+);
\ No newline at end of file
diff --git a/platform/features/layout/test/FixedControllerSpec.js b/platform/features/layout/test/FixedControllerSpec.js
index 460228e362..c8b943cc62 100644
--- a/platform/features/layout/test/FixedControllerSpec.js
+++ b/platform/features/layout/test/FixedControllerSpec.js
@@ -7,6 +7,8 @@ define(
describe("The Fixed Position controller", function () {
var mockScope,
+ mockQ,
+ mockDialogService,
mockSubscriber,
mockFormatter,
mockDomainObject,
@@ -58,6 +60,11 @@ define(
'telemetrySubscriber',
[ 'subscribe' ]
);
+ mockQ = jasmine.createSpyObj('$q', ['when']);
+ mockDialogService = jasmine.createSpyObj(
+ 'dialogService',
+ ['getUserInput']
+ );
mockFormatter = jasmine.createSpyObj(
'telemetryFormatter',
[ 'formatDomainValue', 'formatRangeValue' ]
@@ -99,6 +106,8 @@ define(
controller = new FixedController(
mockScope,
+ mockQ,
+ mockDialogService,
mockSubscriber,
mockFormatter
);
@@ -263,8 +272,6 @@ define(
.toHaveBeenCalledWith(jasmine.any(String));
});
-
-
it("unsubscribes when destroyed", function () {
// Make an object available
findWatch('domainObject')(mockDomainObject);
@@ -275,6 +282,12 @@ define(
// Should have unsubscribed
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
});
+
+ it("exposes its grid size", function () {
+ // Template needs to be able to pass this into line
+ // elements to size SVGs appropriately
+ expect(controller.getGridSize()).toEqual(testGrid);
+ });
});
}
);
\ No newline at end of file
diff --git a/platform/features/layout/test/FixedProxySpec.js b/platform/features/layout/test/FixedProxySpec.js
index 224b19f022..dde97fa142 100644
--- a/platform/features/layout/test/FixedProxySpec.js
+++ b/platform/features/layout/test/FixedProxySpec.js
@@ -6,13 +6,46 @@ define(
"use strict";
describe("Fixed Position view's selection proxy", function () {
- it("has a placeholder message when clicked", function () {
- var oldAlert = window.alert;
- window.alert = jasmine.createSpy('alert');
- new FixedProxy({}).add('');
- expect(window.alert).toHaveBeenCalledWith(jasmine.any(String));
- window.alert = oldAlert;
+ var mockCallback,
+ mockQ,
+ mockDialogService,
+ mockPromise,
+ proxy;
+
+ beforeEach(function () {
+ mockCallback = jasmine.createSpy('callback');
+ mockQ = jasmine.createSpyObj('$q', ['when']);
+ mockDialogService = jasmine.createSpyObj('dialogService', ['getUserInput']);
+ mockPromise = jasmine.createSpyObj('promise', ['then']);
+
+ mockQ.when.andReturn(mockPromise);
+
+ proxy = new FixedProxy(mockCallback, mockQ, mockDialogService);
});
+
+ it("handles promised element creation", function () {
+ // The element factory may return promises (e.g. if
+ // user input is required) so make sure proxy is wrapping these
+ proxy.add("fixed.box");
+ expect(mockQ.when).toHaveBeenCalled();
+ });
+
+ it("notifies its callback when an element is created", function () {
+ proxy.add("fixed.box");
+ // Callback should not have been invoked yet
+ expect(mockCallback).not.toHaveBeenCalled();
+ // Resolve the promise
+ mockPromise.then.mostRecentCall.args[0]({});
+ // Should have fired the callback
+ expect(mockCallback).toHaveBeenCalledWith({
+ type: "fixed.box",
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ });
+ });
+
});
}
);
diff --git a/platform/features/layout/test/elements/ElementFactorySpec.js b/platform/features/layout/test/elements/ElementFactorySpec.js
new file mode 100644
index 0000000000..4dec7a4357
--- /dev/null
+++ b/platform/features/layout/test/elements/ElementFactorySpec.js
@@ -0,0 +1,50 @@
+/*global define,describe,it,expect,beforeEach,jasmine*/
+
+define(
+ ['../../src/elements/ElementFactory'],
+ function (ElementFactory) {
+ "use strict";
+
+ var DIALOG_ELEMENTS = [ 'image', 'text' ],
+ NON_DIALOG_ELEMENTS = [ 'box', 'line' ];
+
+ describe("The fixed position element factory", function () {
+ var mockDialogService,
+ mockPromise,
+ factory;
+
+ beforeEach(function () {
+ mockDialogService = jasmine.createSpyObj(
+ 'dialogService',
+ [ 'getUserInput' ]
+ );
+ mockPromise = jasmine.createSpyObj(
+ 'promise',
+ [ 'then' ]
+ );
+
+ mockDialogService.getUserInput.andReturn(mockPromise);
+ mockPromise.then.andReturn(mockPromise);
+
+ factory = new ElementFactory(mockDialogService);
+ });
+
+ DIALOG_ELEMENTS.forEach(function (type) {
+ it("shows a dialog for " + type + " elements", function () {
+ expect(factory.createElement('fixed.' + type))
+ .toEqual(mockPromise);
+ expect(mockDialogService.getUserInput).toHaveBeenCalled();
+ });
+ });
+
+ NON_DIALOG_ELEMENTS.forEach(function (type) {
+ it("immediately provides " + type + " elements", function () {
+ var result = factory.createElement('fixed.' + type);
+ expect(result).toBeDefined();
+ expect(result).not.toEqual(mockPromise);
+ expect(mockDialogService.getUserInput).not.toHaveBeenCalled();
+ });
+ });
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/features/layout/test/elements/ElementProxiesSpec.js b/platform/features/layout/test/elements/ElementProxiesSpec.js
index cf771a043a..1e58fb8826 100644
--- a/platform/features/layout/test/elements/ElementProxiesSpec.js
+++ b/platform/features/layout/test/elements/ElementProxiesSpec.js
@@ -5,8 +5,13 @@ define(
function (ElementProxies) {
"use strict";
+ // Expect these element types to have proxies
var ELEMENT_TYPES = [
- "fixed.telemetry"
+ "fixed.telemetry",
+ "fixed.line",
+ "fixed.box",
+ "fixed.text",
+ "fixed.image"
];
// Verify that the set of proxies exposed matches the specific
diff --git a/platform/features/layout/test/elements/LineProxySpec.js b/platform/features/layout/test/elements/LineProxySpec.js
new file mode 100644
index 0000000000..fe38c7c3c9
--- /dev/null
+++ b/platform/features/layout/test/elements/LineProxySpec.js
@@ -0,0 +1,72 @@
+/*global define,describe,it,expect,beforeEach,jasmine*/
+
+define(
+ ['../../src/elements/LineProxy'],
+ function (LineProxy) {
+ "use strict";
+
+ describe("A fixed position line proxy", function () {
+ var vertical, horizontal, diagonal, reversed;
+
+ beforeEach(function () {
+ vertical = { x: 1, y: 4, x2: 1, y2: 8 };
+ horizontal = { x: 3, y: 3, x2: 12, y2: 3 };
+ diagonal = { x: 3, y: 8, x2: 5, y2: 11 };
+ reversed = { x2: 3, y2: 8, x: 5, y: 11 };
+ });
+
+ it("ensures visible width for vertical lines", function () {
+ expect(new LineProxy(vertical).width()).toEqual(1);
+ });
+
+ it("ensures visible height for horizontal lines", function () {
+ expect(new LineProxy(horizontal).height()).toEqual(1);
+ });
+
+ it("provides a bounding box for lines", function () {
+ var proxy = new LineProxy(diagonal);
+ expect(proxy.x()).toEqual(3);
+ expect(proxy.y()).toEqual(8);
+ expect(proxy.width()).toEqual(2);
+ expect(proxy.height()).toEqual(3);
+ });
+
+ it("bounds lines identically regardless of point order", function () {
+ // That is, x(), width(), y(), and height() should always give
+ // the same results for the same line segments, regardless of
+ // which point is x,y and which is x2,y2
+ ['x', 'y', 'width', 'height'].forEach(function (method) {
+ expect(new LineProxy(diagonal)[method]())
+ .toEqual(new LineProxy(reversed)[method]());
+ });
+ });
+
+ 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 });
+ });
+
+ 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 });
+ });
+
+ it("provides internal positions for SVG lines", function () {
+ var proxy;
+ proxy = new LineProxy(diagonal);
+ expect(proxy.x1()).toEqual(0);
+ expect(proxy.y1()).toEqual(0);
+ expect(proxy.x2()).toEqual(2);
+ expect(proxy.y2()).toEqual(3);
+ proxy = new LineProxy(reversed);
+ expect(proxy.x1()).toEqual(2);
+ expect(proxy.y1()).toEqual(3);
+ expect(proxy.x2()).toEqual(0);
+ expect(proxy.y2()).toEqual(0);
+ });
+
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/features/layout/test/suite.json b/platform/features/layout/test/suite.json
index af82513e01..c895900595 100644
--- a/platform/features/layout/test/suite.json
+++ b/platform/features/layout/test/suite.json
@@ -5,7 +5,9 @@
"LayoutDrag",
"LayoutSelection",
"elements/AccessorMutator",
+ "elements/ElementFactory",
"elements/ElementProxies",
"elements/ElementProxy",
+ "elements/LineProxy",
"elements/TelemetryProxy"
]
\ No newline at end of file
diff --git a/platform/forms/bundle.json b/platform/forms/bundle.json
index d8e3730eca..eb63e22432 100644
--- a/platform/forms/bundle.json
+++ b/platform/forms/bundle.json
@@ -45,6 +45,10 @@
{
"key": "composite",
"templateUrl": "templates/controls/composite.html"
+ },
+ {
+ "key": "menu-button",
+ "templateUrl": "templates/controls/menu-button.html"
}
],
"controllers": [
diff --git a/platform/forms/res/templates/controls/menu-button.html b/platform/forms/res/templates/controls/menu-button.html
new file mode 100644
index 0000000000..cb6b7e565b
--- /dev/null
+++ b/platform/forms/res/templates/controls/menu-button.html
@@ -0,0 +1,27 @@
+