From 34ad1f20999f29614a0f96cc83ae06db5364947f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 23 Mar 2015 13:39:40 -0700 Subject: [PATCH 1/6] GUAC-1140: Add class which allows easy dynamic reordering of the sorting predicate used by the orderBy filter. --- .../webapp/app/manage/types/StableSort.js | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 guacamole/src/main/webapp/app/manage/types/StableSort.js diff --git a/guacamole/src/main/webapp/app/manage/types/StableSort.js b/guacamole/src/main/webapp/app/manage/types/StableSort.js new file mode 100644 index 000000000..36c21fc78 --- /dev/null +++ b/guacamole/src/main/webapp/app/manage/types/StableSort.js @@ -0,0 +1,115 @@ +/* + * 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. + */ + +/** + * A service for defining the StableSort class. + */ +angular.module('manage').factory('StableSort', [ + function defineStableSort() { + + /** + * Maintains a sorting predicate as required by the Angular orderBy filter. + * The order of properties sorted by the predicate can be altered while + * otherwise maintaining the sort order. + * + * @constructor + * @param {String[]} predicate + * The properties to sort by, in order of precidence. + */ + var StableSort = function StableSort(predicate) { + + /** + * Reference to this instance. + * + * @type StableSort + */ + var stableSort = this; + + /** + * The current sorting predicate. + * + * @type String[] + */ + this.predicate = predicate; + + /** + * The name of the highest-precedence sorting property. + * + * @type String + */ + this.primary = predicate[0]; + + /** + * Whether the highest-precedence sorting property is sorted in + * descending order. + * + * @type Boolean + */ + this.descending = false; + + // Handle initially-descending primary properties + if (this.primary.charAt(0) === '-') { + this.primary = this.primary.substring(1); + this.descending = true; + } + + /** + * Reorders the currently-defined predicate such that the named + * property takes precidence over all others. The property will be + * sorted in ascending order unless otherwise specified. + * + * @param {String} name + * The name of the property to reorder by. + * + * @param {Boolean} [descending=false] + * Whether the property should be sorted in descending order. By + * default, all properties are sorted in ascending order. + */ + this.reorder = function reorder(name, descending) { + + // Build ascending and descending predicate components + var ascendingName = name; + var descendingName = '-' + name; + + // Remove requested property from current predicate + stableSort.predicate = stableSort.predicate.filter(function notRequestedProperty(current) { + return current !== ascendingName + && current !== descendingName; + }); + + // Add property to beginning of predicate + if (descending) + stableSort.predicate.unshift(descendingName); + else + stableSort.predicate.unshift(ascendingName); + + // Update sorted state + stableSort.primary = name; + stableSort.descending = !!descending; + + }; + + }; + + return StableSort; + +}]); \ No newline at end of file From 6135883643eab7a9e48b57fe0c338fd01c0211d2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 23 Mar 2015 13:42:11 -0700 Subject: [PATCH 2/6] GUAC-1140: Use StableSort to maintain active session sort order. --- .../controllers/manageSessionsController.js | 116 +++++++++++++----- .../app/manage/templates/manageSessions.html | 4 +- .../manage/types/ActiveConnectionWrapper.js | 12 +- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js index b4a63c61f..97d9c2b83 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js @@ -29,6 +29,7 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj // Required types var ActiveConnectionWrapper = $injector.get('ActiveConnectionWrapper'); var ConnectionGroup = $injector.get('ConnectionGroup'); + var StableSort = $injector.get('StableSort'); // Required services var activeConnectionService = $injector.get('activeConnectionService'); @@ -37,13 +38,6 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); - /** - * The root connection group of the connection group hierarchy. - * - * @type ConnectionGroup - */ - $scope.rootGroup = null; - /** * All permissions associated with the current user, or null if the user's * permissions have not yet been loaded. @@ -60,19 +54,41 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj */ $scope.wrappers = null; - // Query the user's permissions - permissionService.getPermissions(authenticationService.getCurrentUserID()) - .success(function permissionsReceived(permissions) { - $scope.permissions = permissions; - }); + /** + * StableSort instance which maintains the sort order of the visible + * connection wrappers. + * + * @type StableSort + */ + $scope.wrapperOrder = new StableSort([ + 'activeConnection.username', + 'activeConnection.startDate', + 'activeConnection.remoteHost', + 'name' + ]); + + /** + * The root connection group of the connection group hierarchy. + * + * @type ConnectionGroup + */ + var rootGroup = null; + + /** + * All active connections, if known, or null if active connections have not + * yet been loaded. + * + * @type ActiveConnection + */ + var activeConnections = null; /** * Map of all visible connections by object identifier. * * @type Object. */ - $scope.connections = {}; - + var connections = {}; + /** * Map of all currently-selected active connection wrappers by identifier. * @@ -90,7 +106,7 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj var addConnection = function addConnection(connection) { // Add given connection to set of visible connections - $scope.connections[connection.identifier] = connection; + connections[connection.identifier] = connection; }; @@ -113,23 +129,62 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj connectionGroup.childConnectionGroups.forEach(addDescendantConnections); }; - - // Retrieve all connections - connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER) - .success(function connectionGroupReceived(rootGroup) { - $scope.rootGroup = rootGroup; - addDescendantConnections($scope.rootGroup); - }); - - // Query active sessions - activeConnectionService.getActiveConnections().success(function sessionsRetrieved(activeConnections) { - + + /** + * Wraps all loaded active connections, storing the resulting array within + * the scope. If required data has not yet finished loading, this function + * has no effect. + */ + var wrapActiveConnections = function wrapActiveConnections() { + + // Abort if not all required data is available + if (!activeConnections || !connections) + return; + // Wrap all active connections for sake of display $scope.wrappers = []; for (var identifier in activeConnections) { - $scope.wrappers.push(new ActiveConnectionWrapper(activeConnections[identifier])); + + var activeConnection = activeConnections[identifier]; + var connection = connections[activeConnection.connectionIdentifier]; + + $scope.wrappers.push(new ActiveConnectionWrapper( + connection.name, + activeConnection + )); + } - + + }; + + // Query the user's permissions + permissionService.getPermissions(authenticationService.getCurrentUserID()) + .success(function permissionsReceived(retrievedPermissions) { + $scope.permissions = retrievedPermissions; + }); + + // Retrieve all connections + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER) + .success(function connectionGroupReceived(retrievedRootGroup) { + + // Load connections from retrieved group tree + rootGroup = retrievedRootGroup; + addDescendantConnections(rootGroup); + + // Attempt to produce wrapped list of active connections + wrapActiveConnections(); + + }); + + // Query active sessions + activeConnectionService.getActiveConnections().success(function sessionsRetrieved(retrievedActiveConnections) { + + // Store received list + activeConnections = retrievedActiveConnections; + + // Attempt to produce wrapped list of active connections + wrapActiveConnections(); + }); /** @@ -141,9 +196,8 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj */ $scope.isLoaded = function isLoaded() { - return $scope.wrappers !== null - && $scope.permissions !== null - && $scope.rootGroup !== null; + return $scope.wrappers !== null + && $scope.permissions !== null; }; diff --git a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html b/guacamole/src/main/webapp/app/manage/templates/manageSessions.html index 9b5ad2721..1211cfa93 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageSessions.html @@ -55,7 +55,7 @@ THE SOFTWARE. {{wrapper.activeConnection.username}} {{wrapper.activeConnection.startDate | date:'short'}} {{wrapper.activeConnection.remoteHost}} - {{connections[wrapper.activeConnection.connectionIdentifier].name}} + {{wrapper.name}} @@ -66,7 +66,7 @@ THE SOFTWARE.

- + \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/types/ActiveConnectionWrapper.js b/guacamole/src/main/webapp/app/manage/types/ActiveConnectionWrapper.js index b15a2929b..92130c270 100644 --- a/guacamole/src/main/webapp/app/manage/types/ActiveConnectionWrapper.js +++ b/guacamole/src/main/webapp/app/manage/types/ActiveConnectionWrapper.js @@ -31,10 +31,20 @@ angular.module('manage').factory('ActiveConnectionWrapper', [ * properties, such as a checked option. * * @constructor + * @param {String} name + * The display name of the active connection. + * * @param {ActiveConnection} activeConnection * The ActiveConnection to wrap. */ - var ActiveConnectionWrapper = function ActiveConnectionWrapper(activeConnection) { + var ActiveConnectionWrapper = function ActiveConnectionWrapper(name, activeConnection) { + + /** + * The display name of this connection. + * + * @type String + */ + this.name = name; /** * The wrapped ActiveConnection. From 4d81272d423a4e758999517eefaacebcde15ab95 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 23 Mar 2015 13:54:31 -0700 Subject: [PATCH 3/6] GUAC-1140: Set different CSS classes depending on primary sort column and direction. --- .../controllers/manageSessionsController.js | 44 +++++++++++++++++++ .../webapp/app/manage/styles/sessions.css | 12 +++++ .../app/manage/templates/manageSessions.html | 16 +++++-- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js index 97d9c2b83..c8871e68c 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js @@ -201,6 +201,50 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj }; + /** + * Returns whether the wrapped session list is sorted by username. + * + * @returns {Boolean} + * true if the wrapped session list is sorted by username, false + * otherwise. + */ + $scope.sortedByUsername = function sortedByUsername() { + return $scope.wrapperOrder.primary === 'activeConnection.username'; + }; + + /** + * Returns whether the wrapped session list is sorted by start date. + * + * @returns {Boolean} + * true if the wrapped session list is sorted by start date, false + * otherwise. + */ + $scope.sortedByStartDate = function sortedByStartDate() { + return $scope.wrapperOrder.primary === 'activeConnection.startDate'; + }; + + /** + * Returns whether the wrapped session list is sorted by remote host. + * + * @returns {Boolean} + * true if the wrapped session list is sorted by remote host, false + * otherwise. + */ + $scope.sortedByRemoteHost = function sortedByRemoteHost() { + return $scope.wrapperOrder.primary === 'activeConnection.remoteHost'; + }; + + /** + * Returns whether the wrapped session list is sorted by connection name. + * + * @returns {Boolean} + * true if the wrapped session list is sorted by connection name, false + * otherwise. + */ + $scope.sortedByConnectionName = function sortedByConnectionName() { + return $scope.wrapperOrder.primary === 'name'; + }; + /** * An action to be provided along with the object sent to showStatus which * closes the currently-shown status dialog. diff --git a/guacamole/src/main/webapp/app/manage/styles/sessions.css b/guacamole/src/main/webapp/app/manage/styles/sessions.css index 102546183..cd5b972d6 100644 --- a/guacamole/src/main/webapp/app/manage/styles/sessions.css +++ b/guacamole/src/main/webapp/app/manage/styles/sessions.css @@ -43,3 +43,15 @@ min-width: 2em; text-align: center; } + +.manage table.session-list th.sort-primary { + text-decoration: underline; +} + +.manage table.session-list th.sort-primary:after { + content: ' \25be'; +} + +.manage table.session-list th.sort-primary.sort-descending:after { + content: ' \25b2'; +} diff --git a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html b/guacamole/src/main/webapp/app/manage/templates/manageSessions.html index 1211cfa93..e2e1db153 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageSessions.html @@ -41,10 +41,18 @@ THE SOFTWARE. - {{'MANAGE_SESSION.TABLE_HEADER_SESSION_USERNAME' | translate}} - {{'MANAGE_SESSION.TABLE_HEADER_SESSION_STARTDATE' | translate}} - {{'MANAGE_SESSION.TABLE_HEADER_SESSION_REMOTEHOST' | translate}} - {{'MANAGE_SESSION.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} + + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_USERNAME' | translate}} + + + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_STARTDATE' | translate}} + + + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_REMOTEHOST' | translate}} + + + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} + From 3c5a0b63f676b4b4ddaa78e431f752631fa40fc8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 23 Mar 2015 14:15:43 -0700 Subject: [PATCH 4/6] GUAC-1140: Update sorting order when session column headers are clicked. --- .../controllers/manageSessionsController.js | 52 ++++++++----------- .../webapp/app/manage/styles/sessions.css | 8 +++ .../app/manage/templates/manageSessions.html | 12 +++-- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js b/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js index c8871e68c..17603e209 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageSessionsController.js @@ -202,47 +202,37 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj }; /** - * Returns whether the wrapped session list is sorted by username. + * Returns whether the wrapped session list is sorted by the given + * property. + * + * @param {String} property + * The name of the property to check. * * @returns {Boolean} - * true if the wrapped session list is sorted by username, false - * otherwise. + * true if the wrapped session list is sorted by the given property, + * false otherwise. */ - $scope.sortedByUsername = function sortedByUsername() { - return $scope.wrapperOrder.primary === 'activeConnection.username'; + $scope.isSortedBy = function isSortedBy(property) { + return $scope.wrapperOrder.primary === property; }; /** - * Returns whether the wrapped session list is sorted by start date. + * Sets the primary sorting property to the given property, if not already + * set. If already set, the ascending/descending sort order is toggled. * - * @returns {Boolean} - * true if the wrapped session list is sorted by start date, false - * otherwise. + * @param {String} property + * The name of the property to assign as the primary sorting property. */ - $scope.sortedByStartDate = function sortedByStartDate() { - return $scope.wrapperOrder.primary === 'activeConnection.startDate'; - }; + $scope.toggleSort = function toggleSort(property) { - /** - * Returns whether the wrapped session list is sorted by remote host. - * - * @returns {Boolean} - * true if the wrapped session list is sorted by remote host, false - * otherwise. - */ - $scope.sortedByRemoteHost = function sortedByRemoteHost() { - return $scope.wrapperOrder.primary === 'activeConnection.remoteHost'; - }; + // Sort in ascending order by new property, if different + if (!$scope.isSortedBy(property)) + $scope.wrapperOrder.reorder(property, false); + + // Otherwise, toggle sort order + else + $scope.wrapperOrder.reorder(property, !$scope.wrapperOrder.descending); - /** - * Returns whether the wrapped session list is sorted by connection name. - * - * @returns {Boolean} - * true if the wrapped session list is sorted by connection name, false - * otherwise. - */ - $scope.sortedByConnectionName = function sortedByConnectionName() { - return $scope.wrapperOrder.primary === 'name'; }; /** diff --git a/guacamole/src/main/webapp/app/manage/styles/sessions.css b/guacamole/src/main/webapp/app/manage/styles/sessions.css index cd5b972d6..7949aad01 100644 --- a/guacamole/src/main/webapp/app/manage/styles/sessions.css +++ b/guacamole/src/main/webapp/app/manage/styles/sessions.css @@ -44,6 +44,14 @@ text-align: center; } +.manage table.session-list th { + cursor: pointer; +} + +.manage table.session-list th.select-session { + cursor: auto; +} + .manage table.session-list th.sort-primary { text-decoration: underline; } diff --git a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html b/guacamole/src/main/webapp/app/manage/templates/manageSessions.html index e2e1db153..d2b29adc8 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageSessions.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageSessions.html @@ -41,16 +41,20 @@ THE SOFTWARE. - + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_USERNAME' | translate}} - + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_STARTDATE' | translate}} - + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_REMOTEHOST' | translate}} - + {{'MANAGE_SESSION.TABLE_HEADER_SESSION_CONNECTION_NAME' | translate}} From 1a05309abcf7ffc8a22a992bf79e74199392b0d6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 23 Mar 2015 14:43:49 -0700 Subject: [PATCH 5/6] GUAC-1140: Add up/down arrows. Use up/down arrows to indicate sort direction. Remove old unused arrows. --- .../webapp/app/manage/styles/sessions.css | 19 +++++++++++++++--- .../main/webapp/images/arrows/arrows-d.png | Bin 3182 -> 0 bytes .../main/webapp/images/arrows/arrows-l.png | Bin 2750 -> 0 bytes .../main/webapp/images/arrows/arrows-r.png | Bin 2784 -> 0 bytes .../main/webapp/images/arrows/arrows-u.png | Bin 3185 -> 0 bytes .../src/main/webapp/images/arrows/down.png | Bin 0 -> 282 bytes .../src/main/webapp/images/arrows/up.png | Bin 0 -> 237 bytes 7 files changed, 16 insertions(+), 3 deletions(-) delete mode 100644 guacamole/src/main/webapp/images/arrows/arrows-d.png delete mode 100644 guacamole/src/main/webapp/images/arrows/arrows-l.png delete mode 100644 guacamole/src/main/webapp/images/arrows/arrows-r.png delete mode 100644 guacamole/src/main/webapp/images/arrows/arrows-u.png create mode 100644 guacamole/src/main/webapp/images/arrows/down.png create mode 100644 guacamole/src/main/webapp/images/arrows/up.png diff --git a/guacamole/src/main/webapp/app/manage/styles/sessions.css b/guacamole/src/main/webapp/app/manage/styles/sessions.css index 7949aad01..30b486828 100644 --- a/guacamole/src/main/webapp/app/manage/styles/sessions.css +++ b/guacamole/src/main/webapp/app/manage/styles/sessions.css @@ -31,6 +31,7 @@ .manage table.session-list th { background: rgba(0, 0, 0, 0.125); + font-weight: normal; } .manage table.session-list th, @@ -53,13 +54,25 @@ } .manage table.session-list th.sort-primary { - text-decoration: underline; + font-weight: bold; + padding-right: 0; } .manage table.session-list th.sort-primary:after { - content: ' \25be'; + + display: inline-block; + width: 1em; + height: 1em; + vertical-align: middle; + content: ' '; + + background-size: 1em 1em; + background-position: right center; + background-repeat: no-repeat; + background-image: url('images/arrows/down.png'); + } .manage table.session-list th.sort-primary.sort-descending:after { - content: ' \25b2'; + background-image: url('images/arrows/up.png'); } diff --git a/guacamole/src/main/webapp/images/arrows/arrows-d.png b/guacamole/src/main/webapp/images/arrows/arrows-d.png deleted file mode 100644 index 15b1a77b3f88cedecdd3f83555401b03f01013af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3182 zcmXw+c{o&UAII-Alifr;D9Khs_Ci8r--aw1TZ9o|OtRAxa+FYd7$Zv$QCT1Rp0$~x z5F!d$vW+FgG)V}(r{{g&b6xkj&;7^!y?(!QU)Sez&Rr`DV+`jJP5=M~XJTND%<%8Q z&WhaIwA{LpXqULys$yZK*H@C)?HT-7`R0M|0kK-cDa)|W?7{x%q|WPZK}h_6TM?!)=83EzqxshfM}0#~lADWBLOm zuD_9nd*}FQ^MRzBd&9tkn5|owhH}Ua0_pRIqT8SyVc2Ye_ivm1Vi=m?BmKp_16KP8 zt@DDxpNZbFJd4_BpyLYSpx~i%ChT=QG+$5~=cfk&f);#dDf;Vd$82Zv@xNblWHXt} zQ$h3Rzw^4im@M=CTl(C!|jPe~FYyEz=1@}#(^ylV_v@QD-dTzmerJSK{q zHwJUJ{cb~6#WtyQr+7rQ zvb1t61*R^bmF_K~xUqlY%p<4rfvK69ppej*_~x_m zK(1C7Dh7vDAA{@8vanA%5q)3mY5()FM#BdFGWN`D)7a$okxIKl(5vQ}Z|m&bOQ+LA zUx=y)=EYc2axo6McvxUOWyo$tvdporxR>GX?!NWWD~DraoJiZwLe;)ZDtRBFB^tM1 zc(eee`Taefk4f2yg-Ai1{hZB2->z!|+2_I?F;x2s7tN?W%{w=JX-&ws7e8>fcr+P0 zC7g`y<%(|0-CJ}FYiXCXj!>&@{&glD7rHv$Xt;Tsjc5P*YIe@yN`XS~g(2qXRGV4r zKmAgGi?R1}4Zk>98;V_aNx;LQ+Y+gs=H?hS?!&yidodh;9M+jsCSWX}vs2e<>wQ#- zv#@vOvZ$?4)0MzQg>*I z*50POk)h!M@bmj}N5Y|#){89Y`A|+?gGt;IX?y`M7_M+VDZ0(mRnFS8!Y9`G>9KWr zQ4!(VH3jsdF>B8erOd#xDXSuFSRc$(9A(^SjlX;Mg7&;9h<$Oxm%#P;`K1V{5ESj; zcNpdYNo__9D5zXOL?$FQgt+}25^>_0deNVl>l%6%K_LeI*n9NX#dt48y--;IS|CD_ z+6D^7AMt#r(7emu@KhXV#eB#3qiDTAf%wQIW#u|6fOo;osI9dcH~c0X*#|&B7fl8n z58-)=4^;r59Wa(e^a@v8z*u*A1$5Lo4G%KA{|{|WLXbL3Wn&g<DWaMOJZ+FbTJ8j{g`tYGNkMKS+$Hc4QR{4to$8gl;sRm_CN~*;`Ox@hPsH!SiKbHI5{>tNt$T1=Ukl2y`7u-*AduHZKw|TVs_g5Xy z){c4}<7&KIw%k;6rW&D|^TUz$yMNT(zMAxN|jqHCmP*HFvBeM29~1{rctEqyhpG4|Fvq0onG=Cs0L2Wo42| z4XrGz>Ykf17LCMCuN6jz@egY0a9N0b4ht*52%Yhc8#|)S>^zRR;FFt9ftxMD=FMWh zwFZ5SskHGrl6|&%kWu1pEOasU3UmppZId!ijfxulaPIti!pY}S)5GKyum2*w<*%7} ztCSO#L;KH?{aU^n4So2~g_1iZ)>f&2uu|82edS}#%1~Km=+uDU^R0rIs9PQ_(AJ$u zXq&v=Z26NSVOEQ_k6dg0VY-Rk(nnV(#3OAe`&5U7gd9EXO%7#%#n9%9*4D?SfAzvW z$BMxt6A}AAJ~f4ugl+=0$n9VbYBK#9rc>@IVxC!#0d;kC$LSLi5>>^;ritG*(AD=k zk-%`D0X80yysLR$x<5Q1GfZquG)l_f|

dQ3Bwt(~16@=H-=&-=f5#NGK? zfCY3t^kHH!n4Zo~c8;Bf2DP-*t%)2tmQxSGrptm3sC1DolVu#qO@Tg zGV!q8wl2=UJ^n!1t(=UE&NpvNw?30F4k>u}%!pxNg(-s;jV`-k|E~H1a(cI_nH>B6^L!GZk>el zkF$s+23v94yQnCnEoeIFh{`?a^jla(jb-t1j@{j%;bCQY`CguzSFg&MnVCtiXG1j_ zEbO;tO+;+Fu8r0AR)8AM-ltZ_yUwFM!#H`du62K@s;Z`gjI6Alr6r#&2I;@l-WcnQ zTI?5p-QMm$_a>EZn=&*MH1Q2_Wi=HyuvVstoM3eQpS`CEL*$XX1j9H}S#X{-{k5%f z)s2v#ac{H=;D9NFJ@f6l0vx8Nn&|Q{kP2HZaRF_=`6}Pa0}-@rFFt^WYj7TzNO diff --git a/guacamole/src/main/webapp/images/arrows/arrows-l.png b/guacamole/src/main/webapp/images/arrows/arrows-l.png deleted file mode 100644 index 91f8150d3eec5dbce03232af5056867cd09d2431..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2750 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 XZ)Xd<)&p61*91f}8`J9FFJAvYpPc5l diff --git a/guacamole/src/main/webapp/images/arrows/arrows-r.png b/guacamole/src/main/webapp/images/arrows/arrows-r.png deleted file mode 100644 index 3ab9d5b2b463f323136c66ce1854d1df05b46406..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2784 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 diff --git a/guacamole/src/main/webapp/images/arrows/arrows-u.png b/guacamole/src/main/webapp/images/arrows/arrows-u.png deleted file mode 100644 index 057cccf95314bead5d62541f8ce45bdbf6192f60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3185 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 z diff --git a/guacamole/src/main/webapp/images/arrows/down.png b/guacamole/src/main/webapp/images/arrows/down.png new file mode 100644 index 0000000000000000000000000000000000000000..cfa9885d88d077ecd7379356d70d5662820f470b GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4B@mpRJL!(}-p_os-CHCU z&i=9KImg|j({0^enC;84bo%}=wOg>QQ$M@UAf_fVxh3)OX?~YQ z=yX+OAL;mey#}>4m8OU8ADRAKaMn@z=-N3?rahVPWY&{QANau5Jn*++P)_*Yw%%XN P8YJN9>gTe~DWM4fv=d~f literal 0 HcmV?d00001 diff --git a/guacamole/src/main/webapp/images/arrows/up.png b/guacamole/src/main/webapp/images/arrows/up.png new file mode 100644 index 0000000000000000000000000000000000000000..e751af83d87a83dcdf1d8049bd7b38ddb39f1a98 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Y)RhkE)4%caKYZ?lYt_f1s;*b zKpodXn9)gNb_Gz7y~NYkmHj@mxTvCOs_??^K%pv67srr_TW_y#$>7+KT4EkjxLvcs8jbtaNQAp>qB#YJap6O zluv6>|9H3mgImpZ Date: Mon, 23 Mar 2015 14:45:21 -0700 Subject: [PATCH 6/6] GUAC-1140: Use new generic down arrow instead of action-specific "open-downward". --- .../main/webapp/app/userMenu/styles/user-menu.css | 2 +- .../images/action-icons/guac-open-downward.png | Bin 282 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 guacamole/src/main/webapp/images/action-icons/guac-open-downward.png diff --git a/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css b/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css index deb81cc84..9d28a3253 100644 --- a/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css +++ b/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css @@ -127,7 +127,7 @@ background-repeat: no-repeat; background-size: 1em; background-position: center center; - background-image: url('images/action-icons/guac-open-downward.png'); + background-image: url('images/arrows/down.png'); } diff --git a/guacamole/src/main/webapp/images/action-icons/guac-open-downward.png b/guacamole/src/main/webapp/images/action-icons/guac-open-downward.png deleted file mode 100644 index cfa9885d88d077ecd7379356d70d5662820f470b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4B@mpRJL!(}-p_os-CHCU z&i=9KImg|j({0^enC;84bo%}=wOg>QQ$M@UAf_fVxh3)OX?~YQ z=yX+OAL;mey#}>4m8OU8ADRAKaMn@z=-N3?rahVPWY&{QANau5Jn*++P)_*Yw%%XN P8YJN9>gTe~DWM4fv=d~f