-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add warning for low contrast and equal colors #86
Changes from 5 commits
819d68d
10bfa6b
323ad34
1b53467
9d6e4cb
da16700
de319c2
9dae10d
7627beb
39cc907
f0009f5
496edca
33a661a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,22 @@ | |
"message": "Are you sure that you want to reset all options?", | ||
"description": "The message shown, when the user is prompted to confirm the resetting of all options" | ||
}, | ||
"lowContrastRatioInfo": { | ||
"message": "Your QR code may be hard to scan by some QR code readers because the contrast is too low.", | ||
"desription": "The message shown when the contrast ratio is too low" | ||
}, | ||
"lowContrastRatioWarning": { | ||
"message": "The contrast ratio between the QR code color and the background might be too low to be recognized by the most QR code readers", | ||
"desription": "The warning shown when the contrast ratio is too low" | ||
}, | ||
"lowContrastRatioError": { | ||
"message": "The background and the foreground color are too similar.", | ||
"description": "The message shown when the color for the QR code and the background are equal" | ||
}, | ||
"messageAutoSelectColorButton": { | ||
"message": "Calculate best contrast", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MAybe you can try to shorten this a bit? Instead of calculate maybe just "Use". (That the JS has to calculate this is a technical detail the user does not even want to know. 😄) Theoretically it could e.g. also be okay for the user to get some color variation (i.e. not 100%ly the contrast color, but a color, which has enough contrast, so the message goes away.) |
||
"description": "The text of a button that sets a new color with a sufficient contrast." | ||
}, | ||
|
||
// tips | ||
"tipYouLikeAddon": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -732,7 +732,7 @@ const MessageHandler = (function () {// eslint-disable-line no-unused-vars | |
if (typeof args[0] === "boolean") { | ||
isDismissable = args.shift(); | ||
} | ||
if (typeof args[0] !== undefined && args[0].text !== undefined && args[0].action !== undefined) { | ||
if (args[0] !== undefined && args[0].text !== undefined && args[0].action !== undefined) { | ||
actionButton = args.shift(); | ||
} | ||
|
||
|
@@ -748,7 +748,7 @@ const MessageHandler = (function () {// eslint-disable-line no-unused-vars | |
if (isDismissable === true && elDismissIcon) { | ||
// add an icon which dismisses the message if clicked | ||
elDismissIcon.classList.remove("invisible"); | ||
} else { | ||
} else if (elDismissIcon) { | ||
elDismissIcon.classList.add("invisible"); | ||
} | ||
|
||
|
@@ -770,7 +770,7 @@ const MessageHandler = (function () {// eslint-disable-line no-unused-vars | |
|
||
elActionButton.textContent = browser.i18n.getMessage(actionButton.text) || actionButton.text; | ||
elActionButton.classList.remove("invisible"); | ||
} else { | ||
} else if (elActionButton) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, good point! Dismiss icon (https://github.com/rugk/offline-qr-code/pull/86/files#diff-ccdc4da75e7ac1061a8cc0851e3c7122R751) needs the same adjustments. (but I can also do them by myself) |
||
elActionButton.classList.add("invisible"); | ||
} | ||
|
||
|
@@ -1372,6 +1372,104 @@ const RandomTips = (function () {// eslint-disable-line no-unused-vars | |
return me; | ||
})(); | ||
|
||
const Colors = (() => { // eslint-disable-line no-unused-vars | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this module looks great! So for doing #20 later, everything is set. |
||
const me = {}; | ||
|
||
/** | ||
* Calculates the contrast between two colors | ||
* | ||
* @name Colors.contrastRatio | ||
* @function | ||
* @param {Array} rgb1 | ||
* @param {Array} rgb2 | ||
* @returns {int} | ||
*/ | ||
me.contrastRatio = function(rgb1, rgb2) { | ||
const l1 = luminance(rgb1); | ||
const l2 = luminance(rgb2); | ||
// Formula: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef | ||
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); | ||
}; | ||
|
||
/** | ||
* Calculates the luminance of a given RGB color. | ||
* | ||
* @name Colors.luminance | ||
* @function | ||
* @private | ||
* @param {Array} rgb | ||
* @returns {Array|null} | ||
*/ | ||
function luminance(rgb) { | ||
// Formula: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef | ||
let r = rgb[0] / 255; | ||
let g = rgb[1] / 255; | ||
let b = rgb[2] / 255; | ||
// Note: I'm using 0.04045 here (instead of 0.03928) because 0.04045 is the | ||
// number used in the actual sRGB standard. | ||
// See also: https://github.com/w3c/wcag21/issues/815 | ||
r = r < 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); | ||
g = g < 0.04045 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4); | ||
b = b < 0.04045 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4); | ||
return 0.2126 * r + 0.7152 * g + 0.0722 * b; | ||
} | ||
|
||
/** | ||
* Returns the complementary color of a given RGB array. | ||
* | ||
* @name Colors.invertColor | ||
* @function | ||
* @param {Array} rgb | ||
* @returns {string} | ||
*/ | ||
me.invertColor = function(rgb) { | ||
// invert color components | ||
const r = (255 - rgb[0]).toString(16); | ||
const g = (255 - rgb[1]).toString(16); | ||
const b = (255 - rgb[2]).toString(16); | ||
// pad each with zeros and return | ||
return `#${padZero(r)}${padZero(g)}${padZero(b)}`; | ||
}; | ||
|
||
/** | ||
* Adds missing zeros in front of a string. | ||
* | ||
* @name Colors.padZero | ||
* @function | ||
* @private | ||
* @param {string} string | ||
* @param {int} length | ||
* @returns {string} | ||
*/ | ||
function padZero(string, length) { | ||
length = length || 2; | ||
const zeros = new Array(length).join("0"); | ||
return (zeros + string).slice(-length); | ||
} | ||
|
||
/** | ||
* Converts a hex color string to RGB. | ||
* | ||
* @name Colors.hexToRgb | ||
* @function | ||
* @param {string} hex | ||
* @returns {Array|null} | ||
*/ | ||
me.hexToRgb = function(hex) { | ||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); | ||
if (result) { | ||
return [ | ||
parseInt(result[1], 16), | ||
parseInt(result[2], 16), | ||
parseInt(result[3], 16) | ||
]; | ||
} | ||
return null; | ||
}; | ||
|
||
return me; | ||
})(); | ||
|
||
// init modules | ||
AddonSettings.loadOptions(); | ||
Logger.init(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
/* globals AddonSettings */ | ||
/* globals MESSAGE_LEVEL, MessageHandler */ | ||
/* globals RandomTips */ | ||
/* globals Colors */ | ||
|
||
const OptionHandler = (function () { | ||
const me = {}; | ||
|
@@ -19,7 +20,7 @@ const OptionHandler = (function () { | |
/** | ||
* Applies option to element. | ||
* | ||
* @name OptionHandler.applyOptionToElementToElement | ||
* @name OptionHandler.applyOptionToElement | ||
* @function | ||
* @private | ||
* @param {string} option string ob object ID | ||
|
@@ -151,6 +152,8 @@ const OptionHandler = (function () { | |
// run for each option, which we know to handle | ||
applyOptionLive("popupIconColored", res.popupIconColored); | ||
applyOptionLive("qrCodeSize", res.qrCodeSize); | ||
applyOptionLive("qrColor", res.qrColor); | ||
applyOptionLive("qrBackgroundColor", res.qrBackgroundColor); | ||
}); | ||
} | ||
|
||
|
@@ -200,6 +203,47 @@ const OptionHandler = (function () { | |
|
||
case "debugMode": | ||
Logger.setDebugMode(optionValue); | ||
break; | ||
|
||
case "qrColor": | ||
case "qrBackgroundColor": { | ||
const elQrColor = document.getElementById("qrColor"); | ||
const elQrBackgroundColor = document.getElementById("qrBackgroundColor"); | ||
|
||
const qrCodeColor = Colors.hexToRgb(elQrColor.value); | ||
const qrCodeBackgroundColor = Colors.hexToRgb(elQrBackgroundColor.value); | ||
|
||
const colorContrast = Colors.contrastRatio(qrCodeColor, qrCodeBackgroundColor); | ||
|
||
const actionButton = { | ||
text: "messageAutoSelectColorButton", | ||
action: () => { | ||
const invertedColor = Colors.invertColor(qrCodeColor); | ||
browser.storage.sync.set({ | ||
"qrBackgroundColor": invertedColor | ||
}).catch((error) => { | ||
Logger.logError("could not save option", option, ": ", error); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great you added this button! As for this line just one comment: As the logger passes this to |
||
MessageHandler.showError("couldNotSaveOption", true); | ||
}).finally(() => { | ||
applyOptionLive(); | ||
elQrBackgroundColor.value = invertedColor; | ||
}); | ||
} | ||
}; | ||
|
||
// breakpoints: https://github.com/rugk/offline-qr-code/pull/86#issuecomment-390426286 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh and once we are at it. You can link to my comment, yes, but please still add comments for the colors, i.e. the accessibility guidelines I mentioned. Or even better remember the "no magic numbers" rules. So put these numbers into constants. As they are color stuff not (only) related to the options handler, the best would be to use these as constants in the |
||
if (colorContrast <= 2) { | ||
MessageHandler.showError("lowContrastRatioError", false, actionButton); | ||
} else if (colorContrast <= 3) { | ||
MessageHandler.showWarning("lowContrastRatioWarning", false, actionButton); | ||
} else if (colorContrast <= 4.5) { | ||
MessageHandler.showInfo("lowContrastRatioInfo", false, actionButton); | ||
} else { | ||
MessageHandler.hideInfo(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, this could get problematic, because we also show a permanent "managed setting" info message when this is used (see What we could do about this:
So I can do the second version after your PR is merged. |
||
MessageHandler.hideWarning(); | ||
MessageHandler.hideError(); | ||
} | ||
} | ||
} | ||
|
||
return null; | ||
|
@@ -475,9 +519,9 @@ const OptionHandler = (function () { | |
} | ||
|
||
/** | ||
* Localizes static strings in the HTML file. | ||
* Initializes the options. | ||
* | ||
* @name Localizer.init | ||
* @name OptionHandler.init | ||
* @function | ||
* @returns {void} | ||
*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shorter:
das Kontrastverhältnis -> der Kontrast
is "wahrscheinlich" neccessary? Maybe just use subjunctive ("Konjunktiv") here, too (as in the English version).
grammar:
dem Hintergrund -> dessen Hintergrund
In general you may reword it. Keep the fact that it may not be scanned by many QR code readers.