GUI -- Implemented Panel Service.
Change-Id: I5e60c6ffa5676bc11f7312681af7bca85b4f8036
Showing
6 changed files
with
349 additions
and
39 deletions
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 | }); | ... | ... |
-
Please register or login to post a comment