Simon Hunt

GUI -- Implemented Panel Service.

Change-Id: I5e60c6ffa5676bc11f7312681af7bca85b4f8036
1 +/*
2 + * Copyright 2014,2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/*
18 + ONOS GUI -- Panel Service -- CSS file
19 + */
20 +
21 +.floatpanel {
22 + position: absolute;
23 + z-index: 100;
24 + display: block;
25 + top: 64px;
26 + width: 200px;
27 + right: -220px;
28 + opacity: 0;
29 + background-color: rgba(255,255,255,0.8);
30 +
31 + padding: 10px;
32 + color: black;
33 + font-size: 10pt;
34 +
35 + -moz-border-radius: 6px;
36 + border-radius: 6px;
37 + box-shadow: 0px 2px 12px #777;
38 +}
39 +
40 +/* TODO: light/dark themes */
41 +.light .floatpanel {
42 +
43 +}
44 +.dark .floatpanel {
45 +
46 +}
...@@ -20,46 +20,161 @@ ...@@ -20,46 +20,161 @@
20 (function () { 20 (function () {
21 'use strict'; 21 'use strict';
22 22
23 - var $log; 23 + var $log, fs;
24 24
25 var defaultSettings = { 25 var defaultSettings = {
26 - position: 'TR', 26 + edge: 'right',
27 - side: 'right', 27 + width: 200,
28 - width: 200 28 + height: 80,
29 + margin: 20,
30 + xtnTime: 750
29 }; 31 };
30 32
31 - angular.module('onosLayer') 33 + var panels,
32 - .factory('PanelService', ['$log', function (_$log_) { 34 + panelLayer;
33 - $log = _$log_; 35 +
34 - 36 +
37 + function init() {
38 + panelLayer = d3.select('#floatpanels');
39 + panelLayer.html('');
40 + panels = {};
41 + }
42 +
43 + // helpers for panel
44 + function noop() {}
45 +
46 + function margin(p) {
47 + return p.settings.margin;
48 + }
49 + function noPx(p, what) {
50 + return Number(p.el.style(what).replace(/px$/, ''));
51 + }
52 + function widthVal(p) {
53 + return noPx(p, 'width');
54 + }
55 + function heightVal(p) {
56 + return noPx(p, 'height');
57 + }
58 + function pxShow(p) {
59 + return margin(p) + 'px';
60 + }
61 + function pxHide(p) {
62 + return (-margin(p) - widthVal(p)) + 'px';
63 + }
64 +
65 + function makePanel(id, settings) {
66 + var p = {
67 + id: id,
68 + settings: settings,
69 + on: false,
70 + el: null
71 + },
72 + api = {
73 + show: showPanel,
74 + hide: hidePanel,
75 + empty: emptyPanel,
76 + append: appendPanel,
77 + width: panelWidth,
78 + height: panelHeight,
79 + isVisible: panelIsVisible
80 + };
35 81
36 - function createPanel(opts) { 82 + p.el = panelLayer.append('div')
37 - var settings = angular.extend({}, defaultSettings, opts); 83 + .attr('id', id)
84 + .attr('class', 'floatpanel')
85 + .style('opacity', 0);
86 +
87 + // has to be called after el is set
88 + p.el.style(p.settings.edge, pxHide(p));
89 + panelWidth(p.settings.width);
90 + panelHeight(p.settings.height);
91 +
92 + panels[id] = p;
93 +
94 + function showPanel(cb) {
95 + var endCb = fs.isF(cb) || noop;
96 + p.on = true;
97 + p.el.transition().duration(p.settings.xtnTime)
98 + .each('end', endCb)
99 + .style(p.settings.edge, pxShow(p))
100 + .style('opacity', 1);
101 + }
102 +
103 + function hidePanel(cb) {
104 + var endCb = fs.isF(cb) || noop;
105 + p.on = false;
106 + p.el.transition().duration(p.settings.xtnTime)
107 + .each('end', endCb)
108 + .style(p.settings.edge, pxHide(p))
109 + .style('opacity', 0);
110 + }
111 +
112 + function emptyPanel() {
113 + return p.el.html('');
114 + }
115 +
116 + function appendPanel(what) {
117 + return p.el.append(what);
118 + }
119 +
120 + function panelWidth(w) {
121 + if (w === undefined) {
122 + return widthVal(p);
123 + }
124 + p.el.style('width', w + 'px');
125 + }
38 126
39 - function renderPanel() { 127 + function panelHeight(h) {
128 + if (h === undefined) {
129 + return heightVal(p);
130 + }
131 + p.el.style('height', h + 'px');
132 + }
40 133
41 - } 134 + function panelIsVisible() {
135 + return p.on;
136 + }
42 137
43 - function showPanel() { 138 + return api;
139 + }
44 140
45 - } 141 + function removePanel(id) {
142 + panelLayer.select('#' + id).remove();
143 + delete panels[id];
144 + }
46 145
47 - function hidePanel() { 146 + angular.module('onosLayer')
147 + .factory('PanelService', ['$log', 'FnService', function (_$log_, _fs_) {
148 + $log = _$log_;
149 + fs = _fs_;
48 150
151 + function createPanel(id, opts) {
152 + var settings = angular.extend({}, defaultSettings, opts);
153 + if (!id) {
154 + $log.warn('createPanel: no ID given');
155 + return null;
49 } 156 }
157 + if (panels[id]) {
158 + $log.warn('Panel with ID "' + id + '" already exists');
159 + return null;
160 + }
161 + $log.debug('creating panel:', id, settings);
162 + return makePanel(id, settings);
163 + }
50 164
51 - var api = { 165 + function destroyPanel(id) {
52 - render: renderPanel, 166 + if (panels[id]) {
53 - show: showPanel, 167 + $log.debug('destroying panel:', id);
54 - hide: hidePanel 168 + removePanel(id);
55 - }; 169 + } else {
56 - 170 + $log.debug('no panel to destroy:', id);
57 - $log.debug('creating panel with settings: ', settings); 171 + }
58 - return api;
59 } 172 }
60 173
61 return { 174 return {
62 - createPanel: createPanel 175 + init: init,
176 + createPanel: createPanel,
177 + destroyPanel: destroyPanel
63 }; 178 };
64 }]); 179 }]);
65 180
......
...@@ -66,6 +66,7 @@ ...@@ -66,6 +66,7 @@
66 <link rel="stylesheet" href="common.css"> 66 <link rel="stylesheet" href="common.css">
67 <link rel="stylesheet" href="fw/mast/mast.css"> 67 <link rel="stylesheet" href="fw/mast/mast.css">
68 <link rel="stylesheet" href="fw/svg/icon.css"> 68 <link rel="stylesheet" href="fw/svg/icon.css">
69 + <link rel="stylesheet" href="fw/layer/panel.css">
69 <link rel="stylesheet" href="fw/nav/nav.css"> 70 <link rel="stylesheet" href="fw/nav/nav.css">
70 71
71 <!-- This is where contributed javascript will get injected --> 72 <!-- This is where contributed javascript will get injected -->
......
...@@ -24,12 +24,13 @@ ...@@ -24,12 +24,13 @@
24 // define core module dependencies here... 24 // define core module dependencies here...
25 var coreDependencies = [ 25 var coreDependencies = [
26 'ngRoute', 26 'ngRoute',
27 - 'onosWidget', 27 + 'onosMast',
28 + 'onosNav',
28 'onosUtil', 29 'onosUtil',
29 'onosSvg', 30 'onosSvg',
30 'onosRemote', 31 'onosRemote',
31 - 'onosMast', 32 + 'onosLayer',
32 - 'onosNav' 33 + 'onosWidget'
33 ]; 34 ];
34 35
35 // view IDs.. note the first view listed is loaded at startup 36 // view IDs.. note the first view listed is loaded at startup
...@@ -63,9 +64,9 @@ ...@@ -63,9 +64,9 @@
63 64
64 .controller('OnosCtrl', [ 65 .controller('OnosCtrl', [
65 '$log', '$route', '$routeParams', '$location', 66 '$log', '$route', '$routeParams', '$location',
66 - 'KeyService', 'ThemeService', 'GlyphService', 67 + 'KeyService', 'ThemeService', 'GlyphService', 'PanelService',
67 68
68 - function ($log, $route, $routeParams, $location, ks, ts, gs) { 69 + function ($log, $route, $routeParams, $location, ks, ts, gs, ps) {
69 var self = this; 70 var self = this;
70 71
71 self.$route = $route; 72 self.$route = $route;
...@@ -77,6 +78,7 @@ ...@@ -77,6 +78,7 @@
77 ts.init(); 78 ts.init();
78 ks.installOn(d3.select('body')); 79 ks.installOn(d3.select('body'));
79 gs.init(); 80 gs.init();
81 + ps.init();
80 82
81 $log.log('OnosCtrl has been created'); 83 $log.log('OnosCtrl has been created');
82 84
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
28 ]; 28 ];
29 29
30 // references to injected services etc. 30 // references to injected services etc.
31 - var $log, ks, zs, gs, ms, wss; 31 + var $log, ks, zs, gs, ms, wss, ps;
32 32
33 // DOM elements 33 // DOM elements
34 var ovtopo, svg, defs, zoomLayer, map; 34 var ovtopo, svg, defs, zoomLayer, map;
...@@ -170,11 +170,12 @@ ...@@ -170,11 +170,12 @@
170 angular.module('ovTopo', moduleDependencies) 170 angular.module('ovTopo', moduleDependencies)
171 171
172 .controller('OvTopoCtrl', [ 172 .controller('OvTopoCtrl', [
173 - '$scope', '$log', '$location', 173 + '$scope', '$log', '$location', '$timeout',
174 'KeyService', 'ZoomService', 'GlyphService', 'MapService', 174 'KeyService', 'ZoomService', 'GlyphService', 'MapService',
175 - 'WebSocketService', 175 + 'WebSocketService', 'PanelService',
176 176
177 - function ($scope, _$log_, $loc, _ks_, _zs_, _gs_, _ms_, _wss_) { 177 + function ($scope, _$log_, $loc, $timeout,
178 + _ks_, _zs_, _gs_, _ms_, _wss_, _ps_) {
178 var self = this; 179 var self = this;
179 $log = _$log_; 180 $log = _$log_;
180 ks = _ks_; 181 ks = _ks_;
...@@ -182,16 +183,18 @@ ...@@ -182,16 +183,18 @@
182 gs = _gs_; 183 gs = _gs_;
183 ms = _ms_; 184 ms = _ms_;
184 wss = _wss_; 185 wss = _wss_;
186 + ps = _ps_;
185 187
186 self.notifyResize = function () { 188 self.notifyResize = function () {
187 svgResized(svg.style('width'), svg.style('height')); 189 svgResized(svg.style('width'), svg.style('height'));
188 }; 190 };
189 191
192 + // Cleanup on destroyed scope..
190 $scope.$on('$destroy', function () { 193 $scope.$on('$destroy', function () {
191 $log.log('OvTopoCtrl is saying Buh-Bye!'); 194 $log.log('OvTopoCtrl is saying Buh-Bye!');
192 - // TODO: cleanup when the scope is destroyed... 195 + wsock && wsock.close();
193 - // for example, closing the web socket. 196 + wsock = null;
194 - 197 + ps.destroyPanel('topo-p-summary');
195 }); 198 });
196 199
197 // svg layer and initialization of components 200 // svg layer and initialization of components
...@@ -204,6 +207,12 @@ ...@@ -204,6 +207,12 @@
204 setUpMap(); 207 setUpMap();
205 setUpWebSocket($loc.search().wsport); 208 setUpWebSocket($loc.search().wsport);
206 209
210 + // TODO: remove this temporary code....
211 + var p = ps.createPanel('topo-p-summary');
212 + p.append('h1').text('Hello World');
213 + p.show();
214 + $timeout(function () { p.hide(); }, 2000);
215 +
207 $log.log('OvTopoCtrl has been created'); 216 $log.log('OvTopoCtrl has been created');
208 }]); 217 }]);
209 }()); 218 }());
......
...@@ -18,16 +18,27 @@ ...@@ -18,16 +18,27 @@
18 ONOS GUI -- Layer -- Panel Service - Unit Tests 18 ONOS GUI -- Layer -- Panel Service - Unit Tests
19 */ 19 */
20 describe('factory: fw/layer/panel.js', function () { 20 describe('factory: fw/layer/panel.js', function () {
21 - var $log, fs, ps; 21 + var $log, $timeout, fs, ps, d3Elem;
22 22
23 beforeEach(module('onosLayer')); 23 beforeEach(module('onosLayer'));
24 24
25 - beforeEach(inject(function (_$log_, FnService, PanelService) { 25 + beforeEach(inject(function (_$log_, _$timeout_, FnService, PanelService) {
26 $log = _$log_; 26 $log = _$log_;
27 + $timeout = _$timeout_;
27 fs = FnService; 28 fs = FnService;
28 ps = PanelService; 29 ps = PanelService;
30 + d3Elem = d3.select('body').append('div').attr('id', 'floatpanels');
31 + ps.init();
29 })); 32 }));
30 33
34 + afterEach(function () {
35 + d3.select('#floatpanels').remove();
36 + ps.init();
37 + });
38 +
39 + function floatPanelSelection() {
40 + return d3Elem.selectAll('.floatpanel');
41 + }
31 42
32 it('should define PanelService', function () { 43 it('should define PanelService', function () {
33 expect(ps).toBeDefined(); 44 expect(ps).toBeDefined();
...@@ -35,8 +46,134 @@ describe('factory: fw/layer/panel.js', function () { ...@@ -35,8 +46,134 @@ describe('factory: fw/layer/panel.js', function () {
35 46
36 it('should define api functions', function () { 47 it('should define api functions', function () {
37 expect(fs.areFunctions(ps, [ 48 expect(fs.areFunctions(ps, [
38 - 'createPanel' 49 + 'init', 'createPanel', 'destroyPanel'
39 ])).toBeTruthy(); 50 ])).toBeTruthy();
40 }); 51 });
41 52
53 + it('should have no panels to start', function () {
54 + expect(floatPanelSelection().size()).toBe(0);
55 + });
56 +
57 + it('should log a warning if no ID is given', function () {
58 + spyOn($log, 'warn');
59 + var p = ps.createPanel();
60 + expect(p).toBeNull();
61 + expect($log.warn).toHaveBeenCalledWith('createPanel: no ID given');
62 + expect(floatPanelSelection().size()).toBe(0);
63 + });
64 +
65 + it('should create a default panel', function () {
66 + spyOn($log, 'warn');
67 + spyOn($log, 'debug');
68 + var p = ps.createPanel('foo');
69 + expect(p).not.toBeNull();
70 + expect($log.warn).not.toHaveBeenCalled();
71 + expect(floatPanelSelection().size()).toBe(1);
72 + expect($log.debug).toHaveBeenCalledWith('creating panel:', 'foo', {
73 + edge: 'right',
74 + width: 200,
75 + height: 80,
76 + margin: 20,
77 + xtnTime: 750
78 + });
79 +
80 + // check basic properties
81 + expect(p.width()).toEqual(200);
82 + expect(p.isVisible()).toBeFalsy();
83 +
84 + var el = floatPanelSelection();
85 + expect(el.style('width')).toEqual('200px');
86 + });
87 +
88 + it('should complain when a duplicate ID is used', function () {
89 + spyOn($log, 'warn');
90 + var p = ps.createPanel('foo');
91 + expect(p).not.toBeNull();
92 + expect($log.warn).not.toHaveBeenCalled();
93 + expect(floatPanelSelection().size()).toBe(1);
94 +
95 + var dup = ps.createPanel('foo');
96 + expect(dup).toBeNull();
97 + expect($log.warn).toHaveBeenCalledWith('Panel with ID "foo" already exists');
98 + expect(floatPanelSelection().size()).toBe(1);
99 + });
100 +
101 + it('should note when there is no panel to destroy', function () {
102 + spyOn($log, 'debug');
103 + ps.destroyPanel('bar');
104 + expect($log.debug).toHaveBeenCalledWith('no panel to destroy:', 'bar')
105 + });
106 +
107 + it('should destroy the panel', function () {
108 + spyOn($log, 'debug');
109 + var p = ps.createPanel('foo');
110 + expect(floatPanelSelection().size()).toBe(1);
111 +
112 + ps.destroyPanel('foo');
113 + expect($log.debug).toHaveBeenCalledWith('destroying panel:', 'foo')
114 + expect(floatPanelSelection().size()).toBe(0);
115 + });
116 +
117 + it('should allow alternate settings to be given', function () {
118 + spyOn($log, 'debug');
119 + var p = ps.createPanel('foo', { width: 250, edge: 'left' });
120 + expect($log.debug).toHaveBeenCalledWith('creating panel:', 'foo', {
121 + edge: 'left',
122 + width: 250,
123 + height: 80,
124 + margin: 20,
125 + xtnTime: 750
126 + });
127 + });
128 +
129 + it('should show and hide the panel', function () {
130 + var p = ps.createPanel('foo', {xtnTime:0});
131 + expect(p.isVisible()).toBeFalsy();
132 +
133 + p.show();
134 + expect(p.isVisible()).toBeTruthy();
135 +
136 + p.hide();
137 + expect(p.isVisible()).toBeFalsy();
138 + });
139 +
140 + it('should append content to the panel', function () {
141 + var p = ps.createPanel('foo');
142 + var span = p.append('span').attr('id', 'thisIsMySpan');
143 +
144 + expect(floatPanelSelection().selectAll('span').attr('id'))
145 + .toEqual('thisIsMySpan');
146 + });
147 +
148 + it('should remove content on empty', function () {
149 + var p = ps.createPanel('voop');
150 + p.append('span');
151 + p.append('span');
152 + p.append('span');
153 + expect(floatPanelSelection().selectAll('span').size()).toEqual(3);
154 +
155 + p.empty();
156 + expect(floatPanelSelection().selectAll('span').size()).toEqual(0);
157 + expect(floatPanelSelection().html()).toEqual('');
158 + });
159 +
160 + it('should allow programmatic setting of width', function () {
161 + var p = ps.createPanel('whatcha', {width:234});
162 + expect(floatPanelSelection().style('width')).toEqual('234px');
163 + expect(p.width()).toEqual(234);
164 +
165 + p.width(345);
166 + expect(floatPanelSelection().style('width')).toEqual('345px');
167 + expect(p.width()).toEqual(345);
168 + });
169 +
170 + it('should allow programmatic setting of height', function () {
171 + var p = ps.createPanel('ciao', {height:50});
172 + expect(floatPanelSelection().style('height')).toEqual('50px');
173 + expect(p.height()).toEqual(50);
174 +
175 + p.height(100);
176 + expect(floatPanelSelection().style('height')).toEqual('100px');
177 + expect(p.height()).toEqual(100);
178 + });
42 }); 179 });
......