Simon Hunt

GUI -- Application View Details Panel -

  - simplified DOM structure
  - refactored code to reduce boilerplate
  - cleaned up CSS

Change-Id: Iff443d7f038f1f770e7b3e9ed383c65b96ba6886
......@@ -58,7 +58,7 @@ public class ApplicationViewMessageHandler extends UiMessageHandler {
private static final String URL = "url";
private static final String README = "readme";
private static final String ROLE = "role";
private static final String REQUIRED_APPS = "_required_apps";
private static final String REQUIRED_APPS = "required_apps";
private static final String FEATURES = "features";
private static final String PERMISSIONS = "permissions";
......@@ -175,13 +175,13 @@ public class ApplicationViewMessageHandler extends UiMessageHandler {
// process required applications
ArrayNode requiredApps = arrayNode();
app.requiredApps().forEach(s -> requiredApps.add(s));
app.requiredApps().forEach(requiredApps::add);
data.set(REQUIRED_APPS, requiredApps);
// process features
ArrayNode features = arrayNode();
app.features().forEach(f -> features.add(f));
app.features().forEach(features::add);
data.set(FEATURES, features);
......
......@@ -15,7 +15,7 @@
*/
/*
ONOS GUI -- Host View -- CSS file
ONOS GUI -- Applications View -- CSS file
*/
#ov-app h2 {
......@@ -75,7 +75,7 @@
}
#application-details-panel .container {
padding: 0 12px;
padding: 0 10px;
}
#application-details-panel .close-btn {
......@@ -91,60 +91,48 @@
fill: #ccc;
}
#application-details-panel .dev-icon {
#application-details-panel .app-icon {
display: inline-block;
padding: 0 6px 0 0;
vertical-align: middle;
}
.light .dev-icon svg.embeddedIcon .glyph {
fill: rgb(0, 172, 229);
}
.dark .dev-icon svg.embeddedIcon .glyph {
fill: #486D91;
}
#application-details-panel h2 {
display: inline-block;
margin: 8px 0;
font-size: 12pt;
}
#application-details-panel .top div.left {
float: left;
padding: 0 18px 0 0;
padding: 0 12px 0 0;
}
#application-details-panel .top div.right {
display: inline-block;
overflow: hidden;
width: 320px;
}
#application-details-panel td.label {
#application-details-panel td.label,
#application-details-panel .app-url i {
font-style: italic;
padding-right: 12px;
/* works for both light and dark themes ... */
color: #777;
}
#application-details-panel .actionBtns div {
padding: 12px 6px;
#application-details-panel td.value-bold {
font-weight: bold;
}
#application-details-panel .top hr {
width: 95%;
margin: 0 auto;
#application-details-panel .app-url {
padding: 10px 6px 6px;
}
.light #application-details-panel hr {
opacity: .5;
border-color: #FFF;
}
.dark #application-details-panel hr {
border-color: #666;
}
#application-details-panel .middle hr {
#application-details-panel hr {
width: 95%;
margin: 0 auto;
margin: 10px auto;
}
.light #application-details-panel hr {
opacity: .5;
border-color: #FFF;
......@@ -155,25 +143,12 @@
#application-details-panel .bottom table {
border-spacing: 0;
width: 100%;
}
#application-details-panel .bottom th {
letter-spacing: 0.02em;
}
.light #application-details-panel .bottom th {
background-color: #CCC;
/* default text color */
}
.dark #application-details-panel .bottom th {
background-color: #131313;
color: #ccc;
}
#application-details-panel .bottom th,
#application-details-panel .bottom td {
padding: 6px 12px;
text-align: center;
text-align: left;
}
.light #application-details-panel .bottom tr:nth-child(odd) {
......
......@@ -22,7 +22,7 @@
'use strict';
// injected refs
var $log, $scope, $loc, fs, ps, wss, is, ns, ks, is;
var $log, $scope, wss, fs, ks, ps, is;
// internal state
var detailsPanel,
......@@ -31,7 +31,6 @@
top,
middle,
bottom,
iconDiv,
wSize = false;
// constants
......@@ -39,9 +38,7 @@
ACTIVE = 'ACTIVE',
appMgmtReq = 'appManagementRequest',
topPdg = 50,
ctnrPdg = 24,
tbWidth = 470,
scrollSize = 17,
panelWidth = 500,
pName = 'application-details-panel',
detailsReq = 'appDetailsRequest',
detailsResp = 'appDetailsResponse',
......@@ -57,8 +54,9 @@
},
discouragement = 'Deactivating or uninstalling this component can' +
' have serious negative consequences! Do so at your own risk!!',
propOrder = ['id', 'state', 'category', 'version', 'origin', 'role', 'url'],
friendlyProps = ['App ID', 'State', 'Category', 'Version', 'Origin', 'Role', 'URL'];
propOrder = ['id', 'state', 'category', 'version', 'origin', 'role'],
friendlyProps = ['App ID', 'State', 'Category', 'Version', 'Origin', 'Role'];
// note: url is handled separately
function createDetailsPane() {
detailsPanel = ps.createPanel(pName, {
......@@ -90,156 +88,111 @@
}
function setUpPanel() {
var container, closeBtn, tblDiv;
var container, closeBtn, div;
detailsPanel.empty();
detailsPanel.width(panelWidth);
container = detailsPanel.append('div').classed('container', true);
top = container.append('div').classed('top', true);
closeBtn = top.append('div').classed('close-btn', true);
addCloseBtn(closeBtn);
iconDiv = top.append('div').classed('dev-icon', true);
tblDiv = top.append('div').classed('top-tables', true);
tblDiv.append('div').classed('left', true).append('table');
tblDiv.append('div').classed('right', true).append('table');
tblDiv.append('div').classed('description', true).append('table');
div = top.append('div').classed('top-content', true);
function ndiv(cls, tcls) {
var d = div.append('div').classed(cls, true);
if (tcls) {
d.append('table').classed(tcls, true);
}
}
ndiv('left app-icon');
ndiv('right', 'app-props');
ndiv('app-url');
top.append('hr');
container.append('hr');
middle = container.append('div').classed('middle', true);
tblDiv = middle.append('div').classed('middle-tables', true);
tblDiv.append('div').classed('readme', true).append('table');
middle.append('div').classed('app-readme', true);
middle.append('hr');
container.append('hr');
// TODO: make bottom container scrollable
bottom = container.append('div').classed('bottom', true);
tblDiv = bottom.append('div').classed('bottom-tables', true).append('table');
tblDiv.append('h2').html('Features');
tblDiv.append('div').classed('features', true).append('table');
tblDiv.append('h2').html('Required Apps');
tblDiv.append('div').classed('required-apps', true).append('table');
tblDiv.append('h2').html('Permissions');
tblDiv.append('div').classed('permissions', true).append('table');
function nTable(hdr, cls) {
bottom.append('h2').html(hdr);
bottom.append('div').classed(cls, true).append('table');
}
nTable('Features', 'features');
nTable('Required Apps', 'required-apps');
nTable('Permissions', 'permissions');
}
function addProp(tbody, index, value) {
var tr = tbody.append('tr');
var tr = tbody.append('tr'),
vcls = index ? 'value' : 'value-bold';
function addCell(cls, txt) {
tr.append('td').attr('class', cls).html(txt);
}
addCell('label', friendlyProps[index] + ' :');
addCell('value', value);
}
function addUrl(tbody, index, value) {
var href = '<a href="' + value + '" target="_blank">' + value + '</a>';
addProp(tbody, index, href);
addCell('label', friendlyProps[index] + ':');
addCell(vcls, value);
}
function addIcon(tbody, value) {
var tr = tbody.append('tr');
var td = tr.append('td');
td.append('img').attr('src', iconUrlPrefix + value + iconUrlSuffix);
function urlize(u) {
return '<i>URL:</i> <a href="' + u + '" target="_blank">' + u + '</a>';
}
function addContent(tbody, value) {
var tr = tbody.append('tr');
tr.append('td').html(value);
function addIcon(elem, value) {
elem.append('img').attr('src', iconUrlPrefix + value + iconUrlSuffix);
}
function populateTop(tblDiv, details) {
var leftTbl = tblDiv.select('.left')
.select('table')
.append('tbody'),
rightTbl = tblDiv.select('.right')
.select('table')
.append('tbody'),
descriptionTbl = tblDiv.select('.description')
.select('table')
.append('tbody');
top.select('h2').html(details.name);
function populateTop(details) {
var propsBody = top.select('.app-props').append('tbody'),
url = details.url;
// place application icon to the left table
addIcon(leftTbl, details.id);
addIcon(top.select('.app-icon'), details.id);
// place rest of the fields to the right table
propOrder.forEach(function (prop, i) {
var fn = prop === 'url' ? addUrl : addProp;
fn(rightTbl, i, details[prop]);
addProp(propsBody, i, details[prop]);
});
// place description field to the description table
addContent(descriptionTbl, details.desc);
if (url) {
top.select('.app-url').html(urlize(url));
}
function populateMiddle(tblDiv, details) {
var readmeTbl = tblDiv.select('.readme')
.select('table')
.append('tbody');
// place readme field to the readme table
addContent(readmeTbl, details.readme);
}
function populateName(div, name) {
var lab = div.select('.label'),
val = div.select('.value');
lab.html('Friendly Name:');
val.html(name);
function populateMiddle(details) {
middle.select('.app-readme').text(details.readme);
}
function populateDetails(details) {
var nameDiv, topTbs, middleTbs, bottomTbs;
setUpPanel();
nameDiv = top.select('.name-div');
topTbs = top.select('.top-tables');
middleTbs = middle.select('.middle-tables');
bottomTbs = bottom.select('.bottom-tables');
function populateBottom(details) {
populateName(nameDiv, details.name);
populateTop(topTbs, details);
populateMiddle(middleTbs, details);
populateBottom(bottomTbs, details);
function addItems(cls, items) {
var table = bottom.select('.' + cls).select('table'),
tbody = table.append('tbody');
detailsPanel.height(pHeight);
}
function addItem(tbody, item) {
var tr = tbody.append('tr').attr('width', tbWidth + 'px');
tr.append('td').attr('width', tbWidth + 'px')
.attr('style', 'text-align:left').html(item);
}
function addItems(table, items) {
var tbody = table.append('tbody');
items.forEach(function (item) {
addItem(tbody, item);
tbody.append('tr').append('td').html(item);
});
}
function populateBottom(tblDiv, details) {
var featuresTbl = tblDiv.select('.features')
.select('table'),
permissionsTbl = tblDiv.select('.permissions')
.select('table'),
requiredAppsTbl = tblDiv.select('.required-apps')
.select('table');
addItems(featuresTbl, details.features);
addItems(requiredAppsTbl, details._required_apps);
addItems(permissionsTbl, details.permissions);
featuresTbl.style({
width: tbWidth + 'px',
overflow: 'auto',
display: 'block'
});
addItems('features', details.features);
addItems('required-apps', details.required_apps);
addItems('permissions', details.permissions);
}
detailsPanel.width(tbWidth + ctnrPdg);
function populateDetails(details) {
setUpPanel();
populateTop(details);
populateMiddle(details);
populateBottom(details);
detailsPanel.height(pHeight);
}
function respDetailsCb(data) {
......@@ -250,15 +203,15 @@
angular.module('ovApp', [])
.controller('OvAppCtrl',
['$log', '$scope', '$http',
'FnService', 'TableBuilderService', 'PanelService', 'WebSocketService',
'IconService', 'UrlFnService', 'KeyService', 'DialogService',
'WebSocketService', 'FnService', 'KeyService', 'PanelService',
'IconService', 'UrlFnService', 'DialogService', 'TableBuilderService',
function (_$log_, _$scope_, $http, _fs_, tbs, _ps_, _wss_, _is_, ufs, _ks_, ds) {
function (_$log_, _$scope_, $http, _wss_, _fs_, _ks_, _ps_, _is_, ufs, ds, tbs) {
$log = _$log_;
$scope = _$scope_;
wss = _wss_;
ks = _ks_;
fs = _fs_;
ks = _ks_;
ps = _ps_;
is = _is_;
$scope.panelData = {};
......@@ -285,7 +238,6 @@
} else {
$scope.hidePanel();
}
$log.debug('Got a click on:', row);
}
function refreshCtrls() {
......@@ -318,12 +270,12 @@
// TODO: reexamine where keybindings should be - directive or controller?
ks.keyBindings({
esc: [$scope.selectCallback, 'Deselect app'],
esc: [$scope.selectCallback, 'Deselect application'],
_helpFormat: ['esc']
});
ks.gestureNotes([
['click row', 'Select / deselect app'],
['scroll down', 'See more apps']
['click row', 'Select / deselect application'],
['scroll down', 'See more applications']
]);
function createConfirmationText(action, itemId) {
......