GUACAMOLE-220: Implement generic editor directive for manipulating sets of identifiers.
Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/229b0dee Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/229b0dee Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/229b0dee Branch: refs/heads/staging/1.0.0 Commit: 229b0dee4882352e7583c4f5872bee92158da712 Parents: 1cf16d1 Author: Michael Jumper <mjum...@apache.org> Authored: Wed Jul 25 02:34:27 2018 -0700 Committer: Michael Jumper <mjum...@apache.org> Committed: Wed Aug 8 09:00:06 2018 -0700 ---------------------------------------------------------------------- .../manage/directives/identifierSetEditor.js | 267 +++++++++++++++++++ .../app/manage/styles/related-objects.css | 82 ++++++ .../manage/templates/identifierSetEditor.html | 46 ++++ .../src/main/webapp/images/arrows/right.png | Bin 0 -> 264 bytes guacamole/src/main/webapp/images/x-red.png | Bin 0 -> 583 bytes 5 files changed, 395 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/229b0dee/guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js b/guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js new file mode 100644 index 0000000..82f1109 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/directives/identifierSetEditor.js @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * A directive for manipulating a set of objects sharing some common relation + * and represented by an array of their identifiers. The specific objects + * added or removed are tracked within a separate pair of arrays of + * identifiers. + */ +angular.module('manage').directive('identifierSetEditor', ['$injector', + function identifierSetEditor($injector) { + + var directive = { + + // Element only + restrict: 'E', + replace: true, + + scope: { + + /** + * The translation key of the text which should be displayed within + * the main header of the identifier set editor. + * + * @type String + */ + header : '@', + + /** + * The translation key of the text which should be displayed if no + * identifiers are currently present within the set. + * + * @type String + */ + emptyPlaceholder : '@', + + /** + * The translation key of the text which should be displayed if no + * identifiers are available to be added within the set. + * + * @type String + */ + unavailablePlaceholder : '@', + + /** + * All identifiers which are available to be added to or removed + * from the identifier set being edited. + * + * @type String[] + */ + identifiersAvailable : '=', + + /** + * The current state of the identifier set being manipulated. This + * array will be modified as changes are made through this + * identifier set editor. + * + * @type String[] + */ + identifiers : '=', + + /** + * The set of identifiers that have been added, relative to the + * initial state of the identifier set being manipulated. + * + * @type String[] + */ + identifiersAdded : '=', + + /** + * The set of identifiers that have been removed, relative to the + * initial state of the identifier set being manipulated. + * + * @type String[] + */ + identifiersRemoved : '=' + + }, + + templateUrl: 'app/manage/templates/identifierSetEditor.html' + + }; + + directive.controller = ['$scope', function identifierSetEditorController($scope) { + + /** + * Whether the full list of available identifiers should be displayed. + * Initially, only an abbreviated list of identifiers currently present + * is shown. + * + * @type Boolean + */ + $scope.expanded = false; + + /** + * Map of identifiers to boolean flags indicating whether that + * identifier is currently present (true) or absent (false). If an + * identifier is absent, it may also be absent from this map. + * + * @type Object.<String, Boolean> + */ + $scope.identifierFlags = {}; + + /** + * Adds the given identifier to the given sorted array of identifiers, + * preserving the sorted order of the array. If the identifier is + * already present, no change is made to the array. The given array + * must already be sorted in ascending order. + * + * @param {String[]} arr + * The sorted array of identifiers to add the given identifier to. + * + * @param {String} identifier + * The identifier to add to the given array. + */ + var addIdentifier = function addIdentifier(arr, identifier) { + + // Determine location that the identifier should be added to + // maintain sorted order + var index = _.sortedIndex(arr, identifier); + + // Do not add if already present + if (arr[index] === identifier) + return; + + // Insert identifier at determined location + arr.splice(index, 0, identifier); + + }; + + /** + * Removes the given identifier from the given sorted array of + * identifiers, preserving the sorted order of the array. If the + * identifier is already absent, no change is made to the array. The + * given array must already be sorted in ascending order. + * + * @param {String[]} arr + * The sorted array of identifiers to remove the given identifier + * from. + * + * @param {String} identifier + * The identifier to remove from the given array. + * + * @returns {Boolean} + * true if the identifier was present in the given array and has + * been removed, false otherwise. + */ + var removeIdentifier = function removeIdentifier(arr, identifier) { + + // Search for identifier in sorted array + var index = _.sortedIndexOf(arr, identifier); + + // Nothing to do if already absent + if (index === -1) + return false; + + // Remove identifier + arr.splice(index, 1); + return true; + + }; + + // Keep identifierFlags up to date when identifiers array is replaced + // or initially assigned + $scope.$watch('identifiers', function identifiersChanged(identifiers) { + + // Maintain identifiers in sorted order so additions and removals + // can be made more efficiently + if (identifiers) + identifiers.sort(); + + // Convert array of identifiers into set of boolean + // presence/absence flags + $scope.identifierFlags = {}; + angular.forEach(identifiers, function storeIdentifierFlag(identifier) { + $scope.identifierFlags[identifier] = true; + }); + + }); + + /** + * Notifies the controller that a change has been made to the flag + * denoting presence/absence of a particular identifier within the + * <code>identifierFlags</code> map. The <code>identifiers</code>, + * <code>identifiersAdded</code>, and <code>identifiersRemoved</code> + * arrays are updated accordingly. + * + * @param {String} identifier + * The identifier which has been added or removed through modifying + * its boolean flag within <code>identifierFlags</code>. + */ + $scope.identifierChanged = function identifierChanged(identifier) { + + // Determine status of modified identifier + var present = !!$scope.identifierFlags[identifier]; + + // Add/remove identifier from added/removed sets depending on + // change in flag state + if (present) { + + addIdentifier($scope.identifiers, identifier); + + if (!removeIdentifier($scope.identifiersRemoved, identifier)) + addIdentifier($scope.identifiersAdded, identifier); + + } + else { + + removeIdentifier($scope.identifiers, identifier); + + if (!removeIdentifier($scope.identifiersAdded, identifier)) + addIdentifier($scope.identifiersRemoved, identifier); + + } + + }; + + /** + * Removes the given identifier, updating <code>identifierFlags</code>, + * <code>identifiers</code>, <code>identifiersAdded</code>, and + * <code>identifiersRemoved</code> accordingly. + * + * @param {String} identifier + * The identifier to remove. + */ + $scope.removeIdentifier = function removeIdentifier(identifier) { + $scope.identifierFlags[identifier] = false; + $scope.identifierChanged(identifier); + }; + + /** + * Shows the full list of available identifiers. If the full list is + * already shown, this function has no effect. + */ + $scope.expand = function expand() { + $scope.expanded = true; + }; + + /** + * Hides the full list of available identifiers. If the full list is + * already hidden, this function has no effect. + */ + $scope.collapse = function collapse() { + $scope.expanded = false; + }; + + }]; + + return directive; + +}]); http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/229b0dee/guacamole/src/main/webapp/app/manage/styles/related-objects.css ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/styles/related-objects.css b/guacamole/src/main/webapp/app/manage/styles/related-objects.css new file mode 100644 index 0000000..ddc85b1 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/styles/related-objects.css @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.related-objects .abbreviated-related-objects { + display: table; + margin: 1em 0; +} + +.related-objects .abbreviated-related-objects ul { + display: table-cell; + vertical-align: top; +} + +.related-objects .abbreviated-related-objects ul, +.related-objects .all-related-objects ul { + padding: 0; + list-style: none; +} + +.related-objects .abbreviated-related-objects ul li { + + display: inline-block; + margin: 0.25em; + padding: 0.25em; + + border: 1px solid silver; + background: #F5F5F5; + -moz-border-radius: 0.25em; + -webkit-border-radius: 0.25em; + -khtml-border-radius: 0.25em; + border-radius: 0.25em; + +} + +.related-objects .abbreviated-related-objects ul li img.remove { + max-height: 0.75em; + max-width: 0.75em; + margin: 0 0.25em; +} + +.related-objects .abbreviated-related-objects ul li .identifier { + margin: 0 0.25em; +} + +.related-objects .abbreviated-related-objects img.expand, +.related-objects .abbreviated-related-objects img.collapse { + display: table-cell; + max-height: 1.5em; + max-width: 1.5em; + margin: 0.375em 0; +} + +.related-objects .all-related-objects { + border-top: 1px solid silver; +} + +.related-objects .abbreviated-related-objects p.no-related-objects, +.related-objects .all-related-objects p.no-objects-available { + font-style: italic; + opacity: 0.5; +} + +.related-objects .abbreviated-related-objects p.no-related-objects { + display: table-cell; + vertical-align: middle; +} http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/229b0dee/guacamole/src/main/webapp/app/manage/templates/identifierSetEditor.html ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/app/manage/templates/identifierSetEditor.html b/guacamole/src/main/webapp/app/manage/templates/identifierSetEditor.html new file mode 100644 index 0000000..838decf --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/templates/identifierSetEditor.html @@ -0,0 +1,46 @@ +<div class="related-objects"> + <div class="header"> + <h2>{{ header | translate }}</h2> + <div class="filter"> + <input class="search-string" type="text" + placeholder="{{ 'SETTINGS_USERS.FIELD_PLACEHOLDER_FILTER' | translate }}" + ng-model="filterString"/> + </div> + </div> + + <div class="section"> + + <!-- Abbreviated list of only the currently selected objects --> + <div class="abbreviated-related-objects"> + <img src="images/arrows/right.png" alt="Expand" class="expand" ng-hide="expanded" ng-click="expand()"/> + <img src="images/arrows/down.png" alt="Collapse" class="collapse" ng-show="expanded" ng-click="collapse()"/> + <p ng-hide="identifiers.length" class="no-related-objects">{{ emptyPlaceholder | translate }}</p> + <ul> + <li ng-repeat="identifier in identifiers | filter: filterString"> + <label><img src="images/x-red.png" alt="Remove" class="remove" + ng-click="removeIdentifier(identifier)"/><span class="identifier">{{ identifier }}</span> + </label> + </li> + </ul> + </div> + + <!-- Exhaustive, paginated list of all objects --> + <div class="all-related-objects" ng-show="expanded"> + <p ng-hide="identifiersAvailablePage.length" class="no-objects-available">{{ unavailablePlaceholder | translate }}</p> + <ul> + <li ng-repeat="identifier in identifiersAvailablePage"> + <label><input type="checkbox" + ng-model="identifierFlags[identifier]" + ng-change="identifierChanged(identifier)"/> + <span class="identifier">{{ identifier }}</span> + </label> + </li> + </ul> + + <!-- Pager controls for user list --> + <guac-pager page="identifiersAvailablePage" page-size="25" + items="identifiersAvailable | orderBy | filter: filterString"></guac-pager> + </div> + + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/229b0dee/guacamole/src/main/webapp/images/arrows/right.png ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/images/arrows/right.png b/guacamole/src/main/webapp/images/arrows/right.png new file mode 100644 index 0000000..1b3483e Binary files /dev/null and b/guacamole/src/main/webapp/images/arrows/right.png differ http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/229b0dee/guacamole/src/main/webapp/images/x-red.png ---------------------------------------------------------------------- diff --git a/guacamole/src/main/webapp/images/x-red.png b/guacamole/src/main/webapp/images/x-red.png new file mode 100644 index 0000000..e5497f3 Binary files /dev/null and b/guacamole/src/main/webapp/images/x-red.png differ