mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUAC-586: Implement generic and hierarchical page tabbed page lists.
This commit is contained in:
@@ -353,11 +353,11 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
var linked = dataSource in users;
|
||||
|
||||
// Add page entry
|
||||
$scope.accountPages.push(new PageDefinition(
|
||||
translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME',
|
||||
'/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username),
|
||||
linked ? 'linked' : 'unlinked'
|
||||
));
|
||||
$scope.accountPages.push(new PageDefinition({
|
||||
name : translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME',
|
||||
url : '/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username),
|
||||
className : linked ? 'linked' : 'unlinked'
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
@@ -28,14 +28,14 @@
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.manage-user .settings-tabs .page-list li.unlinked a[href],
|
||||
.manage-user .settings-tabs .page-list li.linked a[href] {
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href],
|
||||
.manage-user .page-tabs .page-list li.linked a[href] {
|
||||
padding-right: 2.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.manage-user .settings-tabs .page-list li.unlinked a[href]:before,
|
||||
.manage-user .settings-tabs .page-list li.linked a[href]:before {
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href]:before,
|
||||
.manage-user .page-tabs .page-list li.linked a[href]:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@@ -47,19 +47,19 @@
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.manage-user .settings-tabs .page-list li.unlinked a[href]:before {
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href]:before {
|
||||
background-image: url('images/plus.png');
|
||||
}
|
||||
|
||||
.manage-user .settings-tabs .page-list li.unlinked a[href] {
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.manage-user .settings-tabs .page-list li.unlinked a[href]:hover,
|
||||
.manage-user .settings-tabs .page-list li.unlinked a[href].current {
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href]:hover,
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href].current {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.manage-user .settings-tabs .page-list li.linked a[href]:before {
|
||||
.manage-user .page-tabs .page-list li.linked a[href]:before {
|
||||
background-image: url('images/checkmark.png');
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ THE SOFTWARE.
|
||||
<h2>{{user.username}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
<div class="settings-tabs">
|
||||
<div class="page-tabs">
|
||||
<guac-page-list pages="accountPages" ng-show="showAccountTabs()"></guac-page-list>
|
||||
</div>
|
||||
|
||||
|
@@ -33,7 +33,7 @@ angular.module('navigation').directive('guacPageList', [function guacPageList()
|
||||
/**
|
||||
* The array of pages to display.
|
||||
*
|
||||
* @type Page[]
|
||||
* @type PageDefinition[]
|
||||
*/
|
||||
pages : '='
|
||||
|
||||
@@ -42,13 +42,119 @@ angular.module('navigation').directive('guacPageList', [function guacPageList()
|
||||
templateUrl: 'app/navigation/templates/guacPageList.html',
|
||||
controller: ['$scope', '$injector', function guacPageListController($scope, $injector) {
|
||||
|
||||
// Get required services
|
||||
// Required types
|
||||
var PageDefinition = $injector.get('PageDefinition');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
|
||||
/**
|
||||
* The URL of the currently-displayed page.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var currentURL = $location.url();
|
||||
|
||||
/**
|
||||
* The names associated with the current page, if the current page
|
||||
* is known. The value of this property corresponds to the value of
|
||||
* PageDefinition.name. Though PageDefinition.name may be a String,
|
||||
* this will always be an Array.
|
||||
*
|
||||
* @type String[]
|
||||
*/
|
||||
var currentPageName = [];
|
||||
|
||||
/**
|
||||
* Array of each level of the page list, where a level is defined
|
||||
* by a mapping of names (translation strings) to the
|
||||
* PageDefinitions corresponding to those names.
|
||||
*
|
||||
* @type Object.<String, PageDefinition>[]
|
||||
*/
|
||||
$scope.levels = [];
|
||||
|
||||
/**
|
||||
* Returns the names associated with the given page, in
|
||||
* hierarchical order. If the page is only associated with a single
|
||||
* name, and that name is not stored as an array, it will be still
|
||||
* be returned as an array containing a single item.
|
||||
*
|
||||
* @param {PageDefinition} page
|
||||
* The page to return the names of.
|
||||
*
|
||||
* @return {String[]}
|
||||
* An array of all names associated with the given page, in
|
||||
* hierarchical order.
|
||||
*/
|
||||
var getPageNames = function getPageNames(page) {
|
||||
|
||||
// If already an array, simply return the name
|
||||
if (angular.isArray(page.name))
|
||||
return page.name;
|
||||
|
||||
// Otherwise, transform into array
|
||||
return [page.name];
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the given PageDefinition to the overall set of pages
|
||||
* displayed by this guacPageList, automatically updating the
|
||||
* available levels ($scope.levels) and the contents of those
|
||||
* levels.
|
||||
*
|
||||
* @param {PageDefinition} page
|
||||
* The PageDefinition to add.
|
||||
*
|
||||
* @param {Number} weight
|
||||
* The sorting weight to use for the page if it does not
|
||||
* already have an associated weight.
|
||||
*/
|
||||
var addPage = function addPage(page, weight) {
|
||||
|
||||
// Pull all names for page
|
||||
var names = getPageNames(page);
|
||||
|
||||
// Copy the hierarchy of this page into the displayed levels
|
||||
// as far as is relevant for the currently-displayed page
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
|
||||
// Create current level, if it doesn't yet exist
|
||||
var pages = $scope.levels[i];
|
||||
if (!pages)
|
||||
pages = $scope.levels[i] = {};
|
||||
|
||||
// Get the name at the current level
|
||||
var name = names[i];
|
||||
|
||||
// Determine whether this page definition is part of the
|
||||
// hierarchy containing the current page
|
||||
var isCurrentPage = (currentPageName[i] === name);
|
||||
|
||||
// Store new page if it doesn't yet exist at this level
|
||||
if (!pages[name]) {
|
||||
pages[name] = new PageDefinition({
|
||||
name : name,
|
||||
url : isCurrentPage ? currentURL : page.url,
|
||||
className : page.className,
|
||||
weight : page.weight || weight
|
||||
});
|
||||
}
|
||||
|
||||
// If the name at this level no longer matches the
|
||||
// hierarchy of the current page, do not go any deeper
|
||||
if (currentPageName[i] !== name)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to the given page.
|
||||
*
|
||||
* @param {Page} page
|
||||
* @param {PageDefinition} page
|
||||
* The page to navigate to.
|
||||
*/
|
||||
$scope.navigateToPage = function navigateToPage(page) {
|
||||
@@ -58,16 +164,67 @@ angular.module('navigation').directive('guacPageList', [function guacPageList()
|
||||
/**
|
||||
* Tests whether the given page is the page currently being viewed.
|
||||
*
|
||||
* @param {Page} page
|
||||
* @param {PageDefinition} page
|
||||
* The page to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given page is the current page, false otherwise.
|
||||
*/
|
||||
$scope.isCurrentPage = function isCurrentPage(page) {
|
||||
return $location.url() === page.url;
|
||||
return currentURL === page.url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an arbitrary map of PageDefinitions, returns an array of
|
||||
* those PageDefinitions, sorted by weight.
|
||||
*
|
||||
* @param {Object.<*, PageDefinition>} level
|
||||
* A map of PageDefinitions with arbitrary keys. The value of
|
||||
* each key is ignored.
|
||||
*
|
||||
* @returns {PageDefinition[]}
|
||||
* An array of all PageDefinitions in the given map, sorted by
|
||||
* weight.
|
||||
*/
|
||||
$scope.getPages = function getPages(level) {
|
||||
|
||||
var pages = [];
|
||||
|
||||
// Convert contents of level to a flat array of pages
|
||||
angular.forEach(level, function addPageFromLevel(page) {
|
||||
pages.push(page);
|
||||
});
|
||||
|
||||
// Sort page array by weight
|
||||
pages.sort(function comparePages(a, b) {
|
||||
return a.weight - b.weight;
|
||||
});
|
||||
|
||||
return pages;
|
||||
|
||||
};
|
||||
|
||||
// Update page levels whenever pages changes
|
||||
$scope.$watch('pages', function setPages(pages) {
|
||||
|
||||
// Determine current page name
|
||||
currentPageName = [];
|
||||
angular.forEach(pages, function findCurrentPageName(page) {
|
||||
|
||||
// If page is current page, store its names
|
||||
if ($scope.isCurrentPage(page))
|
||||
currentPageName = getPageNames(page);
|
||||
|
||||
});
|
||||
|
||||
// Reset contents of levels
|
||||
$scope.levels = [];
|
||||
|
||||
// Add all page definitions
|
||||
angular.forEach(pages, addPage);
|
||||
|
||||
});
|
||||
|
||||
}] // end controller
|
||||
|
||||
};
|
||||
|
@@ -41,31 +41,16 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
var service = {};
|
||||
|
||||
/**
|
||||
* Construct a new PageDefinition object with the given name and url.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} name
|
||||
* The i18n key for the name of the page.
|
||||
*
|
||||
* @param {String} url
|
||||
* The URL of the page.
|
||||
*/
|
||||
var PageDefinition = function PageDefinition(name, url) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
};
|
||||
|
||||
/**
|
||||
* The home page to assign to a user if they can navigate to more than one
|
||||
* page.
|
||||
*
|
||||
* @type PageDefinition
|
||||
*/
|
||||
var SYSTEM_HOME_PAGE = new PageDefinition(
|
||||
'USER_MENU.ACTION_NAVIGATE_HOME',
|
||||
'/'
|
||||
);
|
||||
var SYSTEM_HOME_PAGE = new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_NAVIGATE_HOME',
|
||||
url : '/'
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns an appropriate home page for the current user.
|
||||
@@ -101,14 +86,14 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
// Only one connection present, use as home page
|
||||
if (connection) {
|
||||
homePage = new PageDefinition(
|
||||
connection.name,
|
||||
'/client/' + ClientIdentifier.toString({
|
||||
homePage = new PageDefinition({
|
||||
name : connection.name,
|
||||
url : '/client/' + ClientIdentifier.toString({
|
||||
dataSource : dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION,
|
||||
id : connection.identifier
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Only one balancing group present, use as home page
|
||||
@@ -116,14 +101,14 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
&& connectionGroup.type === ConnectionGroup.Type.BALANCING
|
||||
&& _.isEmpty(connectionGroup.childConnections)
|
||||
&& _.isEmpty(connectionGroup.childConnectionGroups)) {
|
||||
homePage = new PageDefinition(
|
||||
connectionGroup.name,
|
||||
'/client/' + ClientIdentifier.toString({
|
||||
homePage = new PageDefinition({
|
||||
name : connectionGroup.name,
|
||||
url : '/client/' + ClientIdentifier.toString({
|
||||
dataSource : dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION_GROUP,
|
||||
id : connectionGroup.identifier
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -247,33 +232,33 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
// If user can manage sessions, add link to sessions management page
|
||||
if (canManageSessions) {
|
||||
pages.push(new PageDefinition(
|
||||
'USER_MENU.ACTION_MANAGE_SESSIONS',
|
||||
'/settings/sessions'
|
||||
));
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_SESSIONS',
|
||||
url : '/settings/sessions'
|
||||
}));
|
||||
}
|
||||
|
||||
// If user can manage users, add link to user management page
|
||||
if (canManageUsers) {
|
||||
pages.push(new PageDefinition(
|
||||
'USER_MENU.ACTION_MANAGE_USERS',
|
||||
'/settings/users'
|
||||
));
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_USERS',
|
||||
url : '/settings/users'
|
||||
}));
|
||||
}
|
||||
|
||||
// If user can manage connections, add link to connections management page
|
||||
if (canManageConnections) {
|
||||
pages.push(new PageDefinition(
|
||||
'USER_MENU.ACTION_MANAGE_CONNECTIONS',
|
||||
'/settings/connections'
|
||||
));
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_CONNECTIONS',
|
||||
url : '/settings/connections'
|
||||
}));
|
||||
}
|
||||
|
||||
// Add link to user preferences (always accessible)
|
||||
pages.push(new PageDefinition(
|
||||
'USER_MENU.ACTION_MANAGE_PREFERENCES',
|
||||
'/settings/preferences'
|
||||
));
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_PREFERENCES',
|
||||
url : '/settings/preferences'
|
||||
}));
|
||||
|
||||
return pages;
|
||||
};
|
||||
@@ -338,10 +323,10 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
// Add generic link to the first-available settings page
|
||||
if (settingsPages.length) {
|
||||
pages.push(new PageDefinition(
|
||||
'USER_MENU.ACTION_MANAGE_SETTINGS',
|
||||
settingsPages[0].url
|
||||
));
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_SETTINGS',
|
||||
url : settingsPages[0].url
|
||||
}));
|
||||
}
|
||||
|
||||
return pages;
|
||||
|
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.page-tabs .page-list ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0.0125);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-tabs .page-list ul + ul {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href] {
|
||||
display: block;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding: 0.75em 1em;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href]:visited {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href]:hover {
|
||||
background-color: #CDA;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href].current,
|
||||
.page-tabs .page-list li a[href].current:hover {
|
||||
background: rgba(0,0,0,0.3);
|
||||
cursor: default;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
<ul class="page-list">
|
||||
<div class="page-list">
|
||||
<!--
|
||||
Copyright (C) 2015 Glyptodon LLC
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
-->
|
||||
|
||||
<!-- Navigation links -->
|
||||
<li ng-repeat="page in pages" class="{{page.className}}">
|
||||
<a class="home" ng-click="navigateToPage(page)"
|
||||
ng-class="{current: isCurrentPage(page)}" href="#{{page.url}}">
|
||||
{{page.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
<ul class="page-list-level" ng-repeat="level in levels track by $index">
|
||||
<li ng-repeat="page in getPages(level)" class="{{page.className}}">
|
||||
<a class="home" ng-click="navigateToPage(page)"
|
||||
ng-class="{current: isCurrentPage(page)}" href="#{{page.url}}">
|
||||
{{page.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -30,37 +30,46 @@ angular.module('navigation').factory('PageDefinition', [function definePageDefin
|
||||
* an arbitrary, human-readable name.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} name
|
||||
* The the name of the page, which should be a translation table key.
|
||||
*
|
||||
* @param {String} url
|
||||
* The URL of the page.
|
||||
*
|
||||
* @param {String} [className='']
|
||||
* The CSS class name to associate with this page, if any.
|
||||
* @param {PageDefinition|Object} template
|
||||
* The object whose properties should be copied within the new
|
||||
* PageDefinition.
|
||||
*/
|
||||
var PageDefinition = function PageDefinition(name, url, className) {
|
||||
var PageDefinition = function PageDefinition(template) {
|
||||
|
||||
/**
|
||||
* The the name of the page, which should be a translation table key.
|
||||
* Alternatively, this may also be a list of names, where the final
|
||||
* name represents the page and earlier names represent categorization.
|
||||
* Those categorical names may be rendered hierarchically as a system
|
||||
* of menus, tabs, etc.
|
||||
*
|
||||
* @type String
|
||||
* @type String|String[]
|
||||
*/
|
||||
this.name = name;
|
||||
this.name = template.name;
|
||||
|
||||
/**
|
||||
* The URL of the page.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.url = url;
|
||||
this.url = template.url;
|
||||
|
||||
/**
|
||||
* The CSS class name to associate with this page, if any.
|
||||
* The CSS class name to associate with this page, if any. This will be
|
||||
* an empty string by default.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.className = className || '';
|
||||
this.className = template.className || '';
|
||||
|
||||
/**
|
||||
* A numeric value denoting the relative sort order when compared to
|
||||
* other sibling PageDefinitions. If unspecified, sort order is
|
||||
* determined by the system using the PageDefinition.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.weight = template.weight;
|
||||
|
||||
};
|
||||
|
||||
|
@@ -34,36 +34,3 @@
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0.0125);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href] {
|
||||
display: block;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding: 0.75em 1em;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href]:visited {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href]:hover {
|
||||
background-color: #CDA;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href].current,
|
||||
.settings-tabs .page-list li a[href].current:hover {
|
||||
background: rgba(0,0,0,0.3);
|
||||
cursor: default;
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ THE SOFTWARE.
|
||||
</div>
|
||||
|
||||
<!-- Available tabs -->
|
||||
<div class="settings-tabs">
|
||||
<div class="page-tabs">
|
||||
<guac-page-list pages="settingsPages" ng-show="showAvailableTabs()"></guac-page-list>
|
||||
</div>
|
||||
|
||||
|
Reference in New Issue
Block a user