Allow option editing
This commit is contained in:
parent
f0098cdbcb
commit
028d4401f2
|
@ -22,7 +22,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@woocommerce/dependency-extraction-webpack-plugin": "1.4.0",
|
"@woocommerce/dependency-extraction-webpack-plugin": "1.4.0",
|
||||||
"@woocommerce/eslint-plugin": "1.1.0",
|
"@woocommerce/eslint-plugin": "1.1.0",
|
||||||
"@wordpress/scripts": "^13.0.3"
|
"@wordpress/scripts": "^13.0.3",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@woocommerce/data": "^1.1.1",
|
"@woocommerce/data": "^1.1.1",
|
||||||
|
|
|
@ -23,4 +23,21 @@
|
||||||
background-color: #dc3545;
|
background-color: #dc3545;
|
||||||
border-color: #dc3545;
|
border-color: #dc3545;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-notice {
|
||||||
|
margin: 0px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wca-test-helper-option-editor {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wca-test-helper-edit-btn-save {
|
||||||
|
float: right;
|
||||||
}
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { useEffect, useState } from '@wordpress/element';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button } from '@wordpress/components';
|
||||||
|
|
||||||
|
const OptionEditor = ( props ) => {
|
||||||
|
const [ value, setValue ] = useState( props.option.content );
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
setValue( props.option.content );
|
||||||
|
}, [ props.option ] );
|
||||||
|
|
||||||
|
const handleChange = ( event ) => {
|
||||||
|
setValue( event.target.value );
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
props.onSave( props.option.name, value );
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<textarea
|
||||||
|
className="wca-test-helper-option-editor"
|
||||||
|
value={ value }
|
||||||
|
onChange={ handleChange }
|
||||||
|
></textarea>
|
||||||
|
<Button
|
||||||
|
className="wca-test-helper-edit-btn-save"
|
||||||
|
isPrimary
|
||||||
|
onClick={ handleSave }
|
||||||
|
disabled={ props.option.isSaving === true }
|
||||||
|
>
|
||||||
|
{ props.option.isSaving ? 'Saving...' : 'Save' }
|
||||||
|
</Button>
|
||||||
|
<div className="clear"></div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
OptionEditor.propTypes = {
|
||||||
|
option: PropTypes.object.isRequired,
|
||||||
|
onSave: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OptionEditor;
|
|
@ -1,6 +1,8 @@
|
||||||
const TYPES = {
|
const TYPES = {
|
||||||
SET_OPTIONS: 'SET_OPTIONS',
|
SET_OPTIONS: 'SET_OPTIONS',
|
||||||
|
SET_OPTION_FOR_EDITING: 'SET_OPTION_FOR_EDITING',
|
||||||
SET_IS_LOADING: 'SET_IS_LOADING',
|
SET_IS_LOADING: 'SET_IS_LOADING',
|
||||||
|
SET_NOTICE: 'SET_NOTICE',
|
||||||
DELETE_OPTION_BY_ID: 'DELETE_OPTION_BY_ID',
|
DELETE_OPTION_BY_ID: 'DELETE_OPTION_BY_ID',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,20 @@ export function setLoadingState( isLoading ) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setOptionForEditing( editingOption ) {
|
||||||
|
return {
|
||||||
|
type: TYPES.SET_OPTION_FOR_EDITING,
|
||||||
|
editingOption,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setNotice( notice ) {
|
||||||
|
return {
|
||||||
|
type: TYPES.SET_NOTICE,
|
||||||
|
notice,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function* deleteOptionById( optionId ) {
|
export function* deleteOptionById( optionId ) {
|
||||||
try {
|
try {
|
||||||
yield apiFetch( {
|
yield apiFetch( {
|
||||||
|
@ -42,3 +56,26 @@ export function* deleteOptionById( optionId ) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* saveOption( optionName, newOptionValue ) {
|
||||||
|
try {
|
||||||
|
const payload = {};
|
||||||
|
payload[ optionName ] = JSON.parse( newOptionValue );
|
||||||
|
yield apiFetch( {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/wc-admin/options',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify( payload ),
|
||||||
|
} );
|
||||||
|
yield setNotice( {
|
||||||
|
status: 'success',
|
||||||
|
message: optionName + ' has been saved.',
|
||||||
|
} );
|
||||||
|
} catch {
|
||||||
|
yield setNotice( {
|
||||||
|
status: 'error',
|
||||||
|
message: 'Unable to save ' + optionName,
|
||||||
|
} );
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,10 +6,26 @@ import TYPES from './action-types';
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
options: [],
|
options: [],
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
editingOption: {
|
||||||
|
name: null,
|
||||||
|
content: '{}',
|
||||||
|
},
|
||||||
|
notice: {
|
||||||
|
status: 'success',
|
||||||
|
message: '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = ( state = DEFAULT_STATE, action ) => {
|
const reducer = ( state = DEFAULT_STATE, action ) => {
|
||||||
switch ( action.type ) {
|
switch ( action.type ) {
|
||||||
|
case TYPES.SET_OPTION_FOR_EDITING:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editingOption: {
|
||||||
|
...state.editingOption,
|
||||||
|
...action.editingOption,
|
||||||
|
},
|
||||||
|
};
|
||||||
case TYPES.SET_IS_LOADING:
|
case TYPES.SET_IS_LOADING:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -21,6 +37,14 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
|
||||||
options: action.options,
|
options: action.options,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
case TYPES.SET_NOTICE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
notice: {
|
||||||
|
...state.notice,
|
||||||
|
...action.notice,
|
||||||
|
},
|
||||||
|
};
|
||||||
case TYPES.DELETE_OPTION_BY_ID:
|
case TYPES.DELETE_OPTION_BY_ID:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { apiFetch } from '@wordpress/data-controls';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { API_NAMESPACE } from './constants';
|
import { API_NAMESPACE } from './constants';
|
||||||
import { setLoadingState, setOptions } from './actions';
|
import { setLoadingState, setOptions, setOptionForEditing } from './actions';
|
||||||
|
|
||||||
export function* getOptions( search ) {
|
export function* getOptions( search ) {
|
||||||
let path = `${ API_NAMESPACE }/options?`;
|
let path = `${ API_NAMESPACE }/options?`;
|
||||||
|
@ -26,3 +26,36 @@ export function* getOptions( search ) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* getOptionForEditing( optionName ) {
|
||||||
|
const loadingOption = {
|
||||||
|
name: 'Loading...',
|
||||||
|
content: '',
|
||||||
|
saved: false,
|
||||||
|
};
|
||||||
|
if ( optionName === undefined ) {
|
||||||
|
return setOptionForEditing( loadingOption );
|
||||||
|
}
|
||||||
|
|
||||||
|
yield setOptionForEditing( loadingOption );
|
||||||
|
|
||||||
|
const path = '/wc-admin/options?options=' + optionName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = yield apiFetch( {
|
||||||
|
path,
|
||||||
|
} );
|
||||||
|
|
||||||
|
let content = response[ optionName ];
|
||||||
|
if ( typeof content === 'object' ) {
|
||||||
|
content = JSON.stringify( response[ optionName ], null, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
yield setOptionForEditing( {
|
||||||
|
name: optionName,
|
||||||
|
content,
|
||||||
|
} );
|
||||||
|
} catch ( error ) {
|
||||||
|
throw new Error( error );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,11 @@ export function getOptions( state ) {
|
||||||
export function isLoading( state ) {
|
export function isLoading( state ) {
|
||||||
return state.isLoading;
|
return state.isLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOptionForEditing( state ) {
|
||||||
|
return state.editingOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNotice( state ) {
|
||||||
|
return state.notice;
|
||||||
|
}
|
||||||
|
|
|
@ -3,11 +3,14 @@
|
||||||
*/
|
*/
|
||||||
import { withDispatch, withSelect } from '@wordpress/data';
|
import { withDispatch, withSelect } from '@wordpress/data';
|
||||||
import { compose } from '@wordpress/compose';
|
import { compose } from '@wordpress/compose';
|
||||||
|
import { Modal, Notice } from '@wordpress/components';
|
||||||
|
import { useState } from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { STORE_KEY } from './data/constants';
|
import { STORE_KEY } from './data/constants';
|
||||||
|
import { default as OptionEditor } from './OptionEditor';
|
||||||
import './data';
|
import './data';
|
||||||
|
|
||||||
function Options( {
|
function Options( {
|
||||||
|
@ -16,29 +19,50 @@ function Options( {
|
||||||
deleteOptionById,
|
deleteOptionById,
|
||||||
isLoading,
|
isLoading,
|
||||||
invalidateResolution,
|
invalidateResolution,
|
||||||
|
getOptionForEditing,
|
||||||
|
editingOption,
|
||||||
|
saveOption,
|
||||||
|
notice,
|
||||||
|
setNotice,
|
||||||
} ) {
|
} ) {
|
||||||
const deleteOption = function ( optionId ) {
|
const [ isEditModalOpen, setEditModalOpen ] = useState( false );
|
||||||
|
|
||||||
|
const deleteOption = ( optionId ) => {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
if ( confirm( 'Are you sure you want to delete?' ) ) {
|
if ( confirm( 'Are you sure you want to delete?' ) ) {
|
||||||
deleteOptionById( optionId );
|
deleteOptionById( optionId );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderLoading = function () {
|
const openEditModal = ( optionName ) => {
|
||||||
|
invalidateResolution( STORE_KEY, 'getOptionForEditing', [
|
||||||
|
optionName,
|
||||||
|
] );
|
||||||
|
|
||||||
|
getOptionForEditing( optionName );
|
||||||
|
setEditModalOpen( true );
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveOption = ( optionName, newValue ) => {
|
||||||
|
saveOption( optionName, newValue );
|
||||||
|
setEditModalOpen( false );
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderLoading = () => {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="4" align="center">
|
<td colSpan="5" align="center">
|
||||||
Loading...
|
Loading...
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTableData = function () {
|
const renderTableData = () => {
|
||||||
if ( options.length === 0 ) {
|
if ( options.length === 0 ) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="4" align="center">
|
<td colSpan="5" align="center">
|
||||||
No Options Found
|
No Options Found
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -58,8 +82,10 @@ function Options( {
|
||||||
<tr key={ optionId }>
|
<tr key={ optionId }>
|
||||||
<td key={ 0 }>{ optionId }</td>
|
<td key={ 0 }>{ optionId }</td>
|
||||||
<td key={ 1 }>{ optionName }</td>
|
<td key={ 1 }>{ optionName }</td>
|
||||||
<td key={ 2 }>{ autoload }</td>
|
<td className="align-center" key={ 2 }>
|
||||||
<td key={ 3 }>
|
{ autoload }
|
||||||
|
</td>
|
||||||
|
<td className="align-center" key={ 3 }>
|
||||||
<button
|
<button
|
||||||
className="button btn-danger"
|
className="button btn-danger"
|
||||||
onClick={ () => deleteOption( optionId ) }
|
onClick={ () => deleteOption( optionId ) }
|
||||||
|
@ -67,12 +93,20 @@ function Options( {
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
<td className="align-center" key={ 4 }>
|
||||||
|
<button
|
||||||
|
className="button btn-primary"
|
||||||
|
onClick={ () => openEditModal( optionName ) }
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchOption = function ( event ) {
|
const searchOption = ( event ) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const keyword = event.target.search.value;
|
const keyword = event.target.search.value;
|
||||||
|
|
||||||
|
@ -84,7 +118,31 @@ function Options( {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{ isEditModalOpen && (
|
||||||
|
<Modal
|
||||||
|
title={ editingOption.name }
|
||||||
|
onRequestClose={ () => {
|
||||||
|
setEditModalOpen( false );
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
<OptionEditor
|
||||||
|
option={ editingOption }
|
||||||
|
onSave={ handleSaveOption }
|
||||||
|
></OptionEditor>
|
||||||
|
</Modal>
|
||||||
|
) }
|
||||||
<div id="wc-admin-test-helper-options">
|
<div id="wc-admin-test-helper-options">
|
||||||
|
{ notice.message.length > 0 && (
|
||||||
|
<Notice
|
||||||
|
status={ notice.status }
|
||||||
|
onRemove={ () => {
|
||||||
|
setNotice( { message: '' } );
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ notice.message }
|
||||||
|
</Notice>
|
||||||
|
) }
|
||||||
<form onSubmit={ searchOption }>
|
<form onSubmit={ searchOption }>
|
||||||
<div className="search-box">
|
<div className="search-box">
|
||||||
<label
|
<label
|
||||||
|
@ -106,18 +164,36 @@ function Options( {
|
||||||
<table className="wp-list-table striped table-view-list widefat">
|
<table className="wp-list-table striped table-view-list widefat">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="manage-column column-thumb" key={ 0 }>
|
<td
|
||||||
|
className="manage-column column-thumb"
|
||||||
|
key={ 0 }
|
||||||
|
>
|
||||||
I.D
|
I.D
|
||||||
</td>
|
</td>
|
||||||
<td className="manage-column column-thumb" key={ 1 }>
|
<td
|
||||||
|
className="manage-column column-thumb"
|
||||||
|
key={ 1 }
|
||||||
|
>
|
||||||
Name
|
Name
|
||||||
</td>
|
</td>
|
||||||
<td className="manage-column column-thumb" key={ 2 }>
|
<td
|
||||||
|
className="manage-column column-thumb align-center"
|
||||||
|
key={ 2 }
|
||||||
|
>
|
||||||
Autoload
|
Autoload
|
||||||
</td>
|
</td>
|
||||||
<td className="manage-column column-thumb" key={ 3 }>
|
<td
|
||||||
|
className="manage-column column-thumb align-center"
|
||||||
|
key={ 3 }
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</td>
|
</td>
|
||||||
|
<td
|
||||||
|
className="manage-column column-thumb align-center"
|
||||||
|
key={ 4 }
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -125,20 +201,42 @@ function Options( {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSelect( ( select ) => {
|
withSelect( ( select ) => {
|
||||||
const { getOptions, isLoading } = select( STORE_KEY );
|
const {
|
||||||
|
getOptions,
|
||||||
|
getOptionForEditing,
|
||||||
|
getNotice,
|
||||||
|
isLoading,
|
||||||
|
} = select( STORE_KEY );
|
||||||
const options = getOptions();
|
const options = getOptions();
|
||||||
|
const editingOption = getOptionForEditing();
|
||||||
|
const notice = getNotice();
|
||||||
|
|
||||||
return { options, getOptions, isLoading: isLoading() };
|
return {
|
||||||
|
options,
|
||||||
|
getOptions,
|
||||||
|
isLoading: isLoading(),
|
||||||
|
editingOption,
|
||||||
|
getOptionForEditing,
|
||||||
|
notice,
|
||||||
|
};
|
||||||
} ),
|
} ),
|
||||||
withDispatch( ( dispatch ) => {
|
withDispatch( ( dispatch ) => {
|
||||||
const { deleteOptionById } = dispatch( STORE_KEY );
|
const { deleteOptionById, saveOption, setNotice } = dispatch(
|
||||||
|
STORE_KEY
|
||||||
|
);
|
||||||
const { invalidateResolution } = dispatch( 'core/data' );
|
const { invalidateResolution } = dispatch( 'core/data' );
|
||||||
|
|
||||||
return { deleteOptionById, invalidateResolution };
|
return {
|
||||||
|
deleteOptionById,
|
||||||
|
invalidateResolution,
|
||||||
|
saveOption,
|
||||||
|
setNotice,
|
||||||
|
};
|
||||||
} )
|
} )
|
||||||
)( Options );
|
)( Options );
|
||||||
|
|
Loading…
Reference in New Issue