diff --git a/AGHTechDoc.md b/AGHTechDoc.md index e8f98db4..6e99cbaa 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -1338,9 +1338,13 @@ Request: POST /control/filtering/set_url { + "url": "..." + "data": { + "name": "..." "url": "..." "enabled": true | false } + } Response: diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 9de02278..78ae0a2c 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -137,7 +137,9 @@ "enter_url_hint": "Enter URL", "check_updates_btn": "Check updates", "new_filter_btn": "New filter subscription", + "edit_filter_title": "Edit filter", "enter_valid_filter_url": "Enter a valid URL to a filter subscription or a hosts file.", + "form_error_url_format": "Invalid url format", "custom_filter_rules": "Custom filtering rules", "custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.", "examples_title": "Examples", @@ -404,6 +406,7 @@ "domain": "Domain", "answer": "Answer", "filter_added_successfully": "The filter has been successfully added", + "filter_updated": "The filter successfully updated", "statistics_configuration": "Statistics configuration", "statistics_retention": "Statistics retention", "statistics_retention_desc": "If you decrease the interval value, some data will be lost", diff --git a/client/src/actions/filtering.js b/client/src/actions/filtering.js index 3f0c59d9..117c22ee 100644 --- a/client/src/actions/filtering.js +++ b/client/src/actions/filtering.js @@ -78,10 +78,10 @@ export const toggleFilterRequest = createAction('FILTER_TOGGLE_REQUEST'); export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE'); export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS'); -export const toggleFilterStatus = (url, enabled) => async (dispatch) => { +export const toggleFilterStatus = (url, data) => async (dispatch) => { dispatch(toggleFilterRequest()); try { - await apiClient.setFilterUrl({ url, enabled: !enabled }); + await apiClient.setFilterUrl({ url, data }); dispatch(toggleFilterSuccess(url)); dispatch(getFilteringStatus()); } catch (error) { @@ -90,6 +90,24 @@ export const toggleFilterStatus = (url, enabled) => async (dispatch) => { } }; +export const editFilterRequest = createAction('EDIT_FILTER_REQUEST'); +export const editFilterFailure = createAction('EDIT_FILTER_FAILURE'); +export const editFilterSuccess = createAction('EDIT_FILTER_SUCCESS'); + +export const editFilter = (url, data) => async (dispatch) => { + dispatch(editFilterRequest()); + try { + await apiClient.setFilterUrl({ url, data }); + dispatch(editFilterSuccess(url)); + dispatch(toggleFilteringModal()); + dispatch(addSuccessToast('filter_updated')); + dispatch(getFilteringStatus()); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(editFilterFailure()); + } +}; + export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST'); export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE'); export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS'); diff --git a/client/src/components/Filters/Form.js b/client/src/components/Filters/Form.js new file mode 100644 index 00000000..f53fffce --- /dev/null +++ b/client/src/components/Filters/Form.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Field, reduxForm } from 'redux-form'; +import { Trans, withNamespaces } from 'react-i18next'; +import flow from 'lodash/flow'; + +import { renderInputField, required, isValidUrl } from '../../helpers/form'; + +const Form = (props) => { + const { + t, + closeModal, + handleSubmit, + processingAddFilter, + processingConfigFilter, + } = props; + + return ( +
+
+
+ +
+
+ +
+
+ enter_valid_filter_url +
+
+
+ + +
+
+ ); +}; + +Form.propTypes = { + t: PropTypes.func.isRequired, + closeModal: PropTypes.func.isRequired, + handleSubmit: PropTypes.func.isRequired, + processingAddFilter: PropTypes.bool.isRequired, + processingConfigFilter: PropTypes.bool.isRequired, +}; + +export default flow([ + withNamespaces(), + reduxForm({ + form: 'filterForm', + }), +])(Form); diff --git a/client/src/components/Filters/Modal.js b/client/src/components/Filters/Modal.js index 58a41f5e..72d8d1d3 100644 --- a/client/src/components/Filters/Modal.js +++ b/client/src/components/Filters/Modal.js @@ -1,52 +1,28 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ReactModal from 'react-modal'; -import classnames from 'classnames'; import { Trans, withNamespaces } from 'react-i18next'; -import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants'; + +import { MODAL_TYPE } from '../../helpers/constants'; +import Form from './Form'; import '../ui/Modal.css'; ReactModal.setAppElement('#root'); -const initialState = { - url: '', - name: '', - isUrlValid: false, -}; - class Modal extends Component { - state = initialState; - - isUrlValid = url => R_URL_REQUIRES_PROTOCOL.test(url); - - handleUrlChange = async (e) => { - const { value: url } = e.currentTarget; - this.setState(...this.state, { url, isUrlValid: this.isUrlValid(url) }); - }; - - handleNameChange = (e) => { - const { value: name } = e.currentTarget; - this.setState({ ...this.state, name }); - }; - closeModal = () => { this.props.toggleModal(); - this.setState({ ...this.state, ...initialState }); }; render() { - const { isOpen, processingAddFilter } = this.props; - const { isUrlValid, url, name } = this.state; - const inputUrlClass = classnames({ - 'form-control mb-2': true, - 'is-invalid': url.length > 0 && !isUrlValid, - 'is-valid': url.length > 0 && isUrlValid, - }); - const inputNameClass = classnames({ - 'form-control mb-2': true, - 'is-valid': name.length > 0, - }); - const isValidForSubmit = url.length > 0 && isUrlValid && name.length > 0; + const { + isOpen, + processingAddFilter, + processingConfigFilter, + handleSubmit, + modalType, + currentFilterData, + } = this.props; return (

- new_filter_btn + {modalType === MODAL_TYPE.EDIT ? ( + edit_filter_title + ) : ( + new_filter_btn + )}

-
- - -
- enter_valid_filter_url -
-
-
- - -
+
); @@ -110,6 +63,10 @@ Modal.propTypes = { addFilter: PropTypes.func.isRequired, isFilterAdded: PropTypes.bool.isRequired, processingAddFilter: PropTypes.bool.isRequired, + processingConfigFilter: PropTypes.bool.isRequired, + handleSubmit: PropTypes.func.isRequired, + modalType: PropTypes.string.isRequired, + currentFilterData: PropTypes.object.isRequired, t: PropTypes.func.isRequired, }; diff --git a/client/src/components/Filters/index.js b/client/src/components/Filters/index.js index eb10434d..318b9b95 100644 --- a/client/src/components/Filters/index.js +++ b/client/src/components/Filters/index.js @@ -9,6 +9,8 @@ import CellWrap from '../ui/CellWrap'; import UserRules from './UserRules'; import Modal from './Modal'; +import { MODAL_TYPE } from '../../helpers/constants'; + class Filters extends Component { componentDidMount() { this.props.getFilteringStatus(); @@ -22,15 +24,29 @@ class Filters extends Component { this.props.setRules(this.props.filtering.userRules); }; + handleSubmit = (values) => { + const { name, url } = values; + const { filtering } = this.props; + + if (filtering.modalType === MODAL_TYPE.EDIT) { + const data = { ...values }; + this.props.editFilter(filtering.modalFilterUrl, data); + } else { + this.props.addFilter(url, name); + } + } + renderCheckbox = ({ original }) => { const { processingConfigFilter } = this.props.filtering; - const { url, enabled } = original; + const { url, name, enabled } = original; + const data = { name, url, enabled: !enabled }; + return (