Przeglądaj źródła

refactor CopyDropdown as a functional component

Yuki Takei 5 lat temu
rodzic
commit
eb2b4766dd
1 zmienionych plików z 167 dodań i 169 usunięć
  1. 167 169
      src/client/js/components/Page/CopyDropdown.jsx

+ 167 - 169
src/client/js/components/Page/CopyDropdown.jsx

@@ -1,49 +1,52 @@
-import React from 'react';
+import React, {
+  useState, useMemo, useCallback,
+} from 'react';
 import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
 
 import {
-  UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem,
+  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
   Tooltip,
 } from 'reactstrap';
 
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 
-class CopyDropdown extends React.Component {
+function encodeSpaces(str) {
+  if (str == null) {
+    return null;
+  }
 
-  constructor(props) {
-    super(props);
+  // Encode SPACE and IDEOGRAPHIC SPACE
+  return str.replace(/ /g, '%20').replace(/\u3000/g, '%E3%80%80');
+}
 
-    this.state = {
-      tooltipOpen: false,
-      isParamsAppended: true,
-      pagePathWithParams: '',
-      pagePathUrl: '',
-      permalink: '',
-      markdownLink: '',
-    };
 
-    this.id = (Math.random() * 1000).toString();
+/* eslint-disable react/prop-types */
+const DropdownItemContents = ({ title, contents }) => (
+  <>
+    <div className="h6 mt-1 mb-2"><strong>{title}</strong></div>
+    <div className="card well mb-1 p-2">{contents}</div>
+  </>
+);
+/* eslint-enable react/prop-types */
 
-    this.showToolTip = this.showToolTip.bind(this);
-    this.generateItemContents = this.generateItemContents.bind(this);
-    this.generatePagePathWithParams = this.generatePagePathWithParams.bind(this);
-    this.generatePagePathUrl = this.generatePagePathUrl.bind(this);
-    this.generatePermalink = this.generatePermalink.bind(this);
-    this.generateMarkdownLink = this.generateMarkdownLink.bind(this);
-  }
 
-  showToolTip() {
-    this.setState({ tooltipOpen: true });
-    setTimeout(() => {
-      this.setState({ tooltipOpen: false });
-    }, 1000);
-  }
+const CopyDropdown = (props) => {
+  const [dropdownOpen, setDropdownOpen] = useState(false);
+  const [tooltipOpen, setTooltipOpen] = useState(false);
+  const [isParamsAppended, setParamsAppended] = useState(true);
 
-  get uriParams() {
-    const { isParamsAppended } = this.state;
+  // states for labels and URLs
+  const [pagePathUrl, setPagePathUrl] = useState();
+  const [permalink, setParmalink] = useState();
+  const [markdownLink, setMarkdownLink] = useState();
 
+
+  /*
+   * functions to construct labels and URLs
+   */
+  const getUriParams = useCallback(() => {
     if (!isParamsAppended) {
       return '';
     }
@@ -51,42 +54,23 @@ class CopyDropdown extends React.Component {
     const {
       search, hash,
     } = window.location;
-    return `${search}${hash}`;
-  }
-
-  encodeSpaces(str) {
-    if (str == null) {
-      return null;
-    }
 
-    // Encode SPACE and IDEOGRAPHIC SPACE
-    return str.replace(/ /g, '%20').replace(/\u3000/g, '%E3%80%80');
-  }
-
-  generateItemContents() {
-    const pagePathWithParams = this.generatePagePathWithParams();
-    const pagePathUrl = this.generatePagePathUrl();
-    const permalink = this.generatePermalink();
-    const markdownLink = this.generateMarkdownLink();
-
-    this.setState({
-      pagePathWithParams, pagePathUrl, permalink, markdownLink,
-    });
-  }
+    return `${search}${hash}`;
+  }, [isParamsAppended]);
 
-  generatePagePathWithParams() {
-    const { pagePath } = this.props;
-    return decodeURI(`${pagePath}${this.uriParams}`);
-  }
+  const pagePathWithParams = useMemo(() => {
+    const { pagePath } = props;
+    return decodeURI(`${pagePath}${getUriParams()}`);
+  }, [props, getUriParams]);
 
-  generatePagePathUrl() {
+  const generatePagePathUrl = useCallback(() => {
     const { origin } = window.location;
-    return `${origin}${this.encodeSpaces(this.generatePagePathWithParams())}`;
-  }
+    return `${origin}${encodeSpaces(pagePathWithParams)}`;
+  }, [pagePathWithParams]);
 
-  generatePermalink() {
+  const generatePermalink = useCallback(() => {
     const { origin } = window.location;
-    const { pageId, isShareLinkMode } = this.props;
+    const { pageId, isShareLinkMode } = props;
 
     if (pageId == null) {
       return null;
@@ -95,133 +79,147 @@ class CopyDropdown extends React.Component {
       return decodeURI(`${origin}/share/${pageId}`);
     }
 
-    return this.encodeSpaces(decodeURI(`${origin}/${pageId}${this.uriParams}`));
-  }
+    return encodeSpaces(decodeURI(`${origin}/${pageId}${getUriParams()}`));
+  }, [props, getUriParams]);
 
-  generateMarkdownLink() {
-    const { pagePath } = this.props;
+  const generateMarkdownLink = useCallback(() => {
+    const { pagePath } = props;
 
-    const label = decodeURI(`${pagePath}${this.uriParams}`);
-    const permalink = this.generatePermalink();
+    const label = decodeURI(`${pagePath}${getUriParams()}`);
+    const permalink = generatePermalink();
 
     return `[${label}](${permalink})`;
-  }
+  }, [props, getUriParams, generatePermalink]);
+
+
+  /**
+   * dropdown control
+   */
+  const toggle = useCallback(() => {
+    // regenerate labels and URLs
+    if (!dropdownOpen) {
+      setPagePathUrl(generatePagePathUrl());
+      setParmalink(generatePermalink());
+      setMarkdownLink(generateMarkdownLink());
+    }
 
-  DropdownItemContents = ({ title, contents }) => (
-    <>
-      <div className="h6 mt-1 mb-2"><strong>{title}</strong></div>
-      <div className="card well mb-1 p-2">{contents}</div>
-    </>
-  );
+    setDropdownOpen(!dropdownOpen);
+  }, [dropdownOpen, generatePagePathUrl, generatePermalink, generateMarkdownLink]);
 
-  render() {
-    const {
-      t, pageId, isShareLinkMode,
-    } = this.props;
-    const {
-      isParamsAppended, pagePathWithParams, pagePathUrl, permalink, markdownLink,
-    } = this.state;
-
-    const copyTarget = isShareLinkMode ? `copyShareLink${pageId}` : 'copyPagePathDropdown';
-    const dropdownToggleStyle = isShareLinkMode ? 'btn btn-secondary' : 'd-block text-muted bg-transparent btn-copy border-0';
-
-    const { id, DropdownItemContents } = this;
-
-    const customSwitchForParamsId = `customSwitchForParams_${id}`;
-
-    return (
-      <>
-        <UncontrolledDropdown id={copyTarget} className="grw-copy-dropdown">
-          <DropdownToggle
-            caret
-            className={dropdownToggleStyle}
-            style={this.props.buttonStyle}
-            onClick={this.generateItemContents}
-          >
-            { isShareLinkMode ? (
-              <>Copy Link</>
-            ) : (<i className="ti-clipboard"></i>)}
-          </DropdownToggle>
-
-          <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: null } }}>
-
-            <div className="d-flex align-items-center justify-content-between">
-              <DropdownItem header className="px-3">
-                { t('copy_to_clipboard.Copy to clipboard') }
-              </DropdownItem>
-              <div className="px-3 custom-control custom-switch custom-switch-sm">
-                <input
-                  type="checkbox"
-                  id={customSwitchForParamsId}
-                  className="custom-control-input"
-                  checked={isParamsAppended}
-                  onChange={e => this.setState({ isParamsAppended: !isParamsAppended })}
-                />
-                <label className="custom-control-label small" htmlFor={customSwitchForParamsId}>Append params</label>
-              </div>
-            </div>
+  /**
+   * tooltip control
+   */
+  const showToolTip = useCallback(() => {
+    setTooltipOpen(true);
+    setTimeout(() => {
+      setTooltipOpen(false);
+    }, 1000);
+  }, []);
+
+
+  /*
+   * render
+   */
+  const {
+    t, pageId, isShareLinkMode, buttonStyle,
+  } = props;
 
-            <DropdownItem divider className="my-0"></DropdownItem>
+  const copyTarget = isShareLinkMode ? `copyShareLink${pageId}` : 'copyPagePathDropdown';
+  const dropdownToggleStyle = isShareLinkMode ? 'btn btn-secondary' : 'd-block text-muted bg-transparent btn-copy border-0';
 
-            {/* Page path */}
-            <CopyToClipboard text={pagePathWithParams} onCopy={this.showToolTip}>
+  const customSwitchForParamsId = `customSwitchForParams_${pageId}`;
+
+
+  return (
+    <>
+      <Dropdown id={copyTarget} className="grw-copy-dropdown" isOpen={dropdownOpen} toggle={toggle}>
+        <DropdownToggle
+          caret
+          className={dropdownToggleStyle}
+          style={buttonStyle}
+        >
+          { isShareLinkMode ? (
+            <>Copy Link</>
+          ) : (<i className="ti-clipboard"></i>)}
+        </DropdownToggle>
+
+        <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: null } }}>
+
+          <div className="d-flex align-items-center justify-content-between">
+            <DropdownItem header className="px-3">
+              { t('copy_to_clipboard.Copy to clipboard') }
+            </DropdownItem>
+            <div className="px-3 custom-control custom-switch custom-switch-sm">
+              <input
+                type="checkbox"
+                id={customSwitchForParamsId}
+                className="custom-control-input"
+                checked={isParamsAppended}
+                onChange={() => setParamsAppended(!isParamsAppended)}
+              />
+              <label className="custom-control-label small" htmlFor={customSwitchForParamsId}>Append params</label>
+            </div>
+          </div>
+
+          <DropdownItem divider className="my-0"></DropdownItem>
+
+          {/* Page path */}
+          <CopyToClipboard text={pagePathWithParams} onCopy={showToolTip}>
+            <DropdownItem className="px-3">
+              <DropdownItemContents title={t('copy_to_clipboard.Page path')} contents={pagePathWithParams} />
+            </DropdownItem>
+          </CopyToClipboard>
+
+          <DropdownItem divider className="my-0"></DropdownItem>
+
+          {/* Page path URL */}
+          <CopyToClipboard text={pagePathUrl} onCopy={showToolTip}>
+            <DropdownItem className="px-3">
+              <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={pagePathUrl} />
+            </DropdownItem>
+          </CopyToClipboard>
+          <DropdownItem divider className="my-0"></DropdownItem>
+
+          {/* Permanent Link */}
+          { pageId && (
+            <CopyToClipboard text={permalink} onCopy={showToolTip}>
               <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Page path')} contents={pagePathWithParams} />
+                <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={permalink} />
               </DropdownItem>
             </CopyToClipboard>
+          )}
 
-            <DropdownItem divider className="my-0"></DropdownItem>
+          <DropdownItem divider className="my-0"></DropdownItem>
 
-            {/* Page path URL */}
-            <CopyToClipboard text={pagePathUrl} onCopy={this.showToolTip}>
+          {/* Page path + Permanent Link */}
+          { pageId && (
+            <CopyToClipboard text={`${pagePathWithParams}\n${permalink}`} onCopy={showToolTip}>
               <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={pagePathUrl} />
+                <DropdownItemContents title={t('copy_to_clipboard.Page path and permanent link')} contents={<>{pagePathWithParams}<br />{permalink}</>} />
               </DropdownItem>
             </CopyToClipboard>
-            <DropdownItem divider className="my-0"></DropdownItem>
-
-            {/* Permanent Link */}
-            { pageId && (
-              <CopyToClipboard text={permalink} onCopy={this.showToolTip}>
-                <DropdownItem className="px-3">
-                  <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={permalink} />
-                </DropdownItem>
-              </CopyToClipboard>
-            )}
-
-            <DropdownItem divider className="my-0"></DropdownItem>
-
-            {/* Page path + Permanent Link */}
-            { pageId && (
-              <CopyToClipboard text={`${pagePathWithParams}\n${permalink}`} onCopy={this.showToolTip}>
-                <DropdownItem className="px-3">
-                  <DropdownItemContents title={t('copy_to_clipboard.Page path and permanent link')} contents={<>{pagePathWithParams}<br />{permalink}</>} />
-                </DropdownItem>
-              </CopyToClipboard>
-            )}
-
-            <DropdownItem divider className="my-0"></DropdownItem>
-
-            {/* Markdown Link */}
-            { pageId && (
-              <CopyToClipboard text={markdownLink} onCopy={this.showToolTip}>
-                <DropdownItem className="px-3 text-wrap">
-                  <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={markdownLink} isContentsWrap />
-                </DropdownItem>
-              </CopyToClipboard>
-            )}
-          </DropdownMenu>
-
-        </UncontrolledDropdown>
-
-        <Tooltip placement="bottom" isOpen={this.state.tooltipOpen} target={copyTarget} fade={false}>
-          copied!
-        </Tooltip>
-      </>
-    );
-  }
+          )}
 
-}
+          <DropdownItem divider className="my-0"></DropdownItem>
+
+          {/* Markdown Link */}
+          { pageId && (
+            <CopyToClipboard text={markdownLink} onCopy={showToolTip}>
+              <DropdownItem className="px-3 text-wrap">
+                <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={markdownLink} isContentsWrap />
+              </DropdownItem>
+            </CopyToClipboard>
+          )}
+        </DropdownMenu>
+
+      </Dropdown>
+
+      <Tooltip placement="bottom" isOpen={tooltipOpen} target={copyTarget} fade={false}>
+        copied!
+      </Tooltip>
+    </>
+  );
+};
 
 CopyDropdown.propTypes = {
   t: PropTypes.func.isRequired, // i18next