Merge pull request #17 from glyptodon/cleanup-angular

GUAC-932: Initial cleanup of Angular code
This commit is contained in:
James Muehlner
2014-12-08 14:28:31 -08:00
33 changed files with 728 additions and 787 deletions

View File

@@ -142,8 +142,6 @@
<jsSourceFile>lib/messageformat/messageformat.js</jsSourceFile>
<jsSourceFile>license.txt</jsSourceFile>
<jsSourceFile>guacamole-common-js/all.js</jsSourceFile>
<jsSourceFile>scripts/session.js</jsSourceFile>
<jsSourceFile>scripts/history.js</jsSourceFile>
</jsSourceFiles>
<jsSourceIncludes>

View File

@@ -23,4 +23,4 @@
/**
* The module for authentication and management of tokens.
*/
angular.module('auth', ['util']);
angular.module('auth', ['ngCookies']);

View File

@@ -23,12 +23,13 @@
/**
* A service for authenticating a user against the REST API.
*/
angular.module('auth').factory('authenticationService', ['$http', '$injector',
function authenticationService($http, $injector) {
angular.module('auth').factory('authenticationService', ['$http', '$cookieStore',
function authenticationService($http, $cookieStore) {
var localStorageUtility = $injector.get("localStorageUtility");
var service = {};
var AUTH_COOKIE_ID = "GUAC_AUTH";
/**
* Makes a request to authenticate a user using the token REST API endpoint,
* returning a promise that can be used for processing the results of the call.
@@ -49,8 +50,10 @@ angular.module('auth').factory('authenticationService', ['$http', '$injector',
password: password
})
}).success(function success(data, status, headers, config) {
localStorageUtility.set('authToken', data.authToken);
localStorageUtility.set('userID', data.userID);
$cookieStore.put(AUTH_COOKIE_ID, {
authToken : data.authToken,
userID : data.userID
});
});
};
@@ -68,22 +71,43 @@ angular.module('auth').factory('authenticationService', ['$http', '$injector',
};
/**
* Returns the user ID of the current user.
* Returns the user ID of the current user. If the current user is not
* logged in, this ID may not be valid.
*
* @returns {String} The user ID of the current user.
* @returns {String}
* The user ID of the current user, or null if no authentication data
* is present.
*/
service.getCurrentUserID = function getCurrentUserID() {
return localStorageUtility.get('userID');
// Return user ID, if available
var authData = $cookieStore.get(AUTH_COOKIE_ID);
if (authData)
return authData.userID;
// No auth data present
return null;
};
/**
* Returns the auth token associated with the current user. If the current
* user is not logged in, this token may not be valid.
*
* @returns {String} The auth token associated with the current user.
* @returns {String}
* The auth token associated with the current user, or null if no
* authentication data is present.
*/
service.getCurrentToken = function getCurrentToken() {
return localStorageUtility.get('authToken');
// Return auth token, if available
var authData = $cookieStore.get(AUTH_COOKIE_ID);
if (authData)
return authData.authToken;
// No auth data present
return null;
};
return service;

View File

@@ -23,4 +23,4 @@
/**
* The module for code used to connect to a connection or balancing group.
*/
angular.module('client', []);
angular.module('client', ['auth', 'history']);

View File

@@ -135,7 +135,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
// Get DAO for reading connections and groups
var connectionGroupDAO = $injector.get('connectionGroupDAO');
var connectionDAO = $injector.get('connectionDAO');
var ClientProperties = $injector.get('clientProperties');
var ClientProperties = $injector.get('ClientProperties');
// Client settings and state
$scope.clientProperties = new ClientProperties();
@@ -265,9 +265,6 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
// Show status dialog when client errors occur
$scope.$on('guacClientError', function clientErrorListener(event, client, status) {
// Disconnect
$scope.id = null;
// Determine translation name of error
var errorName = (status in CLIENT_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
@@ -293,6 +290,10 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
// Show new status only if disconnected
if (status === "closed") {
// Disconnect
$scope.id = null;
$scope.showStatus({
title: "client.status.closedStatusTitle",
text: "client.status.tunnelStates." + status
@@ -304,9 +305,6 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams',
// Show status dialog when tunnel errors occur
$scope.$on('guacTunnelError', function tunnelErrorListener(event, tunnel, status) {
// Disconnect
$scope.id = null;
// Determine translation name of error
var errorName = (status in TUNNEL_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";

View File

@@ -30,12 +30,29 @@ angular.module('client').directive('guacClient', [function guacClient() {
restrict: 'E',
replace: true,
scope: {
// Parameters for controlling client state
clientProperties : '=',
/**
* Parameters for controlling client state.
*
* @type ClientProperties|Object
*/
clientProperties : '=',
// Parameters for initially connecting
id : '=',
connectionParameters : '='
/**
* The ID of the Guacamole connection to connect to.
*
* @type String
*/
id : '=',
/**
* Arbitrary URL-encoded parameters to append to the connection
* string when connecting.
*
* @type String
*/
connectionParameters : '='
},
templateUrl: 'app/client/templates/guacClient.html',
controller: ['$scope', '$injector', '$element', function guacClientController($scope, $injector, $element) {
@@ -122,12 +139,13 @@ angular.module('client').directive('guacClient', [function guacClient() {
*/
var touchPad = new Guacamole.Mouse.Touchpad(displayContainer);
var $window = $injector.get('$window'),
guacAudio = $injector.get('guacAudio'),
guacVideo = $injector.get('guacVideo'),
guacTunnelFactory = $injector.get('guacTunnelFactory'),
guacClientFactory = $injector.get('guacClientFactory'),
localStorageUtility = $injector.get('localStorageUtility');
var $window = $injector.get('$window'),
guacAudio = $injector.get('guacAudio'),
guacVideo = $injector.get('guacVideo'),
guacHistory = $injector.get('guacHistory'),
guacTunnelFactory = $injector.get('guacTunnelFactory'),
guacClientFactory = $injector.get('guacClientFactory'),
authenticationService = $injector.get('authenticationService');
/**
* Updates the scale of the attached Guacamole.Client based on current window
@@ -175,7 +193,7 @@ angular.module('client').directive('guacClient', [function guacClient() {
// Build base connect string
var connectString =
"id=" + encodeURIComponent($scope.id)
+ "&authToken=" + encodeURIComponent(localStorageUtility.get('authToken'))
+ "&authToken=" + encodeURIComponent(authenticationService.getCurrentToken())
+ "&width=" + Math.floor(optimal_width)
+ "&height=" + Math.floor(optimal_height)
+ "&dpi=" + Math.floor(optimal_dpi)
@@ -298,12 +316,37 @@ angular.module('client').directive('guacClient', [function guacClient() {
*/
// Connect to given ID whenever ID changes
$scope.$watch('id', function(id) {
$scope.$watch('id', function(id, previousID) {
// If a client is already attached, ensure it is disconnected
if (client)
client.disconnect();
// Update stored thumbnail of previous connection
if (previousID && display && display.getWidth() > 0 && display.getHeight() > 0) {
// Get screenshot
var canvas = display.flatten();
// Calculate scale of thumbnail (max 320x240, max zoom 100%)
var scale = Math.min(320 / canvas.width, 240 / canvas.height, 1);
// Create thumbnail canvas
var thumbnail = document.createElement("canvas");
thumbnail.width = canvas.width*scale;
thumbnail.height = canvas.height*scale;
// Scale screenshot to thumbnail
var context = thumbnail.getContext("2d");
context.drawImage(canvas,
0, 0, canvas.width, canvas.height,
0, 0, thumbnail.width, thumbnail.height
);
guacHistory.updateThumbnail(previousID, thumbnail.toDataURL("image/png"));
}
// Only proceed if a new client is attached
if (!id)
return;

View File

@@ -23,14 +23,20 @@
/**
* A service for generating new guacClient properties objects.
*/
angular.module('client').factory('clientProperties', [function clientProperties() {
angular.module('client').factory('ClientProperties', [function defineClientProperties() {
/**
* Object used for interacting with a guacClient directive.
*
* @constructor
* @param {ClientProperties|Object} [template={}]
* The object whose properties should be copied within the new
* ClientProperties.
*/
return function() {
var ClientProperties = function ClientProperties(template) {
// Use empty object by default
template = template || {};
/**
* Whether the display should be scaled automatically to fit within the
@@ -38,7 +44,7 @@ angular.module('client').factory('clientProperties', [function clientProperties(
*
* @type Boolean
*/
this.autoFit = true;
this.autoFit = template.autoFit || true;
/**
* The current scale. If autoFit is true, the effect of setting this
@@ -46,28 +52,28 @@ angular.module('client').factory('clientProperties', [function clientProperties(
*
* @type Number
*/
this.scale = 1;
this.scale = template.scale || 1;
/**
* The minimum scale value.
*
* @type Number
*/
this.minScale = 1;
this.minScale = template.minScale || 1;
/**
* The maximum scale value.
*
* @type Number
*/
this.maxScale = 3;
this.maxScale = template.maxScale || 3;
/**
* Whether or not the client should listen to keyboard events.
*
* @type Boolean
*/
this.keyboardEnabled = true;
this.keyboardEnabled = template.keyboardEnabled || true;
/**
* Whether translation of touch to mouse events should emulate an
@@ -75,8 +81,10 @@ angular.module('client').factory('clientProperties', [function clientProperties(
*
* @type Boolean
*/
this.emulateAbsoluteMouse = true;
this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || true;
};
return ClientProperties;
}]);

View File

@@ -23,4 +23,4 @@
/**
* The module for code relating to connections.
*/
angular.module('connection', ['util']);
angular.module('connection', ['auth']);

View File

@@ -23,8 +23,8 @@
/**
* The DAO for connection operations agains the REST API.
*/
angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUtility',
function connectionDAO($http, localStorageUtility) {
angular.module('connection').factory('connectionDAO', ['$http', 'authenticationService',
function connectionDAO($http, authenticationService) {
var service = {};
@@ -36,7 +36,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
* @returns {promise} A promise for the HTTP call.
*/
service.getConnection = function getConnection(id) {
return $http.get("api/connection/" + id + "?token=" + localStorageUtility.get('authToken'));
return $http.get("api/connection/" + id + "?token=" + authenticationService.getCurrentToken());
};
/**
@@ -55,7 +55,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
if(parentID !== undefined)
parentIDParam = "&parentID=" + parentID;
return $http.get("api/connection?token=" + localStorageUtility.get('authToken') + parentIDParam);
return $http.get("api/connection?token=" + authenticationService.getCurrentToken() + parentIDParam);
};
/**
@@ -74,7 +74,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
// This is a new connection
if(!connectionToSave.identifier) {
return $http.post("api/connection/?token=" + localStorageUtility.get('authToken'), connectionToSave).success(
return $http.post("api/connection/?token=" + authenticationService.getCurrentToken(), connectionToSave).success(
function setConnectionID(connectionID){
// Set the identifier on the new connection
connection.identifier = connectionID;
@@ -83,7 +83,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
} else {
return $http.post(
"api/connection/" + connectionToSave.identifier +
"?token=" + localStorageUtility.get('authToken'),
"?token=" + authenticationService.getCurrentToken(),
connectionToSave);
}
};
@@ -100,7 +100,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
return $http.put(
"api/connection/" + connection.identifier +
"?token=" + localStorageUtility.get('authToken') +
"?token=" + authenticationService.getCurrentToken() +
"&parentID=" + connection.parentIdentifier,
connection);
@@ -117,7 +117,7 @@ angular.module('connection').factory('connectionDAO', ['$http', 'localStorageUti
service.deleteConnection = function deleteConnection(connection) {
return $http['delete'](
"api/connection/" + connection.identifier +
"?token=" + localStorageUtility.get('authToken'));
"?token=" + authenticationService.getCurrentToken());
};
return service;

View File

@@ -23,4 +23,4 @@
/**
* The module for code relating to connection groups.
*/
angular.module('connectionGroup', ['util', 'connection']);
angular.module('connectionGroup', ['auth', 'util', 'connection']);

View File

@@ -23,8 +23,8 @@
/**
* The DAO for connection group operations agains the REST API.
*/
angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'localStorageUtility',
function connectionGrouDAO($http, localStorageUtility) {
angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'authenticationService',
function connectionGrouDAO($http, authenticationService) {
/**
* The ID of the root connection group.
@@ -49,7 +49,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
if(parentID !== undefined)
parentIDParam = "&parentID=" + parentID;
return $http.get("api/connectionGroup?token=" + localStorageUtility.get('authToken') + parentIDParam);
return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam);
};
/**
@@ -67,7 +67,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
// Use the root connection group ID if no ID is passed in
connectionGroupID = connectionGroupID || ROOT_CONNECTION_GROUP_ID;
return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + localStorageUtility.get('authToken'));
return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken());
};
/**
@@ -81,7 +81,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
service.saveConnectionGroup = function saveConnectionGroup(connectionGroup) {
// This is a new connection group
if(!connectionGroup.identifier) {
return $http.post("api/connectionGroup/?token=" + localStorageUtility.get('authToken'), connectionGroup).success(
return $http.post("api/connectionGroup/?token=" + authenticationService.getCurrentToken(), connectionGroup).success(
function setConnectionGroupID(connectionGroupID){
// Set the identifier on the new connection
connectionGroup.identifier = connectionGroupID;
@@ -90,7 +90,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
} else {
return $http.post(
"api/connectionGroup/" + connectionGroup.identifier +
"?token=" + localStorageUtility.get('authToken'),
"?token=" + authenticationService.getCurrentToken(),
connectionGroup);
}
};
@@ -107,7 +107,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
return $http.put(
"api/connectionGroup/" + connectionGroup.identifier +
"?token=" + localStorageUtility.get('authToken') +
"?token=" + authenticationService.getCurrentToken() +
"&parentID=" + connectionGroup.parentIdentifier,
connectionGroup);
};
@@ -123,7 +123,7 @@ angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'local
service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) {
return $http['delete'](
"api/connectionGroup/" + connectionGroup.identifier +
"?token=" + localStorageUtility.get('authToken'));
"?token=" + authenticationService.getCurrentToken());
};
return service;

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2014 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.
*/
/**
* The module for code relating to connection history.
*/
angular.module('history', []);

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2014 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 reading and manipulating the Guacamole connection history.
*/
angular.module('history').factory('guacHistory', ['HistoryEntry', function guacHistory(HistoryEntry) {
var service = {};
// The parameter name for getting the history from local storage
var GUAC_HISTORY_STORAGE_KEY = "GUAC_HISTORY";
/**
* The number of entries to allow before removing old entries based on the
* cutoff.
*/
var IDEAL_LENGTH = 6;
/**
* The top few recent connections, sorted in order of most recent access.
*
* @type HistoryEntry[]
*/
service.recentConnections = [];
/**
* Updates the thumbnail and access time of the history entry for the
* connection with the given ID.
*
* @param {String} id
* The ID of the connection whose history entry should be updated.
*
* @param {String} thumbnail
* The URL of the thumbnail image to associate with the history entry.
*/
service.updateThumbnail = function(id, thumbnail) {
var i;
// Remove any existing entry for this connection
for (i=0; i < service.recentConnections.length; i++) {
if (service.recentConnections[i].id === id) {
service.recentConnections.splice(i, 1);
break;
}
}
// Store new entry in history
service.recentConnections.unshift(new HistoryEntry(
id,
thumbnail,
new Date().getTime()
));
// Truncate history to ideal length
if (service.recentConnections.length > IDEAL_LENGTH)
service.recentConnections.length = IDEAL_LENGTH;
// Save updated history, ignore inability to use localStorage
try {
if (localStorage)
localStorage.setItem(GUAC_HISTORY_STORAGE_KEY, JSON.stringify(service.recentConnections));
}
catch (ignore) {}
};
// Get stored connection history, ignore inability to use localStorage
try {
if (localStorage) {
var storedHistory = JSON.parse(localStorage.getItem(GUAC_HISTORY_STORAGE_KEY) || "[]");
if (storedHistory instanceof Array)
service.recentConnections = storedHistory;
}
}
catch (ignore) {}
return service;
}]);

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2014 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.
*/
/**
* Provides the HistoryEntry class used by the guacHistory service.
*/
angular.module('history').factory('HistoryEntry', [function defineHistoryEntry() {
/**
* A single entry in the connection history.
*
* @constructor
* @param {String} id The ID of the connection.
*
* @param {String} thumbnail
* The URL of the thumbnail to use to represent the connection.
*/
var HistoryEntry = function HistoryEntry(id, thumbnail) {
/**
* The ID of the connection associated with this history entry.
*/
this.id = id;
/**
* The thumbnail associated with the connection associated with this
* history entry.
*/
this.thumbnail = thumbnail;
};
return HistoryEntry;
}]);

View File

@@ -31,7 +31,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
// Get the dependencies commonJS style
var connectionGroupService = $injector.get("connectionGroupService");
var localStorageUtility = $injector.get("localStorageUtility");
var guacHistory = $injector.get("guacHistory");
// All the connections and connection groups in root
$scope.connectionsAndGroups = [];
@@ -48,14 +48,14 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
connectionGroupService.getAllGroupsAndConnections($scope.connectionsAndGroups)
.then(function findRecentConnections() {
// Try to parse out the recent connections from local storage
var recentConnections;
try {
recentConnections = JSON.parse(localStorageUtility.get(GUAC_HISTORY_STORAGE_KEY));
} catch(e) {
// The recent history is corrupted - clear it
localStorageUtility.clear(GUAC_HISTORY_STORAGE_KEY);
// TODONT: Munch the guacHistory recentConnections list into a legacy-style object
var recentConnections = {};
for (var i=0; i < guacHistory.recentConnections.length; i++) {
var entry = guacHistory.recentConnections[i];
recentConnections[encodeURIComponent(entry.id)] = {
id : entry.id,
thumbnail : entry.thumbnail
};
}
// Figure out which recent connection entries are valid

View File

@@ -20,4 +20,4 @@
* THE SOFTWARE.
*/
angular.module('home', ['connection', 'connectionGroup', 'user', 'permission']);
angular.module('home', ['connection', 'connectionGroup', 'history', 'user', 'permission']);

View File

@@ -61,7 +61,7 @@
<div ng-repeat="recentConnection in recentConnections" class="connection">
<a href="#/client/{{recentConnection.type}}/{{recentConnection.id}}/{{recentConnection.name}}">
<div class="thumbnail">
<img alt="{{recentConnection.name}}" src="{{recentConnection.thumbnail}}"/>
<img alt="{{recentConnection.name}}" ng-src="{{recentConnection.thumbnail}}"/>
</div>
<div class="caption">
<span class="name">{{recentConnection.name}}</span>

View File

@@ -50,6 +50,21 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
}
};
/**
* The current status notification, or false if no status is currently
* shown.
*
* @type Notification|Boolean
*/
$scope.status = false;
/**
* All currently-visible notifications.
*
* @type Notification[]
*/
$scope.notifications = [];
// Put some useful variables in the top level scope
$scope.page = {
title: '',
@@ -59,7 +74,6 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
$scope.currentUserIsAdmin = false;
$scope.currentUserHasUpdate = false;
$scope.currentUserPermissions = null;
$scope.notifications = [];
var notificationUniqueID = 0;
// A promise to be fulfilled when all basic user permissions are loaded.
@@ -77,41 +91,8 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
* notification is currently shown, no further statuses will be shown
* until the current status is hidden.
*
* @param {Object} status The status notification to show.
* @param {String} [status.title] The title of the notification.
* @param {String} [status.text] The body text of the notification.
* @param {String} [status.className] The CSS class name to apply.
*
* @param {String} [status.countdown.text]
* In the case that a countdown applies to the notification, the text to
* display while the countdown is active.
*
* @param {Function} [status.countdown.callback]
* The callback to call when the countdown expires.
*
* @param {String} [status.countdown.remaining]
* The number of seconds remaining before the countdown callback is
* called.
*
* @param {String} [status.progress.text]
* If this notification has associated progress, the text to display
* while the operation is occurring.
*
* @param {String} [status.progress.value]
* The current state of operation progress, as an arbitrary number
* which increases as the operation continues.
*
* @param {String} [status.progress.unit]
* The unit of the arbitrary status.progress.value, if that value has
* an associated unit.
*
* @param {String} [status.progress.ratio]
* If known, the current status of the operation as a value between
* 0 and 1 inclusive, where 0 is not yet started, and 1 is complete.
*
* @param {Object[]} [status.actions]
* Array of action objects which contain an action name and callback to
* be executed when that action is invoked.
* @param {Notification|Boolean|Object} status
* The status notification to show.
*
* @example
*
@@ -138,43 +119,10 @@ angular.module('index').controller('indexController', ['$scope', '$injector',
/**
* Adds a notification to the the list of notifications shown.
*
* @param {Object} notification The notification to add.
* @param {String} [notification.title] The title of the notification.
* @param {String} [notification.text] The body text of the notification.
* @param {String} [notification.className] The CSS class name to apply.
* @param {Notification|Object} notification The notification to add.
*
* @param {String} [notification.countdown.text]
* In the case that a countdown applies to the notification, the text to
* display while the countdown is active.
*
* @param {Function} [notification.countdown.callback]
* The callback to call when the countdown expires.
*
* @param {String} [notification.countdown.remaining]
* The number of seconds remaining before the countdown callback is
* called.
*
* @param {String} [notification.progress.text]
* If this notification has associated progress, the text to display
* while the operation is occurring.
*
* @param {String} [notification.progress.value]
* The current state of operation progress, as an arbitrary number
* which increases as the operation continues.
*
* @param {String} [notification.progress.unit]
* The unit of the arbitrary notification.progress.value, if that value
* has an associated unit.
*
* @param {String} [notification.progress.ratio]
* If known, the current status of the operation as a value between
* 0 and 1 inclusive, where 0 is not yet started, and 1 is complete.
*
* @param {Object[]} [notification.actions]
* Array of action objects which contain an action name and callback to
* be executed when that action is invoked.
*
* @returns {Number} A unique ID for the notification that's just been added.
* @returns {Number}
* A unique ID for the notification that's just been added.
*
* @example
*

View File

@@ -31,110 +31,11 @@ angular.module('notification').directive('guacNotification', [function guacNotif
scope: {
/**
* The CSS class to apply to the notification.
* The notification to display.
*
* @type String
* @type Notification|Object
*/
className : '=',
/**
* The title of the notification.
*
* @type String
*/
title : '=',
/**
* The body text of the notification.
*
* @type String
*/
text : '=',
/**
* The text to use for displaying the countdown. For the sake of
* i18n, the variable REMAINING will be applied within the
* translation string for formatting plurals, etc.
*
* @type String
* @example
* "Only {REMAINING} {REMAINING, plural, one{second} other{seconds}} remain."
*/
countdownText : '=',
/**
* The number of seconds to wait before automatically calling the
* default callback.
*
* @type Number
*/
countdown : '=',
/**
* The function to call when timeRemaining expires. If timeRemaining
* is not set, this does not apply.
*
* @type Function
*/
defaultCallback : '=',
/**
* The text to use for displaying the progress. For the sake of
* i18n, the variable PROGRESS will be applied within the
* translation string for formatting plurals, etc., while the
* variable UNIT will be applied, if needed, for whatever units
* are applicable to the progress display.
*
* @type String
* @example
* "{PROGRESS} {UNIT, select, b{B} kb{KB}} uploaded."
*/
progressText : '=',
/**
* The unit which applies to the progress indicator, if any. This
* will be substituted in the progressText string with the UNIT
* variable.
*
* @type String
*/
progressUnit : '=',
/**
* Arbitrary value denoting how much progress has been made
* in some ongoing task that this notification represents.
*
* @type Number
*/
progress : '=',
/**
* Value between 0 and 1 denoting what proportion of the operation
* has completed. If known, this value should be 0 if the operation
* has not started, and 1 if the operation is complete.
*
* @type Number
*/
progressRatio : '=',
/**
* Array of name/callback pairs for each action the user is allowed
* to take once the notification is shown.
*
* @type Array
* @example
* [
* {
* name : "Action 1 name",
* callback : actionCallback1
* },
* {
* name : "Action 2 text",
* callback : actionCallback2
* }
* ]
*/
actions : '='
notification : '='
},
@@ -142,21 +43,22 @@ angular.module('notification').directive('guacNotification', [function guacNotif
controller: ['$scope', '$interval', function guacNotificationController($scope, $interval) {
// Update progress bar if end known
$scope.$watch("progressRatio", function updateProgress(ratio) {
$scope.$watch("notification.progress.ratio", function updateProgress(ratio) {
$scope.progressPercent = ratio * 100;
});
// Set countdown interval when associated property is set
$scope.$watch("countdown", function resetTimeRemaining(countdown) {
$scope.$watch("notification", function resetTimeRemaining(notification) {
$scope.timeRemaining = countdown;
var countdown = notification.countdown;
// Clean up any existing interval
if ($scope.interval)
$interval.cancel($scope.interval);
// Update and handle countdown, if provided
if ($scope.timeRemaining) {
if (countdown) {
$scope.timeRemaining = countdown.remaining;
$scope.interval = $interval(function updateTimeRemaining() {
@@ -164,8 +66,8 @@ angular.module('notification').directive('guacNotification', [function guacNotif
$scope.timeRemaining--;
// Call countdown callback when time remaining expires
if ($scope.timeRemaining === 0 && $scope.defaultCallback)
$scope.defaultCallback();
if ($scope.timeRemaining === 0)
countdown.performAction();
}, 1000, $scope.timeRemaining);
@@ -184,4 +86,4 @@ angular.module('notification').directive('guacNotification', [function guacNotif
}]
};
}]);
}]);

View File

@@ -1,4 +1,4 @@
<div class="notification" ng-class="className">
<div class="notification" ng-class="notification.className">
<!--
Copyright (C) 2014 Glyptodon LLC
@@ -22,26 +22,26 @@
-->
<!-- Notification title -->
<div ng-show="title" class="title-bar">
<div class="title">{{title | translate}}</div>
<div ng-show="notification.title" class="title-bar">
<div class="title">{{notification.title | translate}}</div>
</div>
<div class="body">
<!-- Notification text -->
<p ng-show="text" class="text">{{text | translate}}</p>
<p ng-show="notification.text" class="text">{{notification.text | translate}}</p>
<!-- Current progress -->
<div ng-show="progressText" class="progress"><div ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}" class="bar"></div><div class="text">{{progressText | translate:"{ PROGRESS: progress, UNIT: progressUnit }"}}</div></div>
<div ng-show="notification.progress" class="progress"><div ng-show="progressPercent" ng-style="{'width': progressPercent + '%'}" class="bar"></div><div ng-show="notification.progress.text" class="text">{{notification.progress.text | translate:"{ PROGRESS: notification.progress.value, UNIT: notification.progress.unit}"}}</div></div>
<!-- Default action countdown text -->
<p ng-show="countdownText" class="countdown-text">{{countdownText | translate:"{ REMAINING: timeRemaining }"}}</p>
<p ng-show="notification.countdown.text" class="countdown-text">{{notification.countdown.text | translate:"{ REMAINING: timeRemaining}"}}</p>
</div>
<!-- Buttons -->
<div ng-show="actions && actions.length" class="buttons">
<button ng-repeat="action in actions" ng-click="action.callback()">{{action.name | translate}}</button>
<div ng-show="notification.actions.length" class="buttons">
<button ng-repeat="action in notification.actions" ng-click="action.callback()">{{action.name | translate}}</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2014 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.
*/
/**
* Provides the Notification class definition.
*/
angular.module('notification').factory('Notification', [function defineNotification() {
/**
* Creates a new Notification, initializing the properties of that
* Notification with the corresponding properties of the given template.
*
* @constructor
* @param {Notification|Object} [template={}]
* The object whose properties should be copied within the new
* Notification.
*/
var Notification = function Notification(template) {
// Use empty object by default
template = template || {};
/**
* The CSS class to associate with the notification, if any.
*
* @type String
*/
this.className = template.className;
/**
* The title of the notification.
*
* @type String
*/
this.title = template.title;
/**
* The body text of the notification.
*
* @type String
*/
this.text = template.text;
/**
* An array of all actions available to the user in response to this
* notification.
*
* @type NotificationAction[]
*/
this.actions = template.actions || [];
/**
* The current progress state of the ongoing action associated with this
* notification.
*
* @type NotificationProgress
*/
this.progress = template.progress;
/**
* The countdown and corresponding default action which applies to
* this notification, if any.
*
* @type NotificationCountdown
*/
this.countdown = template.countdown;
};
return Notification;
}]);

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2014 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.
*/
/**
* Provides the NotificationAction class definition.
*/
angular.module('notification').factory('NotificationAction', [function defineNotificationAction() {
/**
* Creates a new NotificationAction, which pairs an arbitrary callback with
* an action name. The name of this action will ultimately be presented to
* the user when the user is prompted to choose among available actions.
*
* @constructor
* @param {String} name The name of this action.
*
* @param {Function} callback
* The callback to call when the user elects to perform this action.
*/
var NotificationAction = function NotificationAction(name, callback) {
/**
* Reference to this NotificationAction.
*
* @type NotificationAction
*/
var action = this;
/**
* The name of this action.
*
* @type String
*/
this.name = name;
/**
* The callback to call when this action is performed.
*
* @type Function
*/
this.callback = callback;
/**
* Calls the callback associated with this NotificationAction, if any.
* If no callback is associated with this NotificationAction, this
* function has no effect.
*/
this.performAction = function performAction() {
if (action.callback)
action.callback();
};
};
return NotificationAction;
}]);

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2014 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.
*/
/**
* Provides the NotificationCountdown class definition.
*/
angular.module('notification').factory('NotificationCountdown', [function defineNotificationCountdown() {
/**
* Creates a new NotificationCountdown which describes an action that
* should be performed after a specific number of seconds has elapsed.
*
* @constructor
* @param {String} text The body text of the notification countdown.
*
* @param {Number} remaining
* The number of seconds remaining in the countdown.
*
* @param {Function} [callback]
* The callback to call when the countdown elapses.
*/
var NotificationCountdown = function NotificationCountdown(text, remaining, callback) {
/**
* Reference to this NotificationCountdown.
*
* @type NotificationCountdown
*/
var countdown = this;
/**
* The body text of the notification countdown. For the sake of i18n,
* the variable REMAINING should be applied within the translation
* string for formatting plurals, etc.
*
* @type String
*/
this.text = text;
/**
* The number of seconds remaining in the countdown. After this number
* of seconds elapses, the callback associated with this
* NotificationCountdown will be called.
*
* @type Number
*/
this.remaining = remaining;
/**
* The callback to call when this countdown expires.
*
* @type Function
*/
this.callback = callback;
/**
* Calls the callback associated with this NotificationCountdown, if any.
* If no callback is associated with this NotificationCountdown, this
* function has no effect.
*/
this.performAction = function performAction() {
if (countdown.callback)
countdown.callback();
};
};
return NotificationCountdown;
}]);

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2014 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.
*/
/**
* Provides the NotificationProgress class definition.
*/
angular.module('notification').factory('NotificationProgress', [function defineNotificationProgress() {
/**
* Creates a new NotificationProgress which describes the current status
* of an operation, and how much of that operation remains to be performed.
*
* @constructor
* @param {String} text The text describing the operation progress.
*
* @param {Number} value
* The current state of operation progress, as an arbitrary number
* which increases as the operation continues.
*
* @param {String} [unit]
* The unit of the arbitrary value, if that value has an associated
* unit.
*
* @param {Number} [ratio]
* If known, the current status of the operation as a value between 0
* and 1 inclusive, where 0 is not yet started, and 1 is complete.
*/
var NotificationProgress = function NotificationProgress(text, value, unit, ratio) {
/**
* The text describing the operation progress. For the sake of i18n,
* the variable PROGRESS should be applied within the translation
* string for formatting plurals, etc., while UNIT should be used
* for the progress unit, if any.
*
* @type String
*/
this.text = text;
/**
* The current state of operation progress, as an arbitrary number which
* increases as the operation continues.
*
* @type Number
*/
this.value = value;
/**
* The unit of the arbitrary value, if that value has an associated
* unit.
*
* @type String
*/
this.unit = unit;
/**
* If known, the current status of the operation as a value between 0
* and 1 inclusive, where 0 is not yet started, and 1 is complete.
*
* @type String
*/
this.ratio = ratio;
};
return NotificationProgress;
}]);

View File

@@ -23,4 +23,4 @@
/**
* A module for code relating to permissions.
*/
angular.module('permission', []);
angular.module('permission', ['auth']);

View File

@@ -23,8 +23,8 @@
/**
* The DAO for permission operations agains the REST API.
*/
angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUtility',
function permissionDAO($http, localStorageUtility) {
angular.module('permission').factory('permissionDAO', ['$http', 'authenticationService',
function permissionDAO($http, authenticationService) {
var service = {};
@@ -37,7 +37,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
* @returns {promise} A promise for the HTTP call.
*/
service.getPermissions = function getPermissions(userID) {
return $http.get("api/permission/" + userID + "/?token=" + localStorageUtility.get('authToken'));
return $http.get("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken());
};
/**
@@ -50,7 +50,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
* @returns {promise} A promise for the HTTP call.
*/
service.addPermission = function addPermission(userID, permission) {
return $http.post("api/permission/" + userID + "/?token=" + localStorageUtility.get('authToken'), permission);
return $http.post("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission);
};
@@ -65,7 +65,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
* @returns {promise} A promise for the HTTP call.
*/
service.removePermission = function removePermission(userID, permission) {
return $http.post("api/permission/remove/" + userID + "/?token=" + localStorageUtility.get('authToken'), permission);
return $http.post("api/permission/remove/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission);
};
@@ -103,7 +103,7 @@ angular.module('permission').factory('permissionDAO', ['$http', 'localStorageUti
// Make the HTTP call
return $http({
method : 'PATCH',
url : "api/permission/?token=" + localStorageUtility.get('authToken'),
url : "api/permission/?token=" + authenticationService.getCurrentToken(),
data : permissionPatch
});
}

View File

@@ -23,8 +23,8 @@
/**
* The DAO for connection operations agains the REST API.
*/
angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
function userDAO($http, localStorageUtility) {
angular.module('user').factory('userDAO', ['$http', 'authenticationService',
function userDAO($http, authenticationService) {
var service = {};
@@ -35,7 +35,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
* @returns {promise} A promise for the HTTP call.
*/
service.getUsers = function getUsers() {
return $http.get("api/user?token=" + localStorageUtility.get('authToken'));
return $http.get("api/user?token=" + authenticationService.getCurrentToken());
};
/**
@@ -47,7 +47,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
* @returns {promise} A promise for the HTTP call.
*/
service.getUser = function getUser(userID) {
return $http.get("api/user/" + userID + "/?token=" + localStorageUtility.get('authToken'));
return $http.get("api/user/" + userID + "/?token=" + authenticationService.getCurrentToken());
};
/**
@@ -61,7 +61,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
service.deleteUser = function deleteUser(user) {
return $http['delete'](
"api/user/" + user.username +
"?token=" + localStorageUtility.get('authToken'));
"?token=" + authenticationService.getCurrentToken());
};
@@ -76,7 +76,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
service.createUser = function createUser(user) {
return $http.post(
"api/user/"
+ "?token=" + localStorageUtility.get('authToken'),
+ "?token=" + authenticationService.getCurrentToken(),
user
);
}
@@ -92,7 +92,7 @@ angular.module('user').factory('userDAO', ['$http', 'localStorageUtility',
service.saveUser = function saveUser(user) {
return $http.post(
"api/user/" + user.username +
"?token=" + localStorageUtility.get('authToken'),
"?token=" + authenticationService.getCurrentToken(),
user);
};

View File

@@ -23,4 +23,4 @@
/**
* A module for code relating to users.
*/
angular.module('user', []);
angular.module('user', ['auth']);

View File

@@ -1,97 +0,0 @@
/*
* Copyright (C) 2014 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 handling storage and retrieval of values on localStorage.
* If local storage is not available, cookies will be used as a fallback.
*/
angular.module('util').factory('localStorageUtility', ['$cookieStore',
function localStorageUtility($cookieStore) {
var service = {};
// The prefix to use when storing cookies
var COOKIE_PREFIX = "guacamole.ui.localstorage.";
// Check if we can actually use localStorage
var localStorageEnabled;
try {
window.localStorage.setItem("test", "test");
window.localStorage.removeItem("test");
localStorageEnabled = true;
} catch(e) {
localStorageEnabled = false;
}
var getFunc, setFunc;
if(localStorageEnabled) {
// Just a passthrough to localStorage
getFunc = function getFromLocalStorage(key) {
return window.localStorage.getItem(key);
};
setFunc = function setOnLocalStorage(key, value) {
return window.localStorage.setItem(key, value);
};
}
else {
// Store the values as cookies
getFunc = function getValueFromCookie(key) {
return $cookieStore.get(COOKIE_PREFIX + key);
};
setFunc = function setValueOnCookie(key, value) {
return $cookieStore.put(COOKIE_PREFIX + key, value);
}
}
/**
* Gets a value from the persistent local store.
*
* @param {string} key The key to use as an index into the map.
*
* @returns {string} The value, if found.
*/
service.get = getFunc;
/**
* Sets a value on the persistent local store.
*
* @param {string} key The key to use as an index into the map.
* @param {string} value The value to store in the map.
*/
service.set = setFunc;
/**
* Clear a value from the persistent local store.
*
* @param {string} key The key to clear from the map.
*/
service.clear = function clear(key) {
return service.set(key, undefined);
};
return service;
}]);

View File

@@ -23,4 +23,4 @@
/**
* A module for miscellaneous services and utilities that don't belong elsewhere.
*/
angular.module('util', ['ngCookies']);
angular.module('util', []);

View File

@@ -37,20 +37,7 @@ THE SOFTWARE.
<!-- Global status/error dialog -->
<div ng-class="{shown: status}" class="status-outer">
<div class="status-middle">
<guac-notification
class-name="status.className"
title="status.title"
text="status.text"
progress-text="status.progress.text"
progress-unit="status.progress.unit"
progress-ratio="status.progress.ratio"
progress="status.progress.value"
actions="status.actions"
countdown-text="status.countdown.text"
countdown="status.countdown.remaining"
default-callback="status.countdown.callback"/>
<guac-notification notification="status"/>
</div>
</div>
@@ -60,20 +47,7 @@ THE SOFTWARE.
<!-- Notification area -->
<div id="notificationArea">
<div ng-repeat="wrapper in notifications">
<guac-notification
class-name="wrapper.notification.className"
title="wrapper.notification.title"
text="wrapper.notification.text"
progress-text="wrapper.notification.progress.text"
progress-unit="wrapper.notification.progress.unit"
progress-ratio="wrapper.notification.progress.ratio"
progress="wrapper.notification.progress.value"
actions="wrapper.notification.actions"
countdown-text="wrapper.notification.countdown.text"
countdown="wrapper.notification.countdown.remaining"
default-callback="wrapper.notification.countdown.callback"/>
<guac-notification notification="wrapper.notification"/>
<div>
</div>

View File

@@ -1,212 +0,0 @@
/*
* Copyright (C) 2013 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.
*/
/**
* Set of thumbnails for each connection, indexed by ID.
*/
GuacamoleHistory = new (function() {
/**
* Reference to this GuacamoleHistory.
*/
var guac_history = this;
/**
* The number of entries to allow before removing old entries based on the
* cutoff.
*/
var IDEAL_LENGTH = 6;
/**
* The maximum age of a history entry before it is removed, in
* milliseconds.
*/
var CUTOFF_AGE = 900000;
var history = {};
function truncate() {
// Build list of entries
var entries = [];
for (var old_id in history)
entries.push(history[old_id]);
// Avoid history growth beyond defined number of entries
if (entries.length > IDEAL_LENGTH) {
// Sort list
entries.sort(GuacamoleHistory.Entry.compare);
// Remove entries until length is ideal or all are recent
var now = new Date().getTime();
while (entries.length > IDEAL_LENGTH
&& now - entries[0].accessed > CUTOFF_AGE) {
// Remove entry
var removed = entries.shift();
delete history[removed.id];
}
}
}
/**
* Returns the URL for the thumbnail of the connection with the given ID,
* or undefined if no thumbnail is associated with that connection.
*/
this.get = function(id) {
return history[id] || new GuacamoleHistory.Entry();
};
/**
* Updates the thumbnail and access time of the history entry for the
* connection with the given ID.
*/
this.update = function(id, thumbnail) {
/* Do nothing if localStorage not present */
if (!localStorage)
return;
// Create updated entry
var entry = new GuacamoleHistory.Entry(
id,
thumbnail,
new Date().getTime()
);
// Store entry in history
history[id] = entry;
truncate();
// Save updated history, ignore inability to use localStorage
try {
localStorage.setItem("GUAC_HISTORY", JSON.stringify(history));
}
catch (ignore) {}
};
/**
* Reloads all history data.
*/
this.reload = function() {
/* Do nothing if localStorage not present */
if (!localStorage)
return;
// Get old and new for comparison, ignore inability to use localStorage
var old_history = history;
try {
var new_history = JSON.parse(localStorage.getItem("GUAC_HISTORY") || "{}");
}
catch (ignore) {
return;
}
// Update history
history = new_history;
// Call onchange handler as necessary
if (guac_history.onchange) {
// Produce union of all known IDs
var known_ids = {};
for (var new_id in new_history) known_ids[new_id] = true;
for (var old_id in old_history) known_ids[old_id] = true;
// For each known ID
for (var id in known_ids) {
// Get entries
var old_entry = old_history[id];
var new_entry = new_history[id];
// Call handler for all changed
if (!old_entry || !new_entry
|| old_entry.accessed != new_entry.accessed)
guac_history.onchange(id, old_entry, new_entry);
}
} // end onchange
};
/**
* Event handler called whenever a history entry is changed.
*
* @event
* @param {String} id The ID of the connection whose history entry is
* changing.
* @param {GuacamoleHistory.Entry} old_entry The old value of the entry, if
* any.
* @param {GuacamoleHistory.Entry} new_entry The new value of the entry, if
* any.
*/
this.onchange = null;
// Reload when modified
window.addEventListener("storage", guac_history.reload, false);
// Initial load
guac_history.reload();
})();
/**
* A single entry in the indexed connection usage history.
*
* @constructor
* @param {String} id The ID of this connection.
* @param {String} thumbnail The URL of the thumbnail to use to represent this
* connection.
* @param {Number} last_access The time this connection was last accessed, in
* seconds.
*/
GuacamoleHistory.Entry = function(id, thumbnail, last_access) {
/**
* The ID of the connection associated with this history entry.
*/
this.id = id;
/**
* The thumbnail associated with the connection associated with this history
* entry.
*/
this.thumbnail = thumbnail;
/**
* The time the connection associated with this entry was last accessed.
*/
this.accessed = last_access;
};
GuacamoleHistory.Entry.compare = function(a, b) {
return a.accessed - b.accessed;
};

View File

@@ -1,171 +0,0 @@
/*
* Copyright (C) 2013 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.
*/
/**
* Global storage for Guacamole pages.
*/
GuacamoleSessionStorage = (function() {
// Retrieve storage from owner of window, if possible
var opener_storage = null;
try {
opener_storage = opener && opener.GuacamoleSessionStorage;
}
catch (e) {}
return opener_storage;
})() || new (function() {
/**
* The contents of storage, as a JSON string containing name/value pairs as
* properties.
*
* @private
* @type String
*/
var stored_json = "{}";
/**
* Called whenever an item value changes.
*
* @callback onchange
* @param {String} name The name of the item changed.
* @param value The new item value.
*/
/**
* All attached listeners.
*
* @type onchange[]
*/
var listeners = [];
/**
* Notifies all listeners that an item has changed.
*
* @param {String} name The name of the item that changed.
* @param value The new item value.
*/
function __notify_changed(name, value) {
for (var i=0; i<listeners.length; i++)
listeners[i](name, value);
}
/**
* Returns the value stored within the item having the given name.
*
* @param {String} name The name of the item to read.
* @param [value] The default value, if any.
* @return The value of the given item.
*/
this.getItem = function(name, value) {
// Attempt to read JSON from localStorage, default to local variable
var json = stored_json;
if (localStorage) {
try {
json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
}
catch (ignore) {}
}
var obj = JSON.parse(json);
if (obj[name] !== undefined)
return obj[name];
return value;
};
/**
* Sets the item having the given name to the given value.
*
* @param {String} name The name of the item to change.
* @param [value] An arbitrary value.
*/
this.setItem = function(name, value) {
// Attempt to read JSON from localStorage, default to local variable
var json = stored_json;
if (localStorage) {
try {
json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
}
catch (ignore) {}
}
// Modify object property
var obj = JSON.parse(json);
var old = obj[name];
obj[name] = value;
// Notify of change
if (old !== value)
__notify_changed(name, value);
// Attempt to set JSON within localStorage, default to local variable
stored_json = JSON.stringify(obj);
if (localStorage) {
try {
localStorage.setItem("GUACAMOLE_STATE", stored_json);
}
catch (ignore) {}
}
};
// Reload when modified
window.addEventListener("storage", function reload() {
// Pull current state
var new_json = localStorage.getItem("GUACAMOLE_STATE") || "{}";
var new_state = JSON.parse(new_json);
var old_state = JSON.parse(stored_json);
// Check if any values are different
for (var name in new_state) {
// If value changed, notify
var old = old_state[name];
if (old !== new_state[name])
__notify_changed(name, new_state[name]);
}
stored_json = new_json;
}, false);
/**
* Ensures that the given function will be called for each change in
* item value. The function must accept a single argument which will be
* the name of the item changed.
*
* @param {onchange} onchange The function to call when an item changes.
*/
this.addChangeListener = function(onchange) {
listeners.push(onchange);
};
})();