summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx')
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx323
1 files changed, 323 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx b/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
new file mode 100644
index 0000000000..7dd61bdc93
--- /dev/null
+++ b/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
@@ -0,0 +1,323 @@
+/* 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/. */
+
+import {
+ actionCreators as ac,
+ actionTypes as at,
+} from "common/Actions.sys.mjs";
+import { A11yLinkButton } from "content-src/components/A11yLinkButton/A11yLinkButton";
+import React from "react";
+import { TOP_SITES_SOURCE } from "./TopSitesConstants";
+import { TopSiteFormInput } from "./TopSiteFormInput";
+import { TopSiteLink } from "./TopSite";
+
+export class TopSiteForm extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ const { site } = props;
+ this.state = {
+ label: site ? site.label || site.hostname : "",
+ url: site ? site.url : "",
+ validationError: false,
+ customScreenshotUrl: site ? site.customScreenshotURL : "",
+ showCustomScreenshotForm: site ? site.customScreenshotURL : false,
+ };
+ this.onClearScreenshotInput = this.onClearScreenshotInput.bind(this);
+ this.onLabelChange = this.onLabelChange.bind(this);
+ this.onUrlChange = this.onUrlChange.bind(this);
+ this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
+ this.onClearUrlClick = this.onClearUrlClick.bind(this);
+ this.onDoneButtonClick = this.onDoneButtonClick.bind(this);
+ this.onCustomScreenshotUrlChange =
+ this.onCustomScreenshotUrlChange.bind(this);
+ this.onPreviewButtonClick = this.onPreviewButtonClick.bind(this);
+ this.onEnableScreenshotUrlForm = this.onEnableScreenshotUrlForm.bind(this);
+ this.validateUrl = this.validateUrl.bind(this);
+ }
+
+ onLabelChange(event) {
+ this.setState({ label: event.target.value });
+ }
+
+ onUrlChange(event) {
+ this.setState({
+ url: event.target.value,
+ validationError: false,
+ });
+ }
+
+ onClearUrlClick() {
+ this.setState({
+ url: "",
+ validationError: false,
+ });
+ }
+
+ onEnableScreenshotUrlForm() {
+ this.setState({ showCustomScreenshotForm: true });
+ }
+
+ _updateCustomScreenshotInput(customScreenshotUrl) {
+ this.setState({
+ customScreenshotUrl,
+ validationError: false,
+ });
+ this.props.dispatch({ type: at.PREVIEW_REQUEST_CANCEL });
+ }
+
+ onCustomScreenshotUrlChange(event) {
+ this._updateCustomScreenshotInput(event.target.value);
+ }
+
+ onClearScreenshotInput() {
+ this._updateCustomScreenshotInput("");
+ }
+
+ onCancelButtonClick(ev) {
+ ev.preventDefault();
+ this.props.onClose();
+ }
+
+ onDoneButtonClick(ev) {
+ ev.preventDefault();
+
+ if (this.validateForm()) {
+ const site = { url: this.cleanUrl(this.state.url) };
+ const { index } = this.props;
+ if (this.state.label !== "") {
+ site.label = this.state.label;
+ }
+
+ if (this.state.customScreenshotUrl) {
+ site.customScreenshotURL = this.cleanUrl(
+ this.state.customScreenshotUrl
+ );
+ } else if (this.props.site && this.props.site.customScreenshotURL) {
+ // Used to flag that previously cached screenshot should be removed
+ site.customScreenshotURL = null;
+ }
+ this.props.dispatch(
+ ac.AlsoToMain({
+ type: at.TOP_SITES_PIN,
+ data: { site, index },
+ })
+ );
+ this.props.dispatch(
+ ac.UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: "TOP_SITES_EDIT",
+ action_position: index,
+ })
+ );
+
+ this.props.onClose();
+ }
+ }
+
+ onPreviewButtonClick(event) {
+ event.preventDefault();
+ if (this.validateForm()) {
+ this.props.dispatch(
+ ac.AlsoToMain({
+ type: at.PREVIEW_REQUEST,
+ data: { url: this.cleanUrl(this.state.customScreenshotUrl) },
+ })
+ );
+ this.props.dispatch(
+ ac.UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: "PREVIEW_REQUEST",
+ })
+ );
+ }
+ }
+
+ cleanUrl(url) {
+ // If we are missing a protocol, prepend http://
+ if (!url.startsWith("http:") && !url.startsWith("https:")) {
+ return `http://${url}`;
+ }
+ return url;
+ }
+
+ _tryParseUrl(url) {
+ try {
+ return new URL(url);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ validateUrl(url) {
+ const validProtocols = ["http:", "https:"];
+ const urlObj =
+ this._tryParseUrl(url) || this._tryParseUrl(this.cleanUrl(url));
+
+ return urlObj && validProtocols.includes(urlObj.protocol);
+ }
+
+ validateCustomScreenshotUrl() {
+ const { customScreenshotUrl } = this.state;
+ return !customScreenshotUrl || this.validateUrl(customScreenshotUrl);
+ }
+
+ validateForm() {
+ const validate =
+ this.validateUrl(this.state.url) && this.validateCustomScreenshotUrl();
+
+ if (!validate) {
+ this.setState({ validationError: true });
+ }
+
+ return validate;
+ }
+
+ _renderCustomScreenshotInput() {
+ const { customScreenshotUrl } = this.state;
+ const requestFailed = this.props.previewResponse === "";
+ const validationError =
+ (this.state.validationError && !this.validateCustomScreenshotUrl()) ||
+ requestFailed;
+ // Set focus on error if the url field is valid or when the input is first rendered and is empty
+ const shouldFocus =
+ (validationError && this.validateUrl(this.state.url)) ||
+ !customScreenshotUrl;
+ const isLoading =
+ this.props.previewResponse === null &&
+ customScreenshotUrl &&
+ this.props.previewUrl === this.cleanUrl(customScreenshotUrl);
+
+ if (!this.state.showCustomScreenshotForm) {
+ return (
+ <A11yLinkButton
+ onClick={this.onEnableScreenshotUrlForm}
+ className="enable-custom-image-input"
+ data-l10n-id="newtab-topsites-use-image-link"
+ />
+ );
+ }
+ return (
+ <div className="custom-image-input-container">
+ <TopSiteFormInput
+ errorMessageId={
+ requestFailed
+ ? "newtab-topsites-image-validation"
+ : "newtab-topsites-url-validation"
+ }
+ loading={isLoading}
+ onChange={this.onCustomScreenshotUrlChange}
+ onClear={this.onClearScreenshotInput}
+ shouldFocus={shouldFocus}
+ typeUrl={true}
+ value={customScreenshotUrl}
+ validationError={validationError}
+ titleId="newtab-topsites-image-url-label"
+ placeholderId="newtab-topsites-url-input"
+ />
+ </div>
+ );
+ }
+
+ render() {
+ const { customScreenshotUrl } = this.state;
+ const requestFailed = this.props.previewResponse === "";
+ // For UI purposes, editing without an existing link is "add"
+ const showAsAdd = !this.props.site;
+ const previous =
+ (this.props.site && this.props.site.customScreenshotURL) || "";
+ const changed =
+ customScreenshotUrl && this.cleanUrl(customScreenshotUrl) !== previous;
+ // Preview mode if changes were made to the custom screenshot URL and no preview was received yet
+ // or the request failed
+ const previewMode = changed && !this.props.previewResponse;
+ const previewLink = Object.assign({}, this.props.site);
+ if (this.props.previewResponse) {
+ previewLink.screenshot = this.props.previewResponse;
+ previewLink.customScreenshotURL = this.props.previewUrl;
+ }
+ // Handles the form submit so an enter press performs the correct action
+ const onSubmit = previewMode
+ ? this.onPreviewButtonClick
+ : this.onDoneButtonClick;
+
+ const addTopsitesHeaderL10nId = "newtab-topsites-add-shortcut-header";
+ const editTopsitesHeaderL10nId = "newtab-topsites-edit-shortcut-header";
+ return (
+ <form className="topsite-form" onSubmit={onSubmit}>
+ <div className="form-input-container">
+ <h3
+ className="section-title grey-title"
+ data-l10n-id={
+ showAsAdd ? addTopsitesHeaderL10nId : editTopsitesHeaderL10nId
+ }
+ />
+ <div className="fields-and-preview">
+ <div className="form-wrapper">
+ <TopSiteFormInput
+ onChange={this.onLabelChange}
+ value={this.state.label}
+ titleId="newtab-topsites-title-label"
+ placeholderId="newtab-topsites-title-input"
+ autoFocusOnOpen={true}
+ />
+ <TopSiteFormInput
+ onChange={this.onUrlChange}
+ shouldFocus={
+ this.state.validationError &&
+ !this.validateUrl(this.state.url)
+ }
+ value={this.state.url}
+ onClear={this.onClearUrlClick}
+ validationError={
+ this.state.validationError &&
+ !this.validateUrl(this.state.url)
+ }
+ titleId="newtab-topsites-url-label"
+ typeUrl={true}
+ placeholderId="newtab-topsites-url-input"
+ errorMessageId="newtab-topsites-url-validation"
+ />
+ {this._renderCustomScreenshotInput()}
+ </div>
+ <TopSiteLink
+ link={previewLink}
+ defaultStyle={requestFailed}
+ title={this.state.label}
+ />
+ </div>
+ </div>
+ <section className="actions">
+ <button
+ className="cancel"
+ type="button"
+ onClick={this.onCancelButtonClick}
+ data-l10n-id="newtab-topsites-cancel-button"
+ />
+ {previewMode ? (
+ <button
+ className="done preview"
+ type="submit"
+ data-l10n-id="newtab-topsites-preview-button"
+ />
+ ) : (
+ <button
+ className="done"
+ type="submit"
+ data-l10n-id={
+ showAsAdd
+ ? "newtab-topsites-add-button"
+ : "newtab-topsites-save-button"
+ }
+ />
+ )}
+ </section>
+ </form>
+ );
+ }
+}
+
+TopSiteForm.defaultProps = {
+ site: null,
+ index: -1,
+};