Simon Hunt

ONOS-4733: Simplified Settings Table view (removed redundant data)

 - Now shows just simple class name for component
 - Removed default value column
 - Values that are *not* the same as the default are highlighted (emboldened)
 - Table rows now selectable and show details panel
     - panel shows component fully qualified class name
     - panel currently read-only
     - in future, user will be able to change the property value from here

Change-Id: I01a2af727dcfad82c6b7d2378701a5cb3e24e43a
...@@ -36,14 +36,17 @@ public class SettingsViewMessageHandler extends UiMessageHandler { ...@@ -36,14 +36,17 @@ public class SettingsViewMessageHandler extends UiMessageHandler {
36 private static final String SETTINGS = "settings"; 36 private static final String SETTINGS = "settings";
37 37
38 private static final String COMPONENT = "component"; 38 private static final String COMPONENT = "component";
39 + private static final String FQ_COMPONENT = "fqComponent";
39 private static final String ID = "id"; 40 private static final String ID = "id";
40 private static final String TYPE = "type"; 41 private static final String TYPE = "type";
41 private static final String VALUE = "value"; 42 private static final String VALUE = "value";
42 private static final String DEFAULT = "defValue"; 43 private static final String DEFAULT = "defValue";
43 private static final String DESC = "desc"; 44 private static final String DESC = "desc";
44 45
46 + private static final char DOT = '.';
47 +
45 private static final String[] COL_IDS = { 48 private static final String[] COL_IDS = {
46 - COMPONENT, ID, TYPE, VALUE, DEFAULT, DESC 49 + COMPONENT, FQ_COMPONENT, ID, TYPE, VALUE, DEFAULT, DESC
47 }; 50 };
48 51
49 @Override 52 @Override
...@@ -90,13 +93,25 @@ public class SettingsViewMessageHandler extends UiMessageHandler { ...@@ -90,13 +93,25 @@ public class SettingsViewMessageHandler extends UiMessageHandler {
90 } 93 }
91 } 94 }
92 95
93 - private void populateRow(TableModel.Row row, String component, ConfigProperty prop) { 96 + private void populateRow(TableModel.Row row, String fqComp,
94 - row.cell(COMPONENT, component) 97 + ConfigProperty prop) {
98 + row.cell(COMPONENT, simpleName(fqComp))
99 + .cell(FQ_COMPONENT, fqComp)
95 .cell(ID, prop.name()) 100 .cell(ID, prop.name())
96 - .cell(TYPE, prop.type().toString().toLowerCase()) 101 + .cell(TYPE, typeName(prop))
97 .cell(VALUE, prop.value()) 102 .cell(VALUE, prop.value())
98 .cell(DEFAULT, prop.defaultValue()) 103 .cell(DEFAULT, prop.defaultValue())
99 .cell(DESC, prop.description()); 104 .cell(DESC, prop.description());
100 } 105 }
106 +
107 + // return just simple class name: "a.b.c.MyClass" -> "MyClass"
108 + private String simpleName(String component) {
109 + int lastDot = component.lastIndexOf(DOT);
110 + return lastDot < 0 ? component : component.substring(lastDot + 1);
111 + }
112 +
113 + private String typeName(ConfigProperty prop) {
114 + return prop.type().toString().toLowerCase();
115 + }
101 } 116 }
102 } 117 }
......
...@@ -470,7 +470,7 @@ ...@@ -470,7 +470,7 @@
470 }); 470 });
471 ks.gestureNotes([ 471 ks.gestureNotes([
472 ['click', 'Select a row to show application details'], 472 ['click', 'Select a row to show application details'],
473 - ['scroll down', 'See more application'] 473 + ['scroll down', 'See more applications']
474 ]); 474 ]);
475 475
476 // if the panelData changes 476 // if the panelData changes
......
...@@ -25,3 +25,41 @@ ...@@ -25,3 +25,41 @@
25 #ov-settings div.ctrl-btns { 25 #ov-settings div.ctrl-btns {
26 width: 45px; 26 width: 45px;
27 } 27 }
28 +
29 +#ov-settings div.summary-list td.notdef {
30 + color: #0071bd;
31 + font-weight: bold;
32 +}
33 +
34 +#settings-details-panel .container {
35 + padding: 0 30px;
36 + overflow-y: scroll;
37 +}
38 +
39 +#settings-details-panel .close-btn {
40 + position: absolute;
41 + right: 26px;
42 + top: 26px;
43 + cursor: pointer;
44 +}
45 +
46 +#settings-details-panel .top .settings-title {
47 + width: 90%;
48 + height: 62px;
49 + font-size: 20pt;
50 + font-weight: lighter;
51 + overflow: hidden;
52 + white-space: nowrap;
53 + padding: 0 12px 0 2px;
54 + margin-top: 19px;
55 + margin-bottom: 5px;
56 +}
57 +
58 +#settings-details-panel td.label {
59 + font-weight: bold;
60 + text-align: right;
61 + font-size: 12pt;
62 + padding-right: 6px;
63 + vertical-align: top;
64 + width: 120px;
65 +}
......
...@@ -14,11 +14,10 @@ ...@@ -14,11 +14,10 @@
14 <div class="table-header" onos-sortable-header> 14 <div class="table-header" onos-sortable-header>
15 <table> 15 <table>
16 <tr> 16 <tr>
17 - <td colId="component" sortable col-width="300px">Component </td> 17 + <td colId="component" sortable col-width="260px">Component </td>
18 - <td colId="id" sortable col-width="240px">Property </td> 18 + <td colId="id" sortable col-width="260px">Property </td>
19 <td colId="type" sortable col-width="100px">Type </td> 19 <td colId="type" sortable col-width="100px">Type </td>
20 <td colId="value" sortable col-width="100px">Value </td> 20 <td colId="value" sortable col-width="100px">Value </td>
21 - <td colId="defValue" sortable col-width="100px">Default </td>
22 <td colId="desc">Description </td> 21 <td colId="desc">Description </td>
23 </tr> 22 </tr>
24 </table> 23 </table>
...@@ -33,17 +32,21 @@ ...@@ -33,17 +32,21 @@
33 </tr> 32 </tr>
34 33
35 <tr ng-repeat="prop in tableData track by $index" 34 <tr ng-repeat="prop in tableData track by $index"
35 + ng-click="selectCallback($event, prop)"
36 + ng-class="{selected: prop.id === selId}"
36 ng-repeat-complete row-id="{{prop.id}}"> 37 ng-repeat-complete row-id="{{prop.id}}">
37 <td>{{prop.component}}</td> 38 <td>{{prop.component}}</td>
38 <td>{{prop.id}}</td> 39 <td>{{prop.id}}</td>
39 <td>{{prop.type}}</td> 40 <td>{{prop.type}}</td>
40 - <td>{{prop.value}}</td> 41 + <td ng-class="{notdef: prop.value !== prop.defValue}">
41 - <td>{{prop.defValue}}</td> 42 + {{prop.value}}
43 + </td>
42 <td>{{prop.desc}}</td> 44 <td>{{prop.desc}}</td>
43 </tr> 45 </tr>
44 </table> 46 </table>
45 </div> 47 </div>
46 48
47 </div> 49 </div>
50 + <settings-details-panel></settings-details-panel>
48 51
49 </div> 52 </div>
......
...@@ -21,16 +21,215 @@ ...@@ -21,16 +21,215 @@
21 (function () { 21 (function () {
22 'use strict'; 22 'use strict';
23 23
24 + // injected refs
25 + var $log, $scope, wss, fs, ks, ps, is;
26 +
27 + // internal state
28 + var detailsPanel,
29 + panelData,
30 + top,
31 + pStartY,
32 + pHeight,
33 + wSize = false;
34 +
35 + // constants
36 + var pName = 'settings-details-panel',
37 + panelWidth = 540,
38 + topPdg = 60,
39 + propOrder = ['fqComponent', 'id', 'type', 'value', 'defValue', 'desc'],
40 + friendlyProps = [
41 + 'Component', 'Property', 'Type', 'Value', 'Default Value',
42 + 'Description'
43 + ];
44 +
45 + function createDetailsPanel() {
46 + detailsPanel = ps.createPanel(pName, {
47 + width: wSize.width,
48 + margin: 0,
49 + hideMargin: 0
50 + });
51 +
52 + detailsPanel.el().style({
53 + position: 'absolute',
54 + top: pStartY + 'px'
55 + });
56 +
57 + detailsPanel.hide();
58 + }
59 +
60 + function closePanel() {
61 + if (detailsPanel.isVisible()) {
62 + $scope.selId = null;
63 + panelData = null;
64 + detailsPanel.hide();
65 + return true;
66 + }
67 + return false;
68 + }
69 +
70 + function addCloseBtn(div) {
71 + is.loadEmbeddedIcon(div, 'close', 26);
72 + div.on('click', closePanel);
73 + }
74 +
75 + function setUpPanel() {
76 + var container, closeBtn, div;
77 +
78 + detailsPanel.empty();
79 + detailsPanel.width(panelWidth);
80 +
81 + container = detailsPanel.append('div').classed('container', true);
82 +
83 + top = container.append('div').classed('top', true);
84 + closeBtn = top.append('div').classed('close-btn', true);
85 + addCloseBtn(closeBtn);
86 +
87 + div = top.append('div').classed('top-content', true);
88 +
89 + function ndiv(cls, addTable) {
90 + var d = div.append('div').classed(cls, true);
91 + if (addTable) {
92 + d.append('table');
93 + }
94 + }
95 +
96 + ndiv('settings-title');
97 + ndiv('settings-props', true);
98 + }
99 +
100 + function addProp(tbody, index, value) {
101 + var tr = tbody.append('tr');
102 +
103 + function addCell(cls, txt) {
104 + tr.append('td').attr('class', cls).html(txt);
105 + }
106 +
107 + addCell('label', friendlyProps[index] + ':');
108 + addCell('value', value);
109 + }
110 +
111 + function populateTop(details) {
112 + var propsBody = top.select('.settings-props').append('tbody');
113 +
114 + top.select('.settings-title').text(details.id);
115 +
116 + propOrder.forEach(function (prop, i) {
117 + addProp(propsBody, i, details[prop]);
118 + });
119 + }
120 +
121 + function populateDetails() {
122 + setUpPanel();
123 + populateTop(panelData);
124 + detailsPanel.height(pHeight);
125 + }
126 +
24 angular.module('ovSettings', []) 127 angular.module('ovSettings', [])
25 - .controller('OvSettingsCtrl', 128 + .controller('OvSettingsCtrl',
26 - ['$log', '$scope', 'TableBuilderService', 129 + ['$log', '$scope',
130 + 'WebSocketService', 'FnService', 'KeyService', 'PanelService',
131 + 'IconService', 'TableBuilderService',
132 +
133 + function (_$log_, _$scope_, _wss_, _fs_, _ks_, _ps_, _is_, tbs) {
134 + $log = _$log_;
135 + $scope = _$scope_;
136 + wss = _wss_;
137 + fs = _fs_;
138 + ks = _ks_;
139 + ps = _ps_;
140 + is = _is_;
141 + $scope.panelData = {};
142 +
143 + function selCb($event, row) {
144 + if ($scope.selId) {
145 + panelData = row;
146 + populateDetails();
147 + detailsPanel.show();
148 + } else {
149 + panelData = null;
150 + detailsPanel.hide();
151 + }
152 + }
27 153
28 - function ($log, $scope, tbs) {
29 tbs.buildTable({ 154 tbs.buildTable({
30 scope: $scope, 155 scope: $scope,
31 - tag: 'setting' 156 + tag: 'setting',
157 + selCb: selCb
158 + });
159 +
160 + ks.keyBindings({
161 + esc: [$scope.selectCallback, 'Deselect property'],
162 + _helpFormat: ['esc']
163 + });
164 + ks.gestureNotes([
165 + ['click row', 'Select / deselect settings property'],
166 + ['scroll down', 'See more settings']
167 + ]);
168 +
169 + $scope.$on('$destroy', function () {
170 + ks.unbindKeys();
32 }); 171 });
33 172
34 $log.log('OvSettingsCtrl has been created'); 173 $log.log('OvSettingsCtrl has been created');
35 - }]); 174 + }])
175 +
176 + .directive('settingsDetailsPanel',
177 + ['$rootScope', '$window', '$timeout', 'KeyService',
178 + function ($rootScope, $window, $timeout, ks) {
179 + return function (scope) {
180 + var unbindWatch;
181 +
182 + function heightCalc() {
183 + pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
184 + + topPdg;
185 + wSize = fs.windowSize(pStartY);
186 + pHeight = wSize.height;
187 + }
188 +
189 + function initPanel() {
190 + heightCalc();
191 + createDetailsPanel();
192 + $log.debug('start to initialize panel!');
193 + }
194 +
195 + // Safari has a bug where it renders the fixed-layout table wrong
196 + // if you ask for the window's size too early
197 + if (scope.onos.browser === 'safari') {
198 + $timeout(initPanel);
199 + } else {
200 + initPanel();
201 + }
202 + // create key bindings to handle panel
203 + ks.keyBindings({
204 + esc: [closePanel, 'Close the details panel'],
205 + _helpFormat: ['esc']
206 + });
207 + ks.gestureNotes([
208 + ['click', 'Select a row to show property details'],
209 + ['scroll down', 'See more properties']
210 + ]);
211 +
212 + // if the window size changes
213 + unbindWatch = $rootScope.$watchCollection(
214 + function () {
215 + return {
216 + h: $window.innerHeight,
217 + w: $window.innerWidth
218 + };
219 + }, function () {
220 + if (panelData) {
221 + heightCalc();
222 + populateDetails();
223 + }
224 + }
225 + );
226 +
227 + scope.$on('$destroy', function () {
228 + panelData = null;
229 + unbindWatch();
230 + ks.unbindKeys();
231 + ps.destroyPanel(pName);
232 + });
233 + };
234 + }]);
36 }()); 235 }());
......