diff --git a/devtools/shim/aboutdevtools/aboutdevtools.css b/devtools/shim/aboutdevtools/aboutdevtools.css index ece9874d489d..60a0eb8919b1 100644 --- a/devtools/shim/aboutdevtools/aboutdevtools.css +++ b/devtools/shim/aboutdevtools/aboutdevtools.css @@ -1,3 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + :root { /* Photon color variables used on the aboutdevtools page */ --blue-60: #0060df; @@ -8,7 +12,14 @@ --grey-90-alpha-10: rgba(12, 12, 13, 0.1); --grey-90-alpha-20: rgba(12, 12, 13, 0.2); --grey-90-alpha-30: rgba(12, 12, 13, 0.3); + --grey-90-alpha-40: rgba(12, 12, 13, 0.4); + --grey-90-alpha-50: rgba(12, 12, 13, 0.5); + --teal-60: #00c8d7; + --red-50: #ff0039; --white: #ffffff; + + /* Shared variables */ + --line-height: 1.5em; } html, body { @@ -17,16 +28,15 @@ html, body { } p { - line-height: 1.5em; + line-height: var(--line-height); } .box { width: 100%; max-width: 850px; display: flex; - align-items: center; - height: 400px; flex-shrink: 0; + padding: 34px 0 50px 0; } .wrapper { @@ -38,19 +48,16 @@ p { } .left-pane { - width: 360px; - height: 100%; + width: 300px; + height: 300px; + margin-inline-end: 20px; background-image: url(images/otter.svg); - background-size: 80%; + background-size: 100%; background-position: 50%; background-repeat: no-repeat; flex-shrink: 0; } -.right-pane { - height: 250px; -} - .features { max-width: 980px; border-top: 1px solid var(--grey-30); @@ -129,7 +136,7 @@ p { } button { - margin: 2em 0 0 0; + margin: 20px 0 0 0; padding: 10px 20px; border: none; diff --git a/devtools/shim/aboutdevtools/aboutdevtools.xhtml b/devtools/shim/aboutdevtools/aboutdevtools.xhtml index e49c1f866bd0..f3982f114d86 100644 --- a/devtools/shim/aboutdevtools/aboutdevtools.xhtml +++ b/devtools/shim/aboutdevtools/aboutdevtools.xhtml @@ -14,7 +14,9 @@ a + + diff --git a/devtools/shim/aboutdevtools/subscribe.css b/devtools/shim/aboutdevtools/subscribe.css new file mode 100644 index 000000000000..3fba8f39668c --- /dev/null +++ b/devtools/shim/aboutdevtools/subscribe.css @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains the styles for the newsletter subscription form on about:devtools. + * It is largely inspired from https://mozilla.github.io/basket-example/ + */ + +#newsletter-errors { + /* Hidden by default */ + display: none; + + margin-bottom: 20px; + padding: 10px; + border-radius: 2px; + + background-color: var(--red-50); + color: var(--white); +} + +#newsletter-errors.show { + display: block; +} + +#newsletter-errors .error { + margin: 0; + margin-bottom: 10px; +} + +#newsletter-errors .error:last-child { + margin-bottom: 0; +} + +#newsletter-thanks { + /* Hidden by default */ + display: none; +} + +#newsletter-thanks.show { + display: block; +} + +.newsletter-form-section { + display: block; + margin-bottom: 20px; + width: 320px; +} + +#newsletter-privacy { + display: flex; + + /* The privacy section is hidden by default and only displayed on focus */ + height: 0; + margin-bottom: -20px; + overflow: hidden; +} + +#newsletter-privacy.animate { + transition: all 0.25s cubic-bezier(.15,.75,.35,.9); +} + +#newsletter-privacy label { + line-height: var(--line-height); +} + +#privacy { + width: 20px; + height: 20px; + margin: 2px; + margin-inline-end: 10px; + flex-shrink: 0; +} + +#email { + width: 100%; + box-sizing: border-box; + padding: 12px 15px; +} + +#newsletter-form input { + border-color: var(--grey-90-alpha-30); +} + +#newsletter-form input:hover { + border-color: var(--grey-90-alpha-50); +} + +#newsletter-form input:focus { + border-color: var(--teal-60); + box-shadow: 0 0 2px 0 var(--teal-60); +} + +#newsletter-form::placeholder { + color: var(--grey-90-alpha-40); +} + +#newsletter-submit { + display: block; +} diff --git a/devtools/shim/aboutdevtools/subscribe.js b/devtools/shim/aboutdevtools/subscribe.js new file mode 100644 index 000000000000..53928eeffc1f --- /dev/null +++ b/devtools/shim/aboutdevtools/subscribe.js @@ -0,0 +1,147 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * This file handles the newsletter subscription form on about:devtools. + * It is largely inspired from https://mozilla.github.io/basket-example/ + */ + +window.addEventListener("load", function () { + const { utils: Cu } = Components; + const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); + + // Timeout for the subscribe XHR. + const REQUEST_TIMEOUT = 5000; + + const ABOUTDEVTOOLS_STRINGS = "chrome://devtools-shim/locale/aboutdevtools.properties"; + const aboutDevtoolsBundle = Services.strings.createBundle(ABOUTDEVTOOLS_STRINGS); + + let emailInput = document.getElementById("email"); + let newsletterErrors = document.getElementById("newsletter-errors"); + let newsletterForm = document.getElementById("newsletter-form"); + let newsletterPrivacySection = document.getElementById("newsletter-privacy"); + let newsletterThanks = document.getElementById("newsletter-thanks"); + + /** + * Update the error panel to display the provided errors. If the argument is null or + * empty, a default error message will be displayed. + * + * @param {Array} errors + * Array of strings, each item being an error message to display. + */ + function updateErrorPanel(errors) { + clearErrorPanel(); + + if (!errors || errors.length == 0) { + errors = [aboutDevtoolsBundle.GetStringFromName("newsletter.error.unknown")]; + } + + // Create errors markup. + let fragment = document.createDocumentFragment(); + for (let error of errors) { + let item = document.createElement("p"); + item.classList.add("error"); + item.appendChild(document.createTextNode(error)); + fragment.appendChild(item); + } + + newsletterErrors.appendChild(fragment); + newsletterErrors.classList.add("show"); + } + + /** + * Hide the error panel and remove all errors. + */ + function clearErrorPanel() { + newsletterErrors.classList.remove("show"); + newsletterErrors.innerHTML = ""; + } + + // Show the additional form fields on focus of the email input. + function onEmailInputFocus() { + // Create a hidden measuring container, append it to the parent of the privacy section + let container = document.createElement("div"); + container.style.cssText = "visibility: hidden; overflow: hidden; position: absolute"; + newsletterPrivacySection.parentNode.appendChild(container); + + // Clone the privacy section, append the clone to the measuring container. + let clone = newsletterPrivacySection.cloneNode(true); + container.appendChild(clone); + + // Measure the target height of the privacy section. + clone.style.height = "auto"; + let height = clone.offsetHeight; + + // Cleanup the measuring container. + container.remove(); + + // Set the animate class and set the height to the measured height. + newsletterPrivacySection.classList.add("animate"); + newsletterPrivacySection.style.cssText = `height: ${height}px; margin-bottom: 0;`; + } + + // XHR subscribe; handle errors; display thanks message on success. + function onFormSubmit(evt) { + evt.preventDefault(); + evt.stopPropagation(); + + // New submission, clear old errors + clearErrorPanel(); + + let xhr = new XMLHttpRequest(); + + xhr.onload = function (r) { + if (r.target.status >= 200 && r.target.status < 300) { + let {response} = r.target; + + if (response.success === true) { + // Hide form and show success message. + newsletterForm.style.display = "none"; + newsletterThanks.classList.add("show"); + } else { + // We trust the error messages from the service to be meaningful for the user. + updateErrorPanel(response.errors); + } + } else { + let {status, statusText} = r.target; + let statusInfo = `${status} - ${statusText}`; + let error = aboutDevtoolsBundle + .formatStringFromName("newsletter.error.common", [statusInfo], 1); + updateErrorPanel([error]); + } + }; + + xhr.onerror = () => { + updateErrorPanel(); + }; + + xhr.ontimeout = () => { + let error = aboutDevtoolsBundle.GetStringFromName("newsletter.error.timeout"); + updateErrorPanel([error]); + }; + + let url = newsletterForm.getAttribute("action"); + + xhr.open("POST", url, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + xhr.timeout = REQUEST_TIMEOUT; + xhr.responseType = "json"; + + // Create form data. + let formData = new FormData(newsletterForm); + formData.append("source_url", document.location.href); + + let params = new URLSearchParams(formData); + + // Send the request. + xhr.send(params.toString()); + } + + // Attach event listeners. + newsletterForm.addEventListener("submit", onFormSubmit); + emailInput.addEventListener("focus", onEmailInputFocus); +}, { once: true }); diff --git a/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd b/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd index c0d1a144e3e0..8daf0b03e299 100644 --- a/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd +++ b/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd @@ -22,4 +22,14 @@ - \ No newline at end of file + + + + + +Privacy Policy."> + + + + + diff --git a/devtools/shim/jar.mn b/devtools/shim/jar.mn index 77da69e68de1..3affffcd940b 100644 --- a/devtools/shim/jar.mn +++ b/devtools/shim/jar.mn @@ -7,6 +7,8 @@ devtools-shim.jar: content/aboutdevtools/aboutdevtools.xhtml (aboutdevtools/aboutdevtools.xhtml) content/aboutdevtools/aboutdevtools.css (aboutdevtools/aboutdevtools.css) content/aboutdevtools/aboutdevtools.js (aboutdevtools/aboutdevtools.js) + content/aboutdevtools/subscribe.css (aboutdevtools/subscribe.css) + content/aboutdevtools/subscribe.js (aboutdevtools/subscribe.js) content/aboutdevtools/images/otter.svg (aboutdevtools/images/otter.svg) diff --git a/devtools/shim/locales/en-US/aboutdevtools.properties b/devtools/shim/locales/en-US/aboutdevtools.properties index 8e33e25cc1fb..cb0cc505df84 100644 --- a/devtools/shim/locales/en-US/aboutdevtools.properties +++ b/devtools/shim/locales/en-US/aboutdevtools.properties @@ -35,3 +35,16 @@ features.performance.desc=Unblock bottlenecks, streamline processes, optimize as features.memory.title=Memory features.memory.desc=Find memory leaks and make your application zippy. + +# LOCALIZATION NOTE (newsletter.error.common): error text displayed when the newsletter +# subscription failed. The argument will be replaced with request's status code and status +# text (e.g. "404 - Not Found") +newsletter.error.common=Subscription request failed (%S). + +# LOCALIZATION NOTE (newsletter.error.unknown): error text displayed when the newsletter +# subscription failed for an unexpected reason. +newsletter.error.unknown=An unexpected error occurred. + +# LOCALIZATION NOTE (newsletter.error.timeout): error text displayed when the newsletter +# subscription timed out. +newsletter.error.timeout=Subscription request timed out.