GUACAMOLE-630: Tolerate insufficient JavaScript features to run "Pickr" color picker (Internet Explorer).

This commit is contained in:
Michael Jumper
2019-08-07 19:46:40 -07:00
parent ad92e86b0c
commit a4a0318f95
3 changed files with 118 additions and 90 deletions

View File

@@ -19,36 +19,13 @@
/** /**
* A directive which implements a color input field, leveraging the "Pickr" * A directive which implements a color input field, leveraging the "Pickr"
* color picker. * color picker. If the "Picker" color picker cannot be used because it relies
* on JavaScript features not supported by the browser (Internet Explorer), a
* "guacInputColorUnavailable" event will be emitted up the scope, and this
* directive will become read-only, functioning essentially as a color preview.
*/ */
angular.module('form').directive('guacInputColor', [function guacInputColor() { angular.module('form').directive('guacInputColor', [function guacInputColor() {
/**
* Returns whether the given color is relatively dark. A color is
* considered dark if white text would be more visible over a background
* of that color (provide better contrast) than black text.
*
* @param {HSVaColor} color
* The color to test.
*
* @returns {Boolean}
* true if the given color is relatively dark (white text would provide
* better contrast than black), false otherwise.
*/
var isDark = function isDark(color) {
var rgb = color.toRGBA();
// Convert RGB to luminance in HSL space (as defined by the
// relative luminance formula given by the W3C for accessibility)
var luminance = 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
// Consider the background to be dark if white text over that
// background would provide better contrast than black
return luminance <= 153; // 153 is the component value 0.6 converted from 0-1 to the 0-255 range
};
var config = { var config = {
restrict: 'E', restrict: 'E',
replace: true, replace: true,
@@ -86,13 +63,44 @@ angular.module('form').directive('guacInputColor', [function guacInputColor() {
var $translate = $injector.get('$translate'); var $translate = $injector.get('$translate');
/** /**
* Whether the color currently selected is "dark" in the sense that the * Whether the color picker ("Pickr") cannot be used. In general, all
* color white will have higher contrast against it than the color * browsers should support Pickr with the exception of Internet
* black. * Explorer.
* *
* @type Boolean * @type Boolean
*/ */
$scope.dark = false; $scope.colorPickerUnavailable = false;
/**
* Returns whether the color currently selected is "dark" in the sense
* that the color white will have higher contrast against it than the
* color black.
*
* @returns {Boolean}
* true if the currently selected color is relatively dark (white
* text would provide better contrast than black), false otherwise.
*/
$scope.isDark = function isDark() {
// Assume not dark if color is invalid or undefined
var rgb = $scope.model && /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec($scope.model);
if (!rgb)
return false;
// Parse color component values as hexadecimal
var red = parseInt(rgb[1], 16);
var green = parseInt(rgb[2], 16);
var blue = parseInt(rgb[3], 16);
// Convert RGB to luminance in HSL space (as defined by the
// relative luminance formula given by the W3C for accessibility)
var luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
// Consider the background to be dark if white text over that
// background would provide better contrast than black
return luminance <= 153; // 153 is the component value 0.6 converted from 0-1 to the 0-255 range
};
// Init color picker after required translation strings are available // Init color picker after required translation strings are available
$q.all({ $q.all({
@@ -100,81 +108,90 @@ angular.module('form').directive('guacInputColor', [function guacInputColor() {
'cancel' : $translate('APP.ACTION_CANCEL') 'cancel' : $translate('APP.ACTION_CANCEL')
}).then(function stringsRetrieved(strings) { }).then(function stringsRetrieved(strings) {
/** try {
* An instance of the "Pickr" color picker, bound to the underlying
* element of this directive.
*
* @type Pickr
*/
var pickr = Pickr.create({
// Bind color picker to the underlying element of this directive /**
el : $element[0], * An instance of the "Pickr" color picker, bound to the underlying
* element of this directive.
*
* @type Pickr
*/
var pickr = Pickr.create({
// Wrap color picker dialog in Guacamole-specific class for // Bind color picker to the underlying element of this directive
// sake of additional styling el : $element[0],
appClass : 'guac-input-color-picker',
// Display color details as hex // Wrap color picker dialog in Guacamole-specific class for
defaultRepresentation : 'HEX', // sake of additional styling
appClass : 'guac-input-color-picker',
// Use "monolith" theme, as a nice balance between "nano" (does // Display color details as hex
// not work in Internet Explorer) and "classic" (too big) defaultRepresentation : 'HEX',
theme : 'monolith',
// Leverage the container element as the button which shows the // Use "monolith" theme, as a nice balance between "nano" (does
// picker, relying on our own styling for that button // not work in Internet Explorer) and "classic" (too big)
useAsButton : true, theme : 'monolith',
appendToBody : true,
// Do not include opacity controls // Leverage the container element as the button which shows the
lockOpacity : true, // picker, relying on our own styling for that button
useAsButton : true,
appendToBody : true,
// Include a selection of palette entries for convenience and // Do not include opacity controls
// reference lockOpacity : true,
swatches : $scope.palette || [],
components: { // Include a selection of palette entries for convenience and
// reference
swatches : $scope.palette || [],
// Include hue and color preview controls components: {
preview : true,
hue : true,
// Display only a text color input field and the save and // Include hue and color preview controls
// cancel buttons (no clear button) preview : true,
interaction: { hue : true,
input : true,
save : true,
cancel : true
}
}, // Display only a text color input field and the save and
// cancel buttons (no clear button)
interaction: {
input : true,
save : true,
cancel : true
}
// Use translation strings for buttons },
strings : strings
}); // Use translation strings for buttons
strings : strings
// Hide color picker after user clicks "cancel"
pickr.on('cancel', function colorChangeCanceled() {
pickr.hide();
});
// Keep model in sync with changes to the color picker
pickr.on('save', function colorChanged(color) {
$scope.$evalAsync(function updateModel() {
$scope.model = color.toHEXA().toString();
$scope.dark = isDark(pickr.getColor());
}); });
});
// Keep color picker in sync with changes to the model // Hide color picker after user clicks "cancel"
pickr.on('init', function pickrReady(color) { pickr.on('cancel', function colorChangeCanceled() {
$scope.$watch('model', function modelChanged(model) { pickr.hide();
pickr.setColor(model);
$scope.dark = isDark(pickr.getColor());
}); });
});
// Keep model in sync with changes to the color picker
pickr.on('save', function colorChanged(color) {
$scope.$evalAsync(function updateModel() {
$scope.model = color.toHEXA().toString();
});
});
// Keep color picker in sync with changes to the model
pickr.on('init', function pickrReady(color) {
$scope.$watch('model', function modelChanged(model) {
pickr.setColor(model);
});
});
}
// If the "Pickr" color picker cannot be loaded (Internet Explorer),
// let the scope above us know
catch (e) {
$scope.colorPickerUnavailable = true;
$scope.$emit('guacInputColorUnavailable', e);
}
}, angular.noop); }, angular.noop);

View File

@@ -67,6 +67,10 @@
cursor: pointer; cursor: pointer;
} }
.form-field .terminal-color-scheme-field .custom-color-scheme .guac-input-color.read-only {
cursor: not-allowed;
}
/* /*
* Color button font colors * Color button font colors
*/ */

View File

@@ -1,3 +1,10 @@
<div class="guac-input-color" ng-class="{ 'dark' : dark }" ng-style="{ 'background-color' : model }"> <div class="guac-input-color"
ng-class="{
'dark' : isDark(),
'read-only' : colorPickerUnavailable
}"
ng-style="{
'background-color' : model
}">
<ng-transclude></ng-transclude> <ng-transclude></ng-transclude>
</div> </div>