Fix getdata records, loaders

This commit is contained in:
soupette 2019-07-08 20:27:38 +02:00
parent d0482f45a3
commit a733571e32
15 changed files with 433 additions and 70 deletions

View File

@ -4,9 +4,10 @@ module.exports = {
'eslint:recommended',
'plugin:react/recommended',
'plugin:redux-saga/recommended',
'prettier',
],
plugins: ['react', 'redux-saga'],
plugins: ['react', 'redux-saga', 'react-hooks'],
env: {
browser: true,
commonjs: true,
@ -41,6 +42,8 @@ module.exports = {
rules: {
'generator-star-spacing': 0,
'no-console': 0,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
settings: {
react: {

View File

@ -10,6 +10,7 @@
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.3.0",
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-react-hooks": "^1.6.1",
"eslint-plugin-redux-saga": "^1.0.0",
"execa": "^1.0.0",
"husky": "^2.3.0",

View File

@ -1,8 +1,8 @@
/*
*
* Search
*
*/
*
* Search
*
*/
import React from 'react';
import { isEmpty, upperFirst } from 'lodash';
@ -20,7 +20,10 @@ class Search extends React.Component {
componentDidUpdate(prevProps) {
const { model, value } = this.props;
if (prevProps.model !== model || !isEmpty(prevProps.value) && isEmpty(value)) {
if (
prevProps.model !== model ||
(!isEmpty(prevProps.value) && isEmpty(value))
) {
this.resetState();
}
}
@ -33,21 +36,20 @@ class Search extends React.Component {
clearTimeout(this.timer);
this.setState({ value: target.value });
this.timer = setTimeout(() => this.triggerChange(target.value), WAIT);
}
};
handleClick = () => {
this.setState({ value: '' });
this.triggerChange('');
}
};
triggerChange = (value) => (
triggerChange = value =>
this.props.changeParams({
target: {
name: 'params._q',
name: '_q',
value,
},
})
);
});
render() {
const { model } = this.props;
@ -57,7 +59,7 @@ class Search extends React.Component {
<div className={styles.search}>
<div>
<FormattedMessage id="content-manager.components.Search.placeholder">
{(message) => (
{message => (
<input
onChange={this.handleChange}
placeholder={message}
@ -66,7 +68,9 @@ class Search extends React.Component {
/>
)}
</FormattedMessage>
{value !== '' && <div className={styles.clearable} onClick={this.handleClick} />}
{value !== '' && (
<div className={styles.clearable} onClick={this.handleClick} />
)}
</div>
<div className={styles.searchLabel}>
<img src={Logo} alt="filter_logo" />

View File

@ -90,6 +90,8 @@ Table.defaultProps = {
enableBulkActions: true,
entriesToDelete: [],
handleDelete: () => {},
records: [],
routeParams: {},
search: '',
showLoader: false,
};

View File

@ -0,0 +1,21 @@
import { GET_DATA, GET_DATA_SUCCEEDED, RESET_PROPS } from './constants';
export function getData(uid, params) {
return {
type: GET_DATA,
uid,
params,
};
}
export function getDataSucceeded(count, data) {
return {
type: GET_DATA_SUCCEEDED,
count,
data,
};
}
export function resetProps() {
return { type: RESET_PROPS };
}

View File

@ -0,0 +1,3 @@
export const GET_DATA = 'ContentManager/ListView/GET_DATA';
export const GET_DATA_SUCCEEDED = 'ContentManager/ListView/GET_DATA_SUCCEEDED';
export const RESET_PROPS = 'ContentManager/ListView/RESET_PROPS';

View File

@ -1,45 +1,124 @@
import React, { memo, useEffect } from 'react';
import React, { memo, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { capitalize, get } from 'lodash';
import { LoadingIndicatorPage } from 'strapi-helper-plugin';
import { PluginHeader, getQueryParameters } from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import { getLayout } from '../Main/actions';
import Container from '../../components/Container';
import Search from '../../components/Search';
import { getData, resetProps } from './actions';
import reducer from './reducer';
import saga from './saga';
import makeSelectListView from './selectors';
function ListView({
getLayout,
count,
emitEvent,
location: { search },
getData,
layouts,
isLoading,
history: { push },
match: {
params: { slug },
},
resetProps,
}) {
strapi.useInjectReducer({ key: 'listView', reducer, pluginId });
strapi.useInjectSaga({ key: 'listView', saga, pluginId });
// Display a loader if the layout from the main reducer is empty
const shouldShowLoader = layouts[slug] === undefined;
const getLayoutSetting = useCallback(
settingName => get(layouts, [slug, 'settings', settingName], ''),
[layouts, slug]
);
const generateSearchParams = useCallback(
(updatedParams = {}) => {
return {
_limit:
getQueryParameters(search, '_limit') || getLayoutSetting('pageSize'),
_page: getQueryParameters(search, '_page') || 1,
_q: getQueryParameters(search, '_q') || '',
_sort:
getQueryParameters(search, '_sort') ||
`${getLayoutSetting('defaultSortBy')}:${getLayoutSetting(
'defaultSortOrder'
)}`,
source: getQueryParameters(search, 'source'),
...updatedParams,
};
},
[getLayoutSetting, search]
);
useEffect(() => {
if (shouldShowLoader) {
getLayout(slug);
}
}, [shouldShowLoader]);
getData(slug, generateSearchParams());
if (shouldShowLoader) {
return <LoadingIndicatorPage />;
}
return () => {
resetProps();
};
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [slug]);
console.log(getLayoutSetting('layouts.list'));
const pluginHeaderActions = [
{
id: 'addEntry',
label: 'content-manager.containers.List.addAnEntry',
labelValues: {
entity: capitalize(slug) || 'Content Manager',
},
kind: 'primaryAddShape',
onClick: () => {
emitEvent('willCreateEntry');
},
},
];
const handleChangeParams = ({ target: { name, value } }) => {
const updatedSearch = generateSearchParams({ [name]: value });
const newSearch = Object.keys(updatedSearch)
.map(key => `${key}=${updatedSearch[key]}`)
.join('&');
push({ search: newSearch });
resetProps();
getData(slug, updatedSearch);
};
return (
<div>
<div>Coming</div>
</div>
<>
<Container>
<PluginHeader
actions={pluginHeaderActions}
description={{
id:
count > 1
? `${pluginId}.containers.List.pluginHeaderDescription`
: `${pluginId}.containers.List.pluginHeaderDescription.singular`,
values: {
label: count,
},
}}
title={{
id: slug || 'Content Manager',
}}
withDescriptionAnim={isLoading}
/>
{getLayoutSetting('searchable') && (
<Search
changeParams={handleChangeParams}
initValue={getQueryParameters(search, '_q') || ''}
model={slug}
value={getQueryParameters(search, '_q') || ''}
/>
)}
</Container>
</>
);
}
ListView.defaultProps = {
@ -47,13 +126,20 @@ ListView.defaultProps = {
};
ListView.propTypes = {
getLayout: PropTypes.func.isRequired,
count: PropTypes.number.isRequired,
emitEvent: PropTypes.func.isRequired,
layouts: PropTypes.object,
location: PropTypes.shape({
search: PropTypes.string.isRequired,
}),
getData: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
match: PropTypes.shape({
params: PropTypes.shape({
slug: PropTypes.string.isRequired,
}),
}),
resetProps: PropTypes.func.isRequired,
};
const mapStateToProps = makeSelectListView();
@ -61,7 +147,8 @@ const mapStateToProps = makeSelectListView();
export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
getLayout,
getData,
resetProps,
},
dispatch
);

View File

@ -3,13 +3,24 @@
* listView reducer
*/
import { fromJS } from 'immutable';
// import { } from './constants';
import { fromJS, List } from 'immutable';
import { GET_DATA_SUCCEEDED, RESET_PROPS } from './constants';
export const initialState = fromJS({});
export const initialState = fromJS({
count: 0,
data: List([]),
isLoading: true,
});
function listViewReducer(state = initialState, action) {
switch (action.type) {
case GET_DATA_SUCCEEDED:
return state
.update('count', () => action.count)
.update('data', () => List(action.data))
.update('isLoading', () => false);
case RESET_PROPS:
return initialState;
default:
return state;
}

View File

@ -1,29 +1,50 @@
// import { all, fork, put, call, takeLatest, select } from 'redux-saga/effects';
// import { request } from 'strapi-helper-plugin';
import { all, fork, put, call, takeLatest, select } from 'redux-saga/effects';
import { request } from 'strapi-helper-plugin';
import { set, unset } from 'lodash';
import pluginId from '../../pluginId';
// import pluginId from '../../pluginId';
// import { } from './actions';
// import { } from './constants';
import { getDataSucceeded } from './actions';
import { GET_DATA } from './constants';
// import {} from './selectors';
// const getRequestUrl = path => `/${pluginId}/fixtures/${path}`;
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
export function* getData() {
// try {
// } catch (err) {
// strapi.notification.error('content-manager.error.model.fetch');
// }
// eslint-disable-next-line require-yield
export function* getData({ uid, params }) {
try {
const _start = (params._page - 1) * parseInt(params._limit, 10);
set(params, '_start', _start);
unset(params, '_page');
if (params._q === '') {
unset(params, '_q');
}
const [{ count }, data] = yield all([
call(request, getRequestUrl(`${uid}/count`), {
method: 'GET',
params,
}),
call(request, getRequestUrl(`${uid}`), {
method: 'GET',
params,
}),
]);
yield put(getDataSucceeded(count, data));
} catch (err) {
console.log({ err });
strapi.notification.error('content-manager.error.model.fetch');
}
}
function* defaultSaga() {
// try {
// yield all([
// fork(takeLatest, GET_DATA, getData)
// ]);
// } catch (err) {
// // Do nothing
// }
try {
yield all([fork(takeLatest, GET_DATA, getData)]);
} catch (err) {
// Do nothing
}
}
export default defaultSaga;

View File

@ -1,4 +1,4 @@
import React, { memo } from 'react';
import React, { memo, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
@ -12,15 +12,25 @@ import SettingViewModel from '../SettingViewModel';
import SettingViewGroup from '../SettingViewGroup';
import SettingsView from '../SettingsView';
import { getLayout } from './actions';
import reducer from './reducer';
import saga from './saga';
import makeSelectMain from './selectors';
function Main({ isLoading, emitEvent, layouts }) {
function Main({ emitEvent, getLayout, layouts, location: { pathname } }) {
strapi.useInjectReducer({ key: 'main', reducer, pluginId });
strapi.useInjectSaga({ key: 'main', saga, pluginId });
const slug = pathname.split('/')[3];
const shouldShowLoader =
slug !== 'ctm-configurations' && layouts[slug] === undefined;
if (isLoading) {
useEffect(() => {
if (shouldShowLoader) {
getLayout(slug);
}
}, [getLayout, shouldShowLoader, slug]);
if (shouldShowLoader) {
return <LoadingIndicatorPage />;
}
@ -52,14 +62,23 @@ function Main({ isLoading, emitEvent, layouts }) {
Main.propTypes = {
emitEvent: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
getLayout: PropTypes.func.isRequired,
layouts: PropTypes.object.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}),
};
const mapStateToProps = makeSelectMain();
export function mapDispatchToProps(dispatch) {
return bindActionCreators({}, dispatch);
return bindActionCreators(
{
getLayout,
},
dispatch
);
}
const withConnect = connect(
mapStateToProps,

View File

@ -11,16 +11,12 @@ import {
} from './constants';
export const initialState = fromJS({
isLoading: false,
layouts: fromJS({
article: {},
}),
layouts: fromJS({}),
});
function mainReducer(state = initialState, action) {
switch (action.type) {
case DELETE_LAYOUT:
console.log({ action });
return state.removeIn(['layouts', action.uid]);
case DELETE_LAYOUTS:
return state.update('layouts', () => fromJS({}));

View File

@ -79,13 +79,13 @@ function SettingViewModel({
return () => {
resetProps();
};
}, []);
}, [getData, name, resetProps]);
useEffect(() => {
if (showWarningSubmit) {
toggleWarningSubmit();
}
}, [shouldToggleModalSubmit]);
}, [shouldToggleModalSubmit, showWarningSubmit]);
if (isLoading) {
return <LoadingIndicatorPage />;

View File

@ -53,19 +53,18 @@ function SettingsView({
const [showWarningCancel, setWarningCancel] = useState(false);
const [showWarningSubmit, setWarningSubmit] = useState(false);
const toggleWarningCancel = () => setWarningCancel(prevState => !prevState);
const toggleWarningSubmit = () =>
setWarningSubmit(prevState => !prevState);
const toggleWarningSubmit = () => setWarningSubmit(prevState => !prevState);
useEffect(() => {
if (showWarningSubmit) {
toggleWarningSubmit();
}
}, [shouldToggleModalSubmit]);
}, [shouldToggleModalSubmit, showWarningSubmit]);
useEffect(() => {
if (isEmpty(initialData)) {
getData();
}
}, [initialData]);
}, [getData, initialData]);
if (isLoading) {
return <LoadingIndicatorPage />;

View File

@ -24,6 +24,192 @@ module.exports = {
getLayout: ctx => {
const layouts = {
tag: {
uid: 'article',
schema: {
// good old schema
connection: 'default',
collectionName: 'articles',
options: {},
infos: {
name: 'article',
description: '',
},
attributes: {
title: {
type: 'string',
},
content: {
type: 'text',
},
json: {
type: 'string',
},
number: {
type: 'integer',
},
date: {
type: 'date',
},
enum: {
enum: ['morning,', 'noon'],
type: 'enumeration',
},
pic: {
model: 'file',
via: 'related',
plugin: 'upload',
},
bool: {
type: 'boolean',
},
tags: {
collection: 'tag',
via: 'articles',
},
},
},
settings: {
mainField: 'id',
defaultSortBy: 'id',
defaultSortOrder: 'ASC',
searchable: true,
filterable: true,
bulkable: false,
pageSize: 10,
},
metadata: {
id: {
edit: {},
list: {
label: 'Id',
searchable: true,
sortable: true,
},
},
title: {
edit: {
label: 'title',
description: '....',
editable: true,
visible: true,
},
list: {
label: 'title',
searchable: true,
sortable: true,
},
},
content: {
edit: {
label: 'content',
description: '....',
editable: true,
visible: true,
},
list: {
label: 'content',
searchable: true,
sortable: true,
},
},
json: {
edit: {
label: 'json',
description: '....',
editable: true,
visible: true,
},
list: {},
},
number: {
edit: {
label: 'number',
description: '....',
editable: true,
visible: true,
},
list: {
label: 'number',
searchable: true,
sortable: true,
},
},
date: {
edit: {
label: 'date',
description: '....',
editable: true,
visible: true,
},
list: {
label: 'date',
searchable: true,
sortable: true,
},
},
enum: {
edit: {
label: 'enum',
description: '....',
editable: true,
visible: true,
},
list: {
label: 'enum',
searchable: true,
sortable: true,
},
},
pic: {
edit: {
label: 'pic',
description: '....',
editable: true,
visible: true,
},
list: {},
},
tags: {
edit: {
label: 'tags',
description: '....',
editable: true,
visible: true,
},
list: {},
},
bool: {
edit: {
label: 'bool',
description: '....',
editable: true,
visible: true,
},
list: {
label: 'bool',
searchable: true,
sortable: true,
},
},
},
layouts: {
list: ['id', 'title', 'content'],
editRelations: [],
edit: [
[
{
name: 'title',
size: 6,
},
{
name: 'content',
size: 6,
},
],
],
},
},
article: {
uid: 'article',
schema: {
@ -405,6 +591,11 @@ module.exports = {
label: 'Article',
destination: 'article',
},
{
name: 'tag',
label: 'Tag',
destination: 'tag',
},
{
name: 'administrator',
label: 'Administrator',

View File

@ -6544,6 +6544,11 @@ eslint-config-prettier@^4.3.0:
dependencies:
get-stdin "^6.0.0"
eslint-plugin-react-hooks@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.1.tgz#3c66a5515ea3e0a221ffc5d4e75c971c217b1a4c"
integrity sha512-wHhmGJyVuijnYIJXZJHDUF2WM+rJYTjulUTqF9k61d3BTk8etydz+M4dXUVH7M76ZRS85rqBTCx0Es/lLsrjnA==
eslint-plugin-react@^7.13.0:
version "7.13.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz#bc13fd7101de67996ea51b33873cd9dc2b7e5758"