From 320d8d2f189dff594954b3f5964279410594ee29 Mon Sep 17 00:00:00 2001 From: ArtemBaskal Date: Wed, 3 Jun 2020 16:40:36 +0300 Subject: [PATCH] Refactor Tabs --- .../src/components/Settings/Clients/Form.js | 183 +++++---- client/src/components/ui/Guide.js | 299 +++++++++++++- client/src/components/ui/Tab.js | 56 ++- client/src/components/ui/Tabs.js | 382 ++---------------- client/src/helpers/form.js | 2 +- 5 files changed, 453 insertions(+), 469 deletions(-) diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index 5ac8faf863e..911cc04fe57 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { @@ -63,7 +63,6 @@ const validate = (values) => { return errors; }; - const renderFieldsWrapper = (placeholder, buttonTitle) => function cell(row) { const { fields, @@ -122,7 +121,7 @@ const renderMultiselect = (props) => { renderMultiselect.propTypes = { input: PropTypes.object.isRequired, placeholder: PropTypes.string, - options: PropTypes.object, + options: PropTypes.array, }; let Form = (props) => { @@ -142,6 +141,101 @@ let Form = (props) => { tagsOptions, } = props; + const [activeTabLabel, setActiveTabLabel] = useState('settings'); + + const tabs = { + settings: { + title: 'settings', + component:
+ {settingsCheckboxes.map((setting) => ( +
+ +
+ ))} +
, + }, + block_services: { + title: 'block_services', + component:
+
+ +
+
+ +
+
+ +
+
+
+ {SERVICES.map((service) => ( + + ))} +
+
+
, + }, + upstream_dns: { + title: 'upstream_dns', + component:
+
+ link]}> + upstream_dns_client_desc + +
+ + +
, + }, + }; + + const activeTab = tabs[activeTabLabel].component; + return (
@@ -207,86 +301,9 @@ let Form = (props) => {
- -
- {settingsCheckboxes.map((setting) => ( -
- -
- ))} -
-
-
- -
-
- -
-
- -
-
-
- {SERVICES.map((service) => ( - - ))} -
-
-
-
-
- link]}> - upstream_dns_client_desc - -
- - -
+ + {activeTab} diff --git a/client/src/components/ui/Guide.js b/client/src/components/ui/Guide.js index 1fef527cd34..4d0a48f3165 100644 --- a/client/src/components/ui/Guide.js +++ b/client/src/components/ui/Guide.js @@ -1,25 +1,293 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { withTranslation } from 'react-i18next'; +import { Trans, withTranslation } from 'react-i18next'; import Tabs from './Tabs'; import Icons from './Icons'; -const Guide = (props) => { - const { dnsAddresses, t } = props; +const dnsPrivacyList = [{ + title: 'Android', + list: [ + { + label: 'setup_dns_privacy_android_1', + }, + { + label: 'setup_dns_privacy_android_2', + components: [ + { + key: 0, + href: 'https://adguard.com/adguard-android/overview.html', + }, + text, + ], + }, + { + label: 'setup_dns_privacy_android_3', + components: [ + { + key: 0, + href: 'https://getintra.org/', + }, + text, + ], + }, + ], +}, +{ + title: 'iOS', + list: [ + { + label: 'setup_dns_privacy_ios_1', + components: [ + { + key: 0, + href: 'https://itunes.apple.com/app/id1452162351', + }, + text, + { + key: 2, + href: 'https://dnscrypt.info/stamps', + }, + + ], + }, + { + label: 'setup_dns_privacy_ios_2', + components: [ + { + key: 0, + href: 'https://adguard.com/adguard-ios/overview.html', + }, + text, + ], + }, + ], +}, +{ + title: 'setup_dns_privacy_other_title', + list: [ + { + label: 'setup_dns_privacy_other_1', + }, + { + label: 'setup_dns_privacy_other_2', + components: [ + { + key: 0, + href: 'https://github.com/AdguardTeam/dnsproxy', + }, + ], + }, + { + href: 'https://github.com/jedisct1/dnscrypt-proxy', + label: 'setup_dns_privacy_other_3', + components: [ + { + key: 0, + href: 'https://github.com/jedisct1/dnscrypt-proxy', + }, + text, + ], + }, + { + label: 'setup_dns_privacy_other_4', + components: [ + { + key: 0, + href: 'https://github.com/jedisct1/dnscrypt-proxy', + }, + text, + ], + }, + { + label: 'setup_dns_privacy_other_5', + components: [ + { + key: 0, + href: 'https://dnscrypt.info/implementations', + }, + { + key: 1, + href: 'https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients', + }, + ], + }, + ], +}, +]; + +const renderDnsPrivacyList = ({ title, list }) =>
+ {title} +
    {list.map(({ label, components }) =>
  • + { + if (React.isValidElement(props)) { + return props; + } + const { + // eslint-disable-next-line react/prop-types + href, target = '_blank', rel = 'noopener noreferrer', key = '0', + } = props; + + return link; + })}> + {label} + +
  • )} +
+
; + +const getTabs = ({ + tlsAddress, + httpsAddress, + showDnsPrivacyNotice, + t, +}) => ({ + Router: { + // eslint-disable-next-line react/display-name + getTitle: () =>

+ install_devices_router_desc +

, + title: 'Router', + list: ['install_devices_router_list_1', + 'install_devices_router_list_2', + 'install_devices_router_list_3', + // eslint-disable-next-line react/jsx-key + + link + , + ]}>install_devices_router_list_4, + ], + }, + Windows: { + title: 'Windows', + list: ['install_devices_windows_list_1', + 'install_devices_windows_list_2', + 'install_devices_windows_list_3', + 'install_devices_windows_list_4', + 'install_devices_windows_list_5', + 'install_devices_windows_list_6'], + }, + macOS: { + title: 'macOS', + list: ['install_devices_macos_list_1', + 'install_devices_macos_list_2', + 'install_devices_macos_list_3', + 'install_devices_macos_list_4'], + }, + Android: { + title: 'Android', + list: ['install_devices_android_list_1', + 'install_devices_android_list_2', + 'install_devices_android_list_3', + 'install_devices_android_list_4', + 'install_devices_android_list_5'], + }, + iOS: { + title: 'iOS', + list: ['install_devices_ios_list_1', + 'install_devices_ios_list_2', + 'install_devices_ios_list_3', + 'install_devices_ios_list_4'], + }, + dns_privacy: { + title: 'dns_privacy', + // eslint-disable-next-line react/display-name + getTitle: () =>
+
+ {tlsAddress && tlsAddress.length > 0 && ( +
+ text, + text, + ]} + > + setup_dns_privacy_1 + +
+ )} + {httpsAddress && httpsAddress.length > 0 && ( +
+ text, + text, + ]} + > + setup_dns_privacy_2 + +
+ )} + {showDnsPrivacyNotice + ?
+ + link + , + text, + ]} + > + setup_dns_notice + +
+ : <> +
+ text

]}> + setup_dns_privacy_3 +
+
+ {dnsPrivacyList.map(renderDnsPrivacyList)} + } +
+
, + }, +}); + +const renderContent = ({ title, list, getTitle }, t) =>
+
{t(title)}
+
+ {typeof getTitle === 'function' && getTitle()} + {list + &&
    {list.map((item) =>
  1. + {item} +
  2. )} +
} +
+
; + +const Guide = ({ dnsAddresses, t }) => { const tlsAddress = (dnsAddresses && dnsAddresses.filter((item) => item.includes('tls://'))) || ''; const httpsAddress = (dnsAddresses && dnsAddresses.filter((item) => item.includes('https://'))) || ''; const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1; + const [activeTabLabel, setActiveTabLabel] = useState('Router'); + + const tabs = getTabs({ + tlsAddress, + httpsAddress, + showDnsPrivacyNotice, + t, + }); + + const activeTab = renderContent(tabs[activeTabLabel], t); + return (
- + {activeTab}
); }; @@ -33,4 +301,15 @@ Guide.propTypes = { t: PropTypes.func.isRequired, }; +renderDnsPrivacyList.propTypes = { + title: PropTypes.string.isRequired, + list: PropTypes.array.isRequired, +}; + +renderContent.propTypes = { + title: PropTypes.string.isRequired, + list: PropTypes.array.isRequired, + getTitle: PropTypes.func, +}; + export default withTranslation()(Guide); diff --git a/client/src/components/ui/Tab.js b/client/src/components/ui/Tab.js index 58050dc15ff..c9860ad59de 100644 --- a/client/src/components/ui/Tab.js +++ b/client/src/components/ui/Tab.js @@ -1,45 +1,37 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import { useTranslation } from 'react-i18next'; -class Tab extends Component { - handleClick = () => { - this.props.onClick(this.props.label); - } +const Tab = ({ + activeTabLabel, label, title, onClick, +}) => { + const [t] = useTranslation(); + const handleClick = () => onClick(label); - render() { - const { - activeTab, - label, - title, - t, - } = this.props; + const tabClass = classnames({ + tab__control: true, + 'tab__control--active': activeTabLabel === label, + }); - const tabClass = classnames({ - tab__control: true, - 'tab__control--active': activeTab === label, - }); - - return ( -
- - - - {t(title || label)} -
- ); - } -} + return ( +
+ + + + {t(title || label)} +
+ ); +}; Tab.propTypes = { - activeTab: PropTypes.string.isRequired, + activeTabLabel: PropTypes.string.isRequired, label: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, title: PropTypes.string, - t: PropTypes.func.isRequired, }; export default Tab; diff --git a/client/src/components/ui/Tabs.js b/client/src/components/ui/Tabs.js index 8939a599d3a..f8081a72447 100644 --- a/client/src/components/ui/Tabs.js +++ b/client/src/components/ui/Tabs.js @@ -1,356 +1,52 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { Trans } from 'react-i18next'; - import Tab from './Tab'; import './Tabs.css'; -const dnsPrivacyList = [{ - title: 'Android', - list: [ - { - label: 'setup_dns_privacy_android_1', - }, - { - label: 'setup_dns_privacy_android_2', - components: [ - { - key: 0, - href: 'https://adguard.com/adguard-android/overview.html', - }, - text, - ], - }, - { - label: 'setup_dns_privacy_android_3', - components: [ - { - key: 0, - href: 'https://getintra.org/', - }, - text, - ], - }, - ], -}, -{ - title: 'iOS', - list: [ - { - label: 'setup_dns_privacy_ios_1', - components: [ - { - key: 0, - href: 'https://itunes.apple.com/app/id1452162351', - }, - text, - { - key: 2, - href: 'https://dnscrypt.info/stamps', - }, - - ], - }, - { - label: 'setup_dns_privacy_ios_2', - components: [ - { - key: 0, - href: 'https://adguard.com/adguard-ios/overview.html', - }, - text, - ], - }, - ], -}, -{ - title: 'setup_dns_privacy_other_title', - list: [ - { - label: 'setup_dns_privacy_other_1', - }, - { - label: 'setup_dns_privacy_other_2', - components: [ - { - key: 0, - href: 'https://github.com/AdguardTeam/dnsproxy', - }, - ], - }, - { - href: 'https://github.com/jedisct1/dnscrypt-proxy', - label: 'setup_dns_privacy_other_3', - components: [ - { - key: 0, - href: 'https://github.com/jedisct1/dnscrypt-proxy', - }, - text, - ], - }, - { - label: 'setup_dns_privacy_other_4', - components: [ - { - key: 0, - href: 'https://github.com/jedisct1/dnscrypt-proxy', - }, - text, - ], - }, - { - label: 'setup_dns_privacy_other_5', - components: [ - { - key: 0, - href: 'https://dnscrypt.info/implementations', - }, - { - key: 1, - href: 'https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients', - }, - ], - }, - ], -}, -]; - -const renderDnsPrivacyList = ({ title, list }) =>
- {title} -
    {list.map(({ label, components }) =>
  • - { - if (React.isValidElement(props)) { - return props; - } - const { - // eslint-disable-next-line react/prop-types - href, target = '_blank', rel = 'noopener noreferrer', key = '0', - } = props; - - return link; - })}> - {label} - -
  • )} -
-
; - -const getContent = ({ - tlsAddress, - httpsAddress, - showDnsPrivacyNotice, - t, -}) => ({ - Router: { - // eslint-disable-next-line react/display-name - getTitle: () =>

- install_devices_router_desc -

, - title: 'Router', - list: ['install_devices_router_list_1', - 'install_devices_router_list_2', - 'install_devices_router_list_3', - // eslint-disable-next-line react/jsx-key - - link - , - ]}>install_devices_router_list_4, - ], - }, - Windows: { - title: 'Windows', - list: ['install_devices_windows_list_1', - 'install_devices_windows_list_2', - 'install_devices_windows_list_3', - 'install_devices_windows_list_4', - 'install_devices_windows_list_5', - 'install_devices_windows_list_6'], - }, - macOS: { - title: 'macOS', - list: ['install_devices_macos_list_1', - 'install_devices_macos_list_2', - 'install_devices_macos_list_3', - 'install_devices_macos_list_4'], - }, - Android: { - title: 'Android', - list: ['install_devices_android_list_1', - 'install_devices_android_list_2', - 'install_devices_android_list_3', - 'install_devices_android_list_4', - 'install_devices_android_list_5'], - }, - iOS: { - title: 'iOS', - list: ['install_devices_ios_list_1', - 'install_devices_ios_list_2', - 'install_devices_ios_list_3', - 'install_devices_ios_list_4'], - }, - dns_privacy: { - title: 'dns_privacy', - // eslint-disable-next-line react/display-name - getTitle: () =>
-
- {tlsAddress && tlsAddress.length > 0 && ( -
- text, - text, - ]} - > - setup_dns_privacy_1 - -
- )} - {httpsAddress && httpsAddress.length > 0 && ( -
- text, - text, - ]} - > - setup_dns_privacy_2 - -
- )} - {showDnsPrivacyNotice - ?
- - link - , - text, - ]} - > - setup_dns_notice - -
- : <> -
- text

]}> - setup_dns_privacy_3 -
-
- {dnsPrivacyList.map(renderDnsPrivacyList)} - } +const Tabs = (props) => { + const { + tabs, controlClass, activeTabLabel, setActiveTabLabel, children: activeTab, + } = props; + + const onClickTabControl = (tabLabel) => setActiveTabLabel(tabLabel); + + const getControlClass = classnames({ + tabs__controls: true, + [`tabs__controls--${controlClass}`]: controlClass, + }); + + return ( +
+
+ {Object.values(tabs) + .map((props) => { + // eslint-disable-next-line react/prop-types + const { title, label = title } = props; + return ( + + ); + })}
-
, - }, -}); - -const renderContent = ({ title, list, getTitle }, t) =>
-
{t(title)}
-
- {typeof getTitle === 'function' && getTitle()} - {list - &&
    {list.map((item) =>
  1. - {item} -
  2. )} -
} -
-
; - -class Tabs extends Component { - constructor() { - super(); - this.state = { - activeTab: 'Router', - }; - } - - onClickTabControl = (tab) => { - this.setState({ activeTab: tab }); - }; - - render() { - const { - props: { - controlClass, - tlsAddress, - httpsAddress, - showDnsPrivacyNotice, - t, - }, - state: { - activeTab, - }, - } = this; - - const items = getContent({ - tlsAddress, - httpsAddress, - showDnsPrivacyNotice, - t, - }); - - const activeTabProps = items[activeTab]; - - const getControlClass = classnames({ - tabs__controls: true, - [`tabs__controls--${controlClass}`]: controlClass, - }); - - return ( -
-
- {Object.values(items) - .map((props) => { - const { title, label = title } = props; - return ( - - ); - })} -
-
- {renderContent(activeTabProps, t)} -
+
+ {activeTab}
- ); - } -} +
+ ); +}; Tabs.propTypes = { controlClass: PropTypes.string, - tlsAddress: PropTypes.array.isRequired, - httpsAddress: PropTypes.array.isRequired, - showDnsPrivacyNotice: PropTypes.bool.isRequired, - t: PropTypes.func.isRequired, -}; - -renderDnsPrivacyList.propTypes = { - title: PropTypes.string.isRequired, - list: PropTypes.array.isRequired, -}; - -renderContent.propTypes = { - title: PropTypes.string.isRequired, - list: PropTypes.array.isRequired, - getTitle: PropTypes.func, + tabs: PropTypes.object.isRequired, + activeTabLabel: PropTypes.string.isRequired, + setActiveTabLabel: PropTypes.func.isRequired, + children: PropTypes.element.isRequired, }; export default Tabs; diff --git a/client/src/helpers/form.js b/client/src/helpers/form.js index d312df398a2..bace77b5661 100644 --- a/client/src/helpers/form.js +++ b/client/src/helpers/form.js @@ -103,7 +103,7 @@ export const renderGroupField = ({ renderGroupField.propTypes = { input: PropTypes.object.isRequired, - id: PropTypes.string.isRequired, + id: PropTypes.string, className: PropTypes.string, placeholder: PropTypes.string, type: PropTypes.string,