Content manager edit layout and fields links list

This commit is contained in:
Ky 2019-02-12 10:50:41 +01:00
parent c1ffbbfb57
commit cbb53ac8d4
16 changed files with 594 additions and 325 deletions

View File

@ -20,7 +20,7 @@ import { bindActionCreators, compose } from 'redux';
// Actions required for disabling and enabling the OverlayBlocker // Actions required for disabling and enabling the OverlayBlocker
import { import {
disableGlobalOverlayBlocker, disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker enableGlobalOverlayBlocker,
} from 'actions/overlayBlocker'; } from 'actions/overlayBlocker';
import { pluginLoaded, updatePlugin } from 'containers/App/actions'; import { pluginLoaded, updatePlugin } from 'containers/App/actions';
import { import {
@ -30,7 +30,7 @@ import {
makeSelectIsAppLoading, makeSelectIsAppLoading,
makeSelectShowGlobalAppBlocker, makeSelectShowGlobalAppBlocker,
selectHasUserPlugin, selectHasUserPlugin,
selectPlugins selectPlugins,
} from 'containers/App/selectors'; } from 'containers/App/selectors';
// Design // Design
import ComingSoonPage from 'containers/ComingSoonPage'; import ComingSoonPage from 'containers/ComingSoonPage';
@ -60,7 +60,7 @@ import styles from './styles.scss';
const PLUGINS_TO_BLOCK_PRODUCTION = [ const PLUGINS_TO_BLOCK_PRODUCTION = [
'content-type-builder', 'content-type-builder',
'settings-manager' 'settings-manager',
]; ];
export class AdminPage extends React.Component { export class AdminPage extends React.Component {
@ -71,7 +71,7 @@ export class AdminPage extends React.Component {
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker, disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker, enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
plugins: this.props.plugins, plugins: this.props.plugins,
updatePlugin: this.props.updatePlugin updatePlugin: this.props.updatePlugin,
}); });
componentDidMount() { componentDidMount() {
@ -84,7 +84,7 @@ export class AdminPage extends React.Component {
const { const {
adminPage: { uuid }, adminPage: { uuid },
location: { pathname }, location: { pathname },
plugins plugins,
} = this.props; } = this.props;
if (prevProps.location.pathname !== pathname) { if (prevProps.location.pathname !== pathname) {
@ -206,7 +206,7 @@ export class AdminPage extends React.Component {
showLoading = () => { showLoading = () => {
const { const {
isAppLoading, isAppLoading,
adminPage: { isLoading } adminPage: { isLoading },
} = this.props; } = this.props;
return ( return (
@ -219,7 +219,7 @@ export class AdminPage extends React.Component {
retrievePlugins = () => { retrievePlugins = () => {
const { const {
adminPage: { currentEnvironment }, adminPage: { currentEnvironment },
plugins plugins,
} = this.props; } = this.props;
if (currentEnvironment === 'production') { if (currentEnvironment === 'production') {
@ -290,11 +290,11 @@ AdminPage.childContextTypes = {
disableGlobalOverlayBlocker: PropTypes.func, disableGlobalOverlayBlocker: PropTypes.func,
enableGlobalOverlayBlocker: PropTypes.func, enableGlobalOverlayBlocker: PropTypes.func,
plugins: PropTypes.object, plugins: PropTypes.object,
updatePlugin: PropTypes.func updatePlugin: PropTypes.func,
}; };
AdminPage.contextTypes = { AdminPage.contextTypes = {
router: PropTypes.object.isRequired router: PropTypes.object.isRequired,
}; };
AdminPage.defaultProps = { AdminPage.defaultProps = {
@ -302,7 +302,7 @@ AdminPage.defaultProps = {
appPlugins: [], appPlugins: [],
hasUserPlugin: true, hasUserPlugin: true,
isAppLoading: true, isAppLoading: true,
overlayBlockerData: {} overlayBlockerData: {},
}; };
AdminPage.propTypes = { AdminPage.propTypes = {
@ -320,7 +320,7 @@ AdminPage.propTypes = {
pluginLoaded: PropTypes.func.isRequired, pluginLoaded: PropTypes.func.isRequired,
plugins: PropTypes.object.isRequired, plugins: PropTypes.object.isRequired,
showGlobalAppBlocker: PropTypes.bool.isRequired, showGlobalAppBlocker: PropTypes.bool.isRequired,
updatePlugin: PropTypes.func.isRequired updatePlugin: PropTypes.func.isRequired,
}; };
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@ -331,7 +331,7 @@ const mapStateToProps = createStructuredSelector({
hasUserPlugin: selectHasUserPlugin(), hasUserPlugin: selectHasUserPlugin(),
isAppLoading: makeSelectIsAppLoading(), isAppLoading: makeSelectIsAppLoading(),
plugins: selectPlugins(), plugins: selectPlugins(),
showGlobalAppBlocker: makeSelectShowGlobalAppBlocker() showGlobalAppBlocker: makeSelectShowGlobalAppBlocker(),
}); });
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
@ -341,7 +341,7 @@ function mapDispatchToProps(dispatch) {
enableGlobalOverlayBlocker, enableGlobalOverlayBlocker,
getAdminData, getAdminData,
pluginLoaded, pluginLoaded,
updatePlugin updatePlugin,
}, },
dispatch dispatch
); );

View File

@ -23,12 +23,12 @@ import {
makeSelectCurrentEnv, makeSelectCurrentEnv,
makeSelectPluginDeleteAction, makeSelectPluginDeleteAction,
makeSelectPlugins, makeSelectPlugins,
makeSelectIsLoading makeSelectIsLoading,
} from './selectors'; } from './selectors';
import { import {
getPlugins, getPlugins,
onDeletePluginClick, onDeletePluginClick,
onDeletePluginConfirm onDeletePluginConfirm,
} from './actions'; } from './actions';
import reducer from './reducer'; import reducer from './reducer';
import saga from './saga'; import saga from './saga';
@ -37,7 +37,7 @@ import styles from './styles.scss';
export class ListPluginsPage extends React.Component { export class ListPluginsPage extends React.Component {
// eslint-disable-line react/prefer-stateless-function // eslint-disable-line react/prefer-stateless-function
getChildContext = () => ({ getChildContext = () => ({
currentEnvironment: this.props.currentEnvironment currentEnvironment: this.props.currentEnvironment,
}); });
componentDidMount() { componentDidMount() {
@ -64,10 +64,10 @@ export class ListPluginsPage extends React.Component {
<div className={cn('container-fluid', styles.listPluginsPage)}> <div className={cn('container-fluid', styles.listPluginsPage)}>
<PluginHeader <PluginHeader
title={{ title={{
id: 'app.components.ListPluginsPage.title' id: 'app.components.ListPluginsPage.title',
}} }}
description={{ description={{
id: 'app.components.ListPluginsPage.description' id: 'app.components.ListPluginsPage.description',
}} }}
actions={[]} actions={[]}
/> />
@ -85,7 +85,7 @@ export class ListPluginsPage extends React.Component {
} }
ListPluginsPage.childContextTypes = { ListPluginsPage.childContextTypes = {
currentEnvironment: PropTypes.string.isRequired currentEnvironment: PropTypes.string.isRequired,
}; };
ListPluginsPage.contextTypes = {}; ListPluginsPage.contextTypes = {};
@ -98,14 +98,14 @@ ListPluginsPage.propTypes = {
onDeletePluginClick: PropTypes.func.isRequired, onDeletePluginClick: PropTypes.func.isRequired,
onDeletePluginConfirm: PropTypes.func.isRequired, onDeletePluginConfirm: PropTypes.func.isRequired,
pluginActionSucceeded: PropTypes.bool.isRequired, pluginActionSucceeded: PropTypes.bool.isRequired,
plugins: PropTypes.object.isRequired plugins: PropTypes.object.isRequired,
}; };
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentEnvironment: makeSelectCurrentEnv(), currentEnvironment: makeSelectCurrentEnv(),
isLoading: makeSelectIsLoading(), isLoading: makeSelectIsLoading(),
pluginActionSucceeded: makeSelectPluginDeleteAction(), pluginActionSucceeded: makeSelectPluginDeleteAction(),
plugins: makeSelectPlugins() plugins: makeSelectPlugins(),
}); });
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
@ -113,15 +113,15 @@ function mapDispatchToProps(dispatch) {
{ {
getPlugins, getPlugins,
onDeletePluginClick, onDeletePluginClick,
onDeletePluginConfirm onDeletePluginConfirm,
}, },
dispatch dispatch,
); );
} }
const withConnect = connect( const withConnect = connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps,
); );
/* Remove this line if the container doesn't have a route and /* Remove this line if the container doesn't have a route and
@ -137,5 +137,5 @@ const withSaga = injectSaga({ key: 'listPluginsPage', saga });
export default compose( export default compose(
withReducer, withReducer,
withSaga, withSaga,
withConnect withConnect,
)(ListPluginsPage); )(ListPluginsPage);

View File

@ -18,7 +18,7 @@ import { translationMessages } from './i18n';
const LoadableApp = Loadable({ const LoadableApp = Loadable({
loader: () => import('containers/App'), loader: () => import('containers/App'),
loading: LoadingIndicatorPage loading: LoadingIndicatorPage,
}); });
const tryRequireRoot = source => { const tryRequireRoot = source => {
@ -70,7 +70,7 @@ function Comp(props) {
if (window.Cypress) { if (window.Cypress) {
window.__store__ = Object.assign(window.__store__ || {}, { window.__store__ = Object.assign(window.__store__ || {}, {
[pluginId]: store [pluginId]: store,
}); });
} }
@ -105,7 +105,7 @@ strapi.registerPlugin({
name: pluginPkg.strapi.name, name: pluginPkg.strapi.name,
pluginRequirements, pluginRequirements,
preventComponentRendering: false, preventComponentRendering: false,
translationMessages translationMessages,
}); });
// Export store // Export store

View File

@ -0,0 +1 @@
<svg width="13" height="11" xmlns="http://www.w3.org/2000/svg"><g fill="#4B515A" fill-rule="evenodd"><rect x="4" y="8" width="9" height="3" rx="1.5"/><rect y="4" width="9" height="3" rx="1.5"/><rect x="3" width="9" height="3" rx="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 246 B

View File

@ -0,0 +1 @@
<svg width="13" height="11" xmlns="http://www.w3.org/2000/svg"><g fill="#007EFF" fill-rule="evenodd"><rect x="4" y="8" width="9" height="3" rx="1.5"/><rect y="4" width="9" height="3" rx="1.5"/><rect x="3" width="9" height="3" rx="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 246 B

View File

@ -0,0 +1,62 @@
/**
*
* NavLink
*
*/
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { isObject } from 'lodash';
import cn from 'classnames';
import styles from './styles.scss';
function NavLink(props) {
let content = props.children;
if (typeof props.message === 'string') {
content = props.message;
}
if (isObject(props.message) && props.message.id) {
content = <FormattedMessage id={props.message.id} />;
}
let icon = <i className={`fa ${props.icon}`} />;
if (props.icon === 'layout') {
icon = <i className={cn(styles.fa, styles.layout)} />;
}
return (
<Link to={props.url} className={cn(styles.navLink)}>
{icon}
{content}
</Link>
);
}
NavLink.defaultProps = {
children: '',
icon: '',
message: '',
url: '',
};
NavLink.propTypes = {
children: PropTypes.node,
icon: PropTypes.string,
message: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
PropTypes.shape({
id: PropTypes.string,
params: PropTypes.object,
}),
]),
url: PropTypes.string,
};
export default NavLink;

View File

@ -0,0 +1,28 @@
a.navLink {
text-decoration: none;
width: 100%;
display: block;
span, i {
color: #333740;
}
span {
font-size: 13px;
}
i {
margin-right: 10px;
width: 13px;
height: 11px;
}
.fa.layout {
background-image: url('../../assets/icons/icon_layout.svg');
display: inline-block;
}
&:hover {
span, i {
color: #007EFF;
}
.fa.layout {
background-image: url('../../assets/icons/icon_layout_hover.svg');
}
}
}

View File

@ -2,16 +2,19 @@ import { map, omit } from 'lodash';
import request from 'utils/request'; import request from 'utils/request';
// This method is executed before the load of the plugin // This method is executed before the load of the plugin
const bootstrap = (plugin) => new Promise((resolve, reject) => { const bootstrap = plugin =>
new Promise((resolve, reject) => {
request('/content-manager/models', { method: 'GET' }) request('/content-manager/models', { method: 'GET' })
.then(models => { .then(models => {
const menu = [{ const menu = [
{
name: 'Content Types', name: 'Content Types',
links: map(omit(models.models.models, 'plugins'), (model, key) => ({ links: map(omit(models.models.models, 'plugins'), (model, key) => ({
label: model.labelPlural || model.label || key, label: model.labelPlural || model.label || key,
destination: key, destination: key,
})), })),
}]; },
];
plugin.leftMenuSections = menu; plugin.leftMenuSections = menu;
resolve(plugin); resolve(plugin);
}) })

View File

@ -7,7 +7,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux'; import { bindActionCreators, compose } from 'redux';
import { Link } from 'react-router-dom';
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from 'reselect';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
@ -19,7 +18,7 @@ import {
toNumber, toNumber,
toString, toString,
truncate, truncate,
replace replace,
} from 'lodash'; } from 'lodash';
import HTML5Backend from 'react-dnd-html5-backend'; import HTML5Backend from 'react-dnd-html5-backend';
import { DragDropContext } from 'react-dnd'; import { DragDropContext } from 'react-dnd';
@ -32,7 +31,7 @@ import EmptyAttributesBlock from 'components/EmptyAttributesBlock';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import PluginHeader from 'components/PluginHeader'; import PluginHeader from 'components/PluginHeader';
import PopUpWarning from 'components/PopUpWarning'; import PopUpWarning from 'components/PopUpWarning';
import ListRow from 'components/ListRow'; import NavLink from 'components/NavLink';
// Plugin's components // Plugin's components
import CustomDragLayer from 'components/CustomDragLayer'; import CustomDragLayer from 'components/CustomDragLayer';
import Edit from 'components/Edit'; import Edit from 'components/Edit';
@ -59,13 +58,15 @@ import {
resetProps, resetProps,
setFileRelations, setFileRelations,
setFormErrors, setFormErrors,
submit submit,
} from './actions'; } from './actions';
import reducer from './reducer'; import reducer from './reducer';
import saga from './saga'; import saga from './saga';
import makeSelectEditPage from './selectors'; import makeSelectEditPage from './selectors';
import styles from './styles.scss'; import styles from './styles.scss';
const pluginId = 'content-manager';
export class EditPage extends React.Component { export class EditPage extends React.Component {
state = { showWarning: false, showWarningDelete: false }; state = { showWarning: false, showWarningDelete: false };
@ -87,17 +88,17 @@ export class EditPage extends React.Component {
includes(this.props.location.search, '?redirectUrl') includes(this.props.location.search, '?redirectUrl')
) { ) {
const redirectUrl = this.props.location.search.split( const redirectUrl = this.props.location.search.split(
'?redirectUrl=' '?redirectUrl=',
)[1]; )[1];
this.props.history.push({ this.props.history.push({
pathname: redirectUrl.split('?')[0], pathname: redirectUrl.split('?')[0],
search: redirectUrl.split('?')[1] search: redirectUrl.split('?')[1],
}); });
} else { } else {
this.props.history.push({ this.props.history.push({
pathname: replace(this.props.location.pathname, '/create', ''), pathname: replace(this.props.location.pathname, '/create', ''),
search: `?source=${this.getSource()}` search: `?source=${this.getSource()}`,
}); });
} }
} }
@ -122,7 +123,7 @@ export class EditPage extends React.Component {
getLayout = () => getLayout = () =>
bindLayout.call( bindLayout.call(
this, this,
get(this.props.schema, ['layout', this.getModelName()], {}) get(this.props.schema, ['layout', this.getModelName()], {}),
); );
/** /**
@ -135,9 +136,9 @@ export class EditPage extends React.Component {
this.props.editPage.formValidations, this.props.editPage.formValidations,
[ [
findIndex(this.props.editPage.formValidations, ['name', name]), findIndex(this.props.editPage.formValidations, ['name', name]),
'validations' 'validations',
], ],
{} {},
); );
getDisplayedFields = () => getDisplayedFields = () =>
@ -153,7 +154,7 @@ export class EditPage extends React.Component {
'models', 'models',
'plugins', 'plugins',
this.getSource(), this.getSource(),
this.getModelName() this.getModelName(),
]); ]);
/** /**
@ -179,12 +180,12 @@ export class EditPage extends React.Component {
* @return {Object} * @return {Object}
*/ */
getSchema = () => getSchema = () =>
this.getSource() !== 'content-manager' this.getSource() !== pluginId
? get(this.props.schema, [ ? get(this.props.schema, [
'models', 'models',
'plugins', 'plugins',
this.getSource(), this.getSource(),
this.getModelName() this.getModelName(),
]) ])
: get(this.props.schema, ['models', this.getModelName()]); : get(this.props.schema, ['models', this.getModelName()]);
@ -195,18 +196,18 @@ export class EditPage extends React.Component {
const primaryKey = this.getModel().primaryKey; const primaryKey = this.getModel().primaryKey;
const { const {
match: { match: {
params: { id } params: { id },
} },
} = this.props; } = this.props;
const title = get( const title = get(
this.getSchema(), this.getSchema(),
'editDisplay.displayedField', 'editDisplay.displayedField',
primaryKey primaryKey,
); );
const valueToDisplay = get( const valueToDisplay = get(
this.props.editPage, this.props.editPage,
['initialRecord', title], ['initialRecord', title],
id id,
); );
return isEmpty(valueToDisplay) return isEmpty(valueToDisplay)
@ -220,6 +221,25 @@ export class EditPage extends React.Component {
*/ */
getSource = () => getQueryParameters(this.props.location.search, 'source'); getSource = () => getQueryParameters(this.props.location.search, 'source');
/**
* Get url base to create edit layout link
* @type {String} url base
*/
getContentManagerBaseUrl = () => {
let url = `/plugins/${pluginId}/ctm-configurations/edit-settings/`;
if (this.getSource() === 'users-permissions') {
url = `${url}plugins/${this.getSource()}/`;
}
return url;
};
/**
* Access url base from injected component to create edit model link
* @type {String} url base
*/
getContentTypeBuilderBaseUrl = () => '/plugins/content-type-builder/models/';
/** /**
* Initialize component * Initialize component
*/ */
@ -229,7 +249,7 @@ export class EditPage extends React.Component {
this.isCreating(), this.isCreating(),
this.getSource(), this.getSource(),
this.getModelAttributes(), this.getModelAttributes(),
this.getDisplayedFields() this.getDisplayedFields(),
); );
if (!this.isCreating()) { if (!this.isCreating()) {
@ -240,7 +260,7 @@ export class EditPage extends React.Component {
// Get all relations made with the upload plugin // Get all relations made with the upload plugin
const fileRelations = Object.keys( const fileRelations = Object.keys(
get(this.getSchema(), 'relations', {}) get(this.getSchema(), 'relations', {}),
).reduce((acc, current) => { ).reduce((acc, current) => {
const association = get(this.getSchema(), ['relations', current], {}); const association = get(this.getSchema(), ['relations', current], {});
@ -250,7 +270,7 @@ export class EditPage extends React.Component {
) { ) {
const relation = { const relation = {
name: current, name: current,
multiple: association.nature === 'manyToManyMorph' multiple: association.nature === 'manyToManyMorph',
}; };
acc.push(relation); acc.push(relation);
@ -265,7 +285,7 @@ export class EditPage extends React.Component {
handleAddRelationItem = ({ key, value }) => { handleAddRelationItem = ({ key, value }) => {
this.props.addRelationItem({ this.props.addRelationItem({
key, key,
value value,
}); });
}; };
@ -276,19 +296,19 @@ export class EditPage extends React.Component {
return this.props.changeData({ return this.props.changeData({
target: { target: {
name: `record.${target.name}`, name: `record.${target.name}`,
value: defaultValue value: defaultValue,
} },
}); });
} }
const errorIndex = findIndex(this.props.editPage.formErrors, [ const errorIndex = findIndex(this.props.editPage.formErrors, [
'name', 'name',
target.name target.name,
]); ]);
const errors = inputValidations( const errors = inputValidations(
target.value, target.value,
this.getAttributeValidations(target.name), this.getAttributeValidations(target.name),
target.type target.type,
); );
const formErrors = cloneDeep(this.props.editPage.formErrors); const formErrors = cloneDeep(this.props.editPage.formErrors);
@ -308,7 +328,7 @@ export class EditPage extends React.Component {
// Check if date // Check if date
if ( if (
['float', 'integer', 'biginteger', 'decimal'].indexOf( ['float', 'integer', 'biginteger', 'decimal'].indexOf(
get(this.getSchema(), ['fields', e.target.name, 'type']) get(this.getSchema(), ['fields', e.target.name, 'type']),
) !== -1 ) !== -1
) { ) {
value = toNumber(e.target.value); value = toNumber(e.target.value);
@ -316,7 +336,7 @@ export class EditPage extends React.Component {
const target = { const target = {
name: `record.${e.target.name}`, name: `record.${e.target.name}`,
value value,
}; };
this.props.changeData({ target }); this.props.changeData({ target });
@ -336,14 +356,14 @@ export class EditPage extends React.Component {
handleGoBack = () => this.props.history.goBack(); handleGoBack = () => this.props.history.goBack();
handleRedirect = ({ model, id, source = 'content-manager' }) => { handleRedirect = ({ model, id, source = pluginId }) => {
/* eslint-disable */ /* eslint-disable */
switch (model) { switch (model) {
case 'permission': case 'permission':
case 'role': case 'role':
case 'file': case 'file':
// Exclude special models which are handled by plugins. // Exclude special models which are handled by plugins.
if (source !== 'content-manager') { if (source !== pluginId) {
break; break;
} }
default: default:
@ -355,8 +375,8 @@ export class EditPage extends React.Component {
pathname, pathname,
search: `?source=${source}&redirectURI=${generateRedirectURI({ search: `?source=${source}&redirectURI=${generateRedirectURI({
model, model,
search: `?source=${source}` search: `?source=${source}`,
})}` })}`,
}); });
} }
/* eslint-enable */ /* eslint-enable */
@ -366,7 +386,7 @@ export class EditPage extends React.Component {
e.preventDefault(); e.preventDefault();
const formErrors = checkFormValidity( const formErrors = checkFormValidity(
this.generateFormFromRecord(), this.generateFormFromRecord(),
this.props.editPage.formValidations this.props.editPage.formValidations,
); );
if (isEmpty(formErrors)) { if (isEmpty(formErrors)) {
@ -376,24 +396,20 @@ export class EditPage extends React.Component {
this.props.setFormErrors(formErrors); this.props.setFormErrors(formErrors);
}; };
hasDisplayedRelations = () => {
return this.getDisplayedRelations().length > 0;
};
hasDisplayedDevSection = () => {
return process.env.NODE_ENV === 'development';
};
hasDisplayedRightSection = () => {
return this.hasDisplayedRelations || this.hasDisplayedDevSection;
};
hasDisplayedFields = () => { hasDisplayedFields = () => {
return get(this.getModel(), ['editDisplay', 'fields'], []).length > 0; return get(this.getModel(), ['editDisplay', 'fields'], []).length > 0;
}; };
isCreating = () => this.props.match.params.id === 'create'; isCreating = () => this.props.match.params.id === 'create';
/**
* Check environment
* @type {boolean} current env is dev
*/
isDevEnvironment = () => {
return process.env.NODE_ENV === 'development';
};
isRelationComponentNull = () => isRelationComponentNull = () =>
Object.keys(get(this.getSchema(), 'relations', {})).filter( Object.keys(get(this.getSchema(), 'relations', {})).filter(
relation => relation =>
@ -401,7 +417,7 @@ export class EditPage extends React.Component {
(!get(this.getSchema(), ['relations', relation, 'nature'], '') (!get(this.getSchema(), ['relations', relation, 'nature'], '')
.toLowerCase() .toLowerCase()
.includes('morph') || .includes('morph') ||
!get(this.getSchema(), ['relations', relation, relation])) !get(this.getSchema(), ['relations', relation, relation])),
).length === 0; ).length === 0;
// NOTE: technical debt that needs to be redone // NOTE: technical debt that needs to be redone
@ -412,25 +428,43 @@ export class EditPage extends React.Component {
return acc; return acc;
}, {}); }, {});
/**
* Render the edit layout link
* @type {NavLink}
*/
layoutLink = () => {
// Retrieve URL
const url = `${this.getContentManagerBaseUrl()}${this.getModelName()}`;
// Link props to display
const message = {
message: {
id: `${pluginId}.containers.Edit.Link.Layout`,
},
icon: 'layout',
};
return <NavLink {...message} url={url} />;
};
pluginHeaderActions = () => [ pluginHeaderActions = () => [
{ {
label: 'content-manager.containers.Edit.reset', label: `${pluginId}.containers.Edit.reset`,
kind: 'secondary', kind: 'secondary',
onClick: this.toggle, onClick: this.toggle,
type: 'button', type: 'button',
disabled: this.showLoaders() disabled: this.showLoaders(),
}, },
{ {
kind: 'primary', kind: 'primary',
label: 'content-manager.containers.Edit.submit', label: `${pluginId}.containers.Edit.submit`,
onClick: this.handleSubmit, onClick: this.handleSubmit,
type: 'submit', type: 'submit',
loader: this.props.editPage.showLoader, loader: this.props.editPage.showLoader,
style: this.props.editPage.showLoader style: this.props.editPage.showLoader
? { marginRight: '18px', flexGrow: 2 } ? { marginRight: '18px', flexGrow: 2 }
: { flexGrow: 2 }, : { flexGrow: 2 },
disabled: this.showLoaders() disabled: this.showLoaders(),
} },
]; ];
pluginHeaderSubActions = () => { pluginHeaderSubActions = () => {
@ -443,18 +477,67 @@ export class EditPage extends React.Component {
kind: 'delete', kind: 'delete',
onClick: this.toggleDelete, onClick: this.toggleDelete,
type: 'button', type: 'button',
disabled: this.showLoaders() disabled: this.showLoaders(),
} },
]; ];
return subActions; return subActions;
/* eslint-enable indent */ /* eslint-enable indent */
}; };
/**
* Retrieve external links from injected components
* @type {Array} List of external links to display
*/
retrieveLinksContainerComponent = () => {
// Should be retrieved from the global props (@soupette)
const { plugins } = this.context;
const appPlugins = plugins.toJS();
const componentToInject = Object.keys(appPlugins).reduce((acc, current) => {
// Retrieve injected compos from plugin
// if compo can be injected in left.links area push the compo in the array
const currentPlugin = appPlugins[current];
const injectedComponents = get(currentPlugin, 'injectedComponents', []);
const compos = injectedComponents
.filter(compo => {
return (
compo.plugin === `${pluginId}.editPage` &&
compo.area === 'right.links'
);
})
.map(compo => {
const Component = compo.component;
return (
<li key={compo.key}>
<Component {...this} {...compo.props} />
</li>
);
});
return [...acc, ...compos];
}, []);
return componentToInject;
};
shouldDisplayedRelations = () => {
return this.getDisplayedRelations().length > 0;
};
/**
* Right section to display if needed
* @type {boolean}
*/
shouldDisplayedRightSection = () => {
return this.shouldDisplayedRelations() || this.isDevEnvironment();
};
showLoaders = () => { showLoaders = () => {
const { const {
editPage: { isLoading }, editPage: { isLoading },
schema: { layout } schema: { layout },
} = this.props; } = this.props;
return ( return (
@ -468,72 +551,38 @@ export class EditPage extends React.Component {
toggleDelete = () => toggleDelete = () =>
this.setState(prevState => ({ this.setState(prevState => ({
showWarningDelete: !prevState.showWarningDelete showWarningDelete: !prevState.showWarningDelete,
})); }));
layoutLink = () => { /**
const url = this.getContentManagerBaseUrl() + this.getModelName(); * Render internal and external links
* @type {Array} List of all links to display
*/
renderNavLinks = () => {
// Add ctm link as list item to external links array to return the entire list
let ctmLink = <li key={`${pluginId}.link`}>{this.layoutLink()}</li>;
return <Link to={url}>Configure the layout</Link>; return [ctmLink, ...this.retrieveLinksContainerComponent()];
};
getContentManagerBaseUrl = () => {
if (this.getModelName() !== 'user') {
return '/plugins/content-manager/ctm-configurations/edit-settings/';
} else {
return '/plugins/content-manager/ctm-configurations/edit-settings/plugins/users-permissions/';
}
};
getContentTypeBuilderBaseUrl = () => {
return '/plugins/content-type-builder/models/';
};
retrieveLinksContainerComponent = () => {
// Should be retrieved from the global props (@soupette)
const { plugins } = this.context;
const appPlugins = plugins.toJS();
const componentToInject = Object.keys(appPlugins).reduce((acc, current) => {
// Retrieve injected compo from plugin
// if compo can be injected in left.links area push the compo in the array
const currentPlugin = appPlugins[current];
const injectedComponents = get(currentPlugin, 'injectedComponents', []);
const compos = injectedComponents
.filter(compo => {
return (
compo.plugin === 'content-manager.editPage' &&
compo.area === 'left.links'
);
})
.map(compo => {
const Component = compo.component;
return <Component {...this} key={compo.key} />;
});
return [...acc, ...compos];
}, []);
return componentToInject;
}; };
renderEdit = () => { renderEdit = () => {
const { const {
editPage, editPage,
location: { search } location: { search },
} = this.props; } = this.props;
const source = getQueryParameters(search, 'source'); const source = getQueryParameters(search, 'source');
const basePath = '/plugins/content-manager/ctm-configurations'; const basePath = `/plugins/${pluginId}/ctm-configurations`;
const pathname = const pathname =
source !== 'content-manager' source !== pluginId
? `${basePath}/plugins/${source}/${this.getModelName()}` ? `${basePath}/plugins/${source}/${this.getModelName()}`
: `${basePath}/${this.getModelName()}`; : `${basePath}/${this.getModelName()}`;
if (this.showLoaders()) { if (this.showLoaders()) {
return ( return (
<div <div
className={!this.hasDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'} className={
!this.shouldDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'
}
> >
<div className={styles.main_wrapper}> <div className={styles.main_wrapper}>
<LoadingIndicator /> <LoadingIndicator />
@ -545,11 +594,13 @@ export class EditPage extends React.Component {
if (!this.hasDisplayedFields()) { if (!this.hasDisplayedFields()) {
return ( return (
<div <div
className={!this.hasDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'} className={
!this.shouldDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'
}
> >
<EmptyAttributesBlock <EmptyAttributesBlock
description="content-manager.components.EmptyAttributesBlock.description" description={`${pluginId}.components.EmptyAttributesBlock.description`}
label="content-manager.components.EmptyAttributesBlock.button" label={`${pluginId}.components.EmptyAttributesBlock.button`}
onClick={() => this.props.history.push(pathname)} onClick={() => this.props.history.push(pathname)}
/> />
</div> </div>
@ -558,7 +609,9 @@ export class EditPage extends React.Component {
return ( return (
<div <div
className={!this.hasDisplayedRightSection() ? 'col-lg-12' : 'col-lg-9'} className={
!this.shouldDisplayedRightSection() ? 'col-lg-12' : 'col-lg-9'
}
> >
<div className={styles.main_wrapper}> <div className={styles.main_wrapper}>
<Edit <Edit
@ -599,11 +652,10 @@ export class EditPage extends React.Component {
isOpen={showWarning} isOpen={showWarning}
toggleModal={this.toggle} toggleModal={this.toggle}
content={{ content={{
title: 'content-manager.popUpWarning.title', title: `${pluginId}.popUpWarning.title`,
message: message: `${pluginId}.popUpWarning.warning.cancelAllSettings`,
'content-manager.popUpWarning.warning.cancelAllSettings', cancel: `${pluginId}.popUpWarning.button.cancel`,
cancel: 'content-manager.popUpWarning.button.cancel', confirm: `${pluginId}.popUpWarning.button.confirm`,
confirm: 'content-manager.popUpWarning.button.confirm'
}} }}
popUpWarningType="danger" popUpWarningType="danger"
onConfirm={this.handleConfirm} onConfirm={this.handleConfirm}
@ -612,20 +664,19 @@ export class EditPage extends React.Component {
isOpen={showWarningDelete} isOpen={showWarningDelete}
toggleModal={this.toggleDelete} toggleModal={this.toggleDelete}
content={{ content={{
title: 'content-manager.popUpWarning.title', title: `${pluginId}.popUpWarning.title`,
message: message: `${pluginId}.popUpWarning.bodyMessage.contentType.delete`,
'content-manager.popUpWarning.bodyMessage.contentType.delete', cancel: `${pluginId}.popUpWarning.button.cancel`,
cancel: 'content-manager.popUpWarning.button.cancel', confirm: `${pluginId}.popUpWarning.button.confirm`,
confirm: 'content-manager.popUpWarning.button.confirm'
}} }}
popUpWarningType="danger" popUpWarningType="danger"
onConfirm={this.handleConfirm} onConfirm={this.handleConfirm}
/> />
<div className="row"> <div className="row">
{this.renderEdit()} {this.renderEdit()}
{this.hasDisplayedRightSection() && ( {this.shouldDisplayedRightSection() && (
<div className={cn('col-lg-3')}> <div className={cn('col-lg-3')}>
{this.hasDisplayedRelations() && ( {this.shouldDisplayedRelations() && (
<div className={styles.sub_wrapper}> <div className={styles.sub_wrapper}>
<EditRelations <EditRelations
changeData={this.props.changeData} changeData={this.props.changeData}
@ -644,12 +695,9 @@ export class EditPage extends React.Component {
</div> </div>
)} )}
{this.hasDisplayedDevSection() && ( {this.isDevEnvironment() && (
<div className={styles.links_wrapper}> <div className={styles.links_wrapper}>
<ul> <ul>{this.renderNavLinks()}</ul>
<li>{this.layoutLink()}</li>
<li>{this.retrieveLinksContainerComponent()}</li>
</ul>
</div> </div>
)} )}
</div> </div>
@ -663,11 +711,11 @@ export class EditPage extends React.Component {
} }
EditPage.contextTypes = { EditPage.contextTypes = {
plugins: PropTypes.object plugins: PropTypes.object,
}; };
EditPage.defaultProps = { EditPage.defaultProps = {
schema: {} schema: {},
}; };
EditPage.propTypes = { EditPage.propTypes = {
@ -688,7 +736,7 @@ EditPage.propTypes = {
schema: PropTypes.object, schema: PropTypes.object,
setFileRelations: PropTypes.func.isRequired, setFileRelations: PropTypes.func.isRequired,
setFormErrors: PropTypes.func.isRequired, setFormErrors: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired submit: PropTypes.func.isRequired,
}; };
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
@ -706,20 +754,20 @@ function mapDispatchToProps(dispatch) {
resetProps, resetProps,
setFileRelations, setFileRelations,
setFormErrors, setFormErrors,
submit submit,
}, },
dispatch dispatch,
); );
} }
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
editPage: makeSelectEditPage(), editPage: makeSelectEditPage(),
schema: makeSelectSchema() schema: makeSelectSchema(),
}); });
const withConnect = connect( const withConnect = connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps,
); );
const withReducer = injectReducer({ key: 'editPage', reducer }); const withReducer = injectReducer({ key: 'editPage', reducer });
@ -728,5 +776,5 @@ const withSaga = injectSaga({ key: 'editPage', saga });
export default compose( export default compose(
withReducer, withReducer,
withSaga, withSaga,
withConnect withConnect,
)(DragDropContext(HTML5Backend)(EditPage)); )(DragDropContext(HTML5Backend)(EditPage));

View File

@ -19,7 +19,7 @@
.sub_wrapper { .sub_wrapper {
padding: 0 20px 1px; padding: 0 20px 1px;
margin-bottom: 40px; margin-bottom: 28px;
} }
.links_wrapper { .links_wrapper {
@ -33,13 +33,5 @@
&:first-of-type { &:first-of-type {
border-color: transparent; border-color: transparent;
} }
a {
text-decoration: none;
width: 100%;
display: block;
p {
font-size: 13px;
}
}
} }
} }

View File

@ -35,6 +35,8 @@
"containers.Edit.returnList": "Return to list", "containers.Edit.returnList": "Return to list",
"containers.Edit.seeDetails": "Details", "containers.Edit.seeDetails": "Details",
"containers.Edit.submit": "Save", "containers.Edit.submit": "Save",
"containers.Edit.Link.Layout": "Configure the layout",
"containers.Edit.Link.Fields": "Edit the fields",
"containers.Home.introduction": "To edit your entries go to the specific link in the left menu. This plugin doesn't have a proper way to edit settings and it's still under active development.", "containers.Home.introduction": "To edit your entries go to the specific link in the left menu. This plugin doesn't have a proper way to edit settings and it's still under active development.",
"containers.Home.pluginHeaderDescription": "Manage your entries through a powerful and beautiful interface.", "containers.Home.pluginHeaderDescription": "Manage your entries through a powerful and beautiful interface.",
"containers.Home.pluginHeaderTitle": "Content Manager", "containers.Home.pluginHeaderTitle": "Content Manager",

View File

@ -35,6 +35,8 @@
"containers.Edit.returnList": "Retourner à la liste", "containers.Edit.returnList": "Retourner à la liste",
"containers.Edit.seeDetails": "Détails", "containers.Edit.seeDetails": "Détails",
"containers.Edit.submit": "Valider", "containers.Edit.submit": "Valider",
"containers.Edit.Link.Layout": "Paramétrer la vue",
"containers.Edit.Link.Fields": "Éditer le modèle",
"containers.Home.introduction": "Pour éditer du contenu, choisissez un type de données dans le menu de gauche.", "containers.Home.introduction": "Pour éditer du contenu, choisissez un type de données dans le menu de gauche.",
"containers.Home.pluginHeaderDescription": "Créer et modifier votre type de contenu", "containers.Home.pluginHeaderDescription": "Créer et modifier votre type de contenu",
"containers.Home.pluginHeaderTitle": "Type de contenu", "containers.Home.pluginHeaderTitle": "Type de contenu",
@ -120,4 +122,3 @@
"success.record.delete": "Supprimé", "success.record.delete": "Supprimé",
"success.record.save": "Sauvegardé" "success.record.save": "Sauvegardé"
} }

View File

@ -5,10 +5,11 @@ const {
getApisKeys, getApisKeys,
getApisUploadRelations, getApisUploadRelations,
getEditDisplayAvailableFieldsPath, getEditDisplayAvailableFieldsPath,
getEditDisplayFieldsPath getEditDisplayFieldsPath,
} = require('./utils/getters'); } = require('./utils/getters');
const splitted = str => str.split('.'); const splitted = str => str.split('.');
const pickData = (model) => _.pick(model, [ const pickData = model =>
_.pick(model, [
'info', 'info',
'connection', 'connection',
'collectionName', 'collectionName',
@ -20,7 +21,7 @@ const pickData = (model) => _.pick(model, [
'options', 'options',
'loadedModel', 'loadedModel',
'primaryKey', 'primaryKey',
'associations' 'associations',
]); ]);
module.exports = async cb => { module.exports = async cb => {
@ -28,7 +29,11 @@ module.exports = async cb => {
const pluginsLayout = Object.keys(strapi.plugins).reduce((acc, current) => { const pluginsLayout = Object.keys(strapi.plugins).reduce((acc, current) => {
const models = _.get(strapi.plugins, [current, 'config', 'layout'], {}); const models = _.get(strapi.plugins, [current, 'config', 'layout'], {});
Object.keys(models).forEach(model => { Object.keys(models).forEach(model => {
const layout = _.get(strapi.plugins, [current, 'config', 'layout', model], {}); const layout = _.get(
strapi.plugins,
[current, 'config', 'layout', model],
{}
);
acc[model] = layout; acc[model] = layout;
}); });
@ -69,13 +74,14 @@ module.exports = async cb => {
models: { models: {
plugins: {}, plugins: {},
}, },
layout: {} layout: {},
}; };
// Populate the schema object // Populate the schema object
const buildSchema = (model, name, plugin = false) => { const buildSchema = (model, name, plugin = false) => {
// Model data // Model data
const schemaModel = Object.assign({ const schemaModel = Object.assign(
{
label: _.upperFirst(name), label: _.upperFirst(name),
labelPlural: _.upperFirst(pluralize(name)), labelPlural: _.upperFirst(pluralize(name)),
orm: model.orm || 'mongoose', orm: model.orm || 'mongoose',
@ -92,13 +98,22 @@ module.exports = async cb => {
fields: [], fields: [],
relations: [], relations: [],
}, },
}, model); },
model
);
const fieldsToRemove = []; const fieldsToRemove = [];
// Fields (non relation) // Fields (non relation)
const fields = _.mapValues(_.pickBy(model.attributes, attribute => const fields = _.mapValues(
!attribute.model && !attribute.collection _.pickBy(
), (value, attribute) => { model.attributes,
const fieldClassName = _.get(tempLayout, [name, 'attributes', attribute, 'className'], ''); attribute => !attribute.model && !attribute.collection
),
(value, attribute) => {
const fieldClassName = _.get(
tempLayout,
[name, 'attributes', attribute, 'className'],
''
);
if (fieldClassName === 'd-none') { if (fieldClassName === 'd-none') {
fieldsToRemove.push(attribute); fieldsToRemove.push(attribute);
@ -110,7 +125,8 @@ module.exports = async cb => {
type: value.type || 'string', type: value.type || 'string',
disabled: false, disabled: false,
}; };
}); }
);
// Don't display fields that are hidden by default like the resetPasswordToken for the model user // Don't display fields that are hidden by default like the resetPasswordToken for the model user
fieldsToRemove.forEach(field => { fieldsToRemove.forEach(field => {
@ -128,7 +144,11 @@ module.exports = async cb => {
const attrType = schemaModel.fields[attr].type; const attrType = schemaModel.fields[attr].type;
const sortable = attrType !== 'json' && attrType !== 'array'; const sortable = attrType !== 'json' && attrType !== 'array';
return Object.assign(schemaModel.fields[attr], { name: attr, sortable, searchable: sortable }); return Object.assign(schemaModel.fields[attr], {
name: attr,
sortable,
searchable: sortable,
});
}) })
// Retrieve only the fourth first items // Retrieve only the fourth first items
.slice(0, 4); .slice(0, 4);
@ -143,13 +163,24 @@ module.exports = async cb => {
// This object will be used to customise the label and description and so on of an input. // This object will be used to customise the label and description and so on of an input.
// TODO: maybe add the customBootstrapClass in it; // TODO: maybe add the customBootstrapClass in it;
schemaModel.editDisplay.availableFields = Object.keys(schemaModel.fields).reduce((acc, current) => { schemaModel.editDisplay.availableFields = Object.keys(
schemaModel.fields
).reduce((acc, current) => {
acc[current] = Object.assign( acc[current] = Object.assign(
_.pick(_.get(schemaModel, ['fields', current], {}), ['label', 'type', 'description', 'name']), _.pick(_.get(schemaModel, ['fields', current], {}), [
'label',
'type',
'description',
'name',
]),
{ {
editable: ['updatedAt', 'createdAt', 'updated_at', 'created_at'].indexOf(current) === -1, editable:
['updatedAt', 'createdAt', 'updated_at', 'created_at'].indexOf(
current
) === -1,
placeholder: '', placeholder: '',
}); }
);
return acc; return acc;
}, {}); }, {});
@ -158,12 +189,36 @@ module.exports = async cb => {
// Model relations // Model relations
schemaModel.relations = model.associations.reduce((acc, current) => { schemaModel.relations = model.associations.reduce((acc, current) => {
const label = _.upperFirst(current.alias); const label = _.upperFirst(current.alias);
const displayedAttribute = current.plugin ? // Value to modified to custom what's displayed in the react-select const displayedAttribute = current.plugin // Value to modified to custom what's displayed in the react-select
_.get(pluginsModel, [current.plugin, 'models', current.model || current.collection, 'info', 'mainField']) || ? _.get(pluginsModel, [
_.findKey(_.get(pluginsModel, [current.plugin, 'models', current.model || current.collection, 'attributes']), { type : 'string'}) || current.plugin,
'id' : 'models',
_.get(models, [current.model || current.collection, 'info', 'mainField']) || current.model || current.collection,
_.findKey(_.get(models, [current.model || current.collection, 'attributes']), { type : 'string'}) || 'info',
'mainField',
]) ||
_.findKey(
_.get(pluginsModel, [
current.plugin,
'models',
current.model || current.collection,
'attributes',
]),
{ type: 'string' }
) ||
'id'
: _.get(models, [
current.model || current.collection,
'info',
'mainField',
]) ||
_.findKey(
_.get(models, [
current.model || current.collection,
'attributes',
]),
{ type: 'string' }
) ||
'id'; 'id';
acc[current.alias] = { acc[current.alias] = {
@ -175,15 +230,26 @@ module.exports = async cb => {
return acc; return acc;
}, {}); }, {});
const relationsArray = Object.keys(schemaModel.relations).filter(relation => { const relationsArray = Object.keys(schemaModel.relations).filter(
const isUploadRelation = _.get(schemaModel, ['relations', relation, 'plugin'], '') === 'upload'; relation => {
const isMorphSide = _.get(schemaModel, ['relations', relation, 'nature'], '').toLowerCase().includes('morp') && _.get(schemaModel, ['relations', relation, relation]) !== undefined; const isUploadRelation =
_.get(schemaModel, ['relations', relation, 'plugin'], '') ===
'upload';
const isMorphSide =
_.get(schemaModel, ['relations', relation, 'nature'], '')
.toLowerCase()
.includes('morp') &&
_.get(schemaModel, ['relations', relation, relation]) !== undefined;
return !isUploadRelation && !isMorphSide; return !isUploadRelation && !isMorphSide;
}); }
);
const uploadRelations = Object.keys(schemaModel.relations).reduce((acc, current) => { const uploadRelations = Object.keys(schemaModel.relations).reduce(
if (_.get(schemaModel, ['relations', current, 'plugin']) === 'upload') { (acc, current) => {
if (
_.get(schemaModel, ['relations', current, 'plugin']) === 'upload'
) {
const model = _.get(schemaModel, ['relations', current]); const model = _.get(schemaModel, ['relations', current]);
acc[current] = { acc[current] = {
@ -199,13 +265,20 @@ module.exports = async cb => {
} }
return acc; return acc;
}, {}); },
{}
);
schemaModel.editDisplay.availableFields = _.merge(schemaModel.editDisplay.availableFields, uploadRelations); schemaModel.editDisplay.availableFields = _.merge(
schemaModel.editDisplay.availableFields,
uploadRelations
);
schemaModel.editDisplay.relations = relationsArray; schemaModel.editDisplay.relations = relationsArray;
} }
schemaModel.editDisplay.fields = Object.keys(schemaModel.editDisplay.availableFields); schemaModel.editDisplay.fields = Object.keys(
schemaModel.editDisplay.availableFields
);
if (plugin) { if (plugin) {
return _.set(schema.models.plugins, `${plugin}.${name}`, schemaModel); return _.set(schema.models.plugins, `${plugin}.${name}`, schemaModel);
@ -230,7 +303,7 @@ module.exports = async cb => {
const pluginStore = strapi.store({ const pluginStore = strapi.store({
environment: '', environment: '',
type: 'plugin', type: 'plugin',
name: 'content-manager' name: 'content-manager',
}); });
try { try {
@ -263,24 +336,42 @@ module.exports = async cb => {
const schemaApis = getApis(schema.models); const schemaApis = getApis(schema.models);
// Array of apis to add // Array of apis to add
const apisToAdd = schemaApis.filter(api => prevSchemaApis.indexOf(api) === -1).map(splitted); const apisToAdd = schemaApis
.filter(api => prevSchemaApis.indexOf(api) === -1)
.map(splitted);
// Array of apis to remove // Array of apis to remove
const apisToRemove = prevSchemaApis.filter(api => schemaApis.indexOf(api) === -1).map(splitted); const apisToRemove = prevSchemaApis
.filter(api => schemaApis.indexOf(api) === -1)
.map(splitted);
// Retrieve the same apis by name // Retrieve the same apis by name
const sameApis = schemaApis.filter(api => prevSchemaApis.indexOf(api) !== -1).map(splitted); const sameApis = schemaApis
.filter(api => prevSchemaApis.indexOf(api) !== -1)
.map(splitted);
// Retrieve all the field's path of the current unchanged api name // Retrieve all the field's path of the current unchanged api name
const schemaSameApisKeys = _.flattenDeep(getApisKeys(schema, sameApis)); const schemaSameApisKeys = _.flattenDeep(getApisKeys(schema, sameApis));
// Retrieve all the field's path of the previous unchanged api name // Retrieve all the field's path of the previous unchanged api name
const prevSchemaSameApisKeys = _.flattenDeep(getApisKeys(prevSchema, sameApis)); const prevSchemaSameApisKeys = _.flattenDeep(
getApisKeys(prevSchema, sameApis)
);
// Determine for the same api if we need to add some fields // Determine for the same api if we need to add some fields
const sameApisAttrToAdd = schemaSameApisKeys.filter(attr => prevSchemaSameApisKeys.indexOf(attr) === -1).map(splitted); const sameApisAttrToAdd = schemaSameApisKeys
.filter(attr => prevSchemaSameApisKeys.indexOf(attr) === -1)
.map(splitted);
// Special case for the relations // Special case for the relations
const prevSchemaSameApisUploadRelations = _.flattenDeep(getApisUploadRelations(prevSchema, sameApis)); const prevSchemaSameApisUploadRelations = _.flattenDeep(
const schemaSameApisUploadRelations = _.flattenDeep(getApisUploadRelations(schema, sameApis)); getApisUploadRelations(prevSchema, sameApis)
const sameApisUploadRelationsToAdd = schemaSameApisUploadRelations.filter(attr => prevSchemaSameApisUploadRelations.indexOf(attr) === -1).map(splitted); );
const schemaSameApisUploadRelations = _.flattenDeep(
getApisUploadRelations(schema, sameApis)
);
const sameApisUploadRelationsToAdd = schemaSameApisUploadRelations
.filter(attr => prevSchemaSameApisUploadRelations.indexOf(attr) === -1)
.map(splitted);
// Determine the fields to remove for the unchanged api name // Determine the fields to remove for the unchanged api name
const sameApisAttrToRemove = prevSchemaSameApisKeys.filter(attr => schemaSameApisKeys.indexOf(attr) === -1).map(splitted); const sameApisAttrToRemove = prevSchemaSameApisKeys
.filter(attr => schemaSameApisKeys.indexOf(attr) === -1)
.map(splitted);
// Remove api // Remove api
apisToRemove.map(apiPath => { apisToRemove.map(apiPath => {
@ -295,7 +386,8 @@ module.exports = async cb => {
// Check default sort and change it if needed // Check default sort and change it if needed
_.unset(prevSchema.models, attrPath); _.unset(prevSchema.models, attrPath);
// Retrieve the api path in the schema Object // Retrieve the api path in the schema Object
const apiPath = attrPath.length > 3 ? _.take(attrPath, 3) : _.take(attrPath, 1); const apiPath =
attrPath.length > 3 ? _.take(attrPath, 3) : _.take(attrPath, 1);
// Retrieve the listDisplay path in the schema Object // Retrieve the listDisplay path in the schema Object
const listDisplayPath = apiPath.concat('listDisplay'); const listDisplayPath = apiPath.concat('listDisplay');
const prevListDisplay = _.get(prevSchema.models, listDisplayPath); const prevListDisplay = _.get(prevSchema.models, listDisplayPath);
@ -306,24 +398,36 @@ module.exports = async cb => {
// If the user has deleted the default sort attribute in the content type builder // If the user has deleted the default sort attribute in the content type builder
// Replace it by new generated one from the current schema // Replace it by new generated one from the current schema
if (_.includes(currentAttr, defaultSort)) { if (_.includes(currentAttr, defaultSort)) {
_.set(prevSchema.models, defaultSortPath, _.get(schema.models, defaultSortPath)); _.set(
prevSchema.models,
defaultSortPath,
_.get(schema.models, defaultSortPath)
);
} }
// Update the displayed fields // Update the displayed fields
const updatedListDisplay = prevListDisplay.filter(obj => obj.name !== currentAttr.join()); const updatedListDisplay = prevListDisplay.filter(
obj => obj.name !== currentAttr.join()
);
// Retrieve the model's displayed fields for the `EditPage` // Retrieve the model's displayed fields for the `EditPage`
const fieldsPath = getEditDisplayFieldsPath(attrPath); const fieldsPath = getEditDisplayFieldsPath(attrPath);
// Retrieve the previous settings // Retrieve the previous settings
const prevEditDisplayFields = _.get(prevSchema.models, fieldsPath); const prevEditDisplayFields = _.get(prevSchema.models, fieldsPath);
// Update the fields // Update the fields
const updatedEditDisplayFields = prevEditDisplayFields.filter(field => field !== currentAttr.join()); const updatedEditDisplayFields = prevEditDisplayFields.filter(
field => field !== currentAttr.join()
);
// Set the new layout // Set the new layout
_.set(prevSchema.models, fieldsPath, updatedEditDisplayFields); _.set(prevSchema.models, fieldsPath, updatedEditDisplayFields);
if (updatedListDisplay.length === 0) { if (updatedListDisplay.length === 0) {
// Update it with the one from the generated schema // Update it with the one from the generated schema
_.set(prevSchema.models, listDisplayPath, _.get(schema.models, listDisplayPath, [])); _.set(
prevSchema.models,
listDisplayPath,
_.get(schema.models, listDisplayPath, [])
);
} else { } else {
_.set(prevSchema.models, listDisplayPath, updatedListDisplay); _.set(prevSchema.models, listDisplayPath, updatedListDisplay);
} }
@ -333,7 +437,10 @@ module.exports = async cb => {
// Here we just need to add the data from the current schema Object // Here we just need to add the data from the current schema Object
apisToAdd.map(apiPath => { apisToAdd.map(apiPath => {
const api = _.get(schema.models, apiPath); const api = _.get(schema.models, apiPath);
const { search, filters, bulkActions, pageEntries, options } = _.get(prevSchema, 'generalSettings'); const { search, filters, bulkActions, pageEntries, options } = _.get(
prevSchema,
'generalSettings'
);
_.set(api, 'options', options); _.set(api, 'options', options);
_.set(api, 'filters', filters); _.set(api, 'filters', filters);
@ -364,7 +471,13 @@ module.exports = async cb => {
sameApis.forEach(apiPath => { sameApis.forEach(apiPath => {
// This doesn't keep the prevSettings for the relations, the user will have to reset it. // This doesn't keep the prevSettings for the relations, the user will have to reset it.
// We might have to improve this if we want the order of the relations to be kept // We might have to improve this if we want the order of the relations to be kept
['relations', 'loadedModel', 'associations', 'attributes', ['editDisplay', 'relations']] [
'relations',
'loadedModel',
'associations',
'attributes',
['editDisplay', 'relations'],
]
.map(key => apiPath.concat(key)) .map(key => apiPath.concat(key))
.forEach(keyPath => { .forEach(keyPath => {
const newValue = _.get(schema.models, keyPath); const newValue = _.get(schema.models, keyPath);
@ -384,16 +497,23 @@ module.exports = async cb => {
_.set(prevSchema.models, fieldsPath, currentFields); _.set(prevSchema.models, fieldsPath, currentFields);
}); });
schemaApis.map((model) => { schemaApis.map(model => {
const isPlugin = model.includes('plugins.'); const isPlugin = model.includes('plugins.');
_.set(prevSchema.models[model], 'info', _.get(!isPlugin ? strapi.models[model] : strapi[model], 'info')); _.set(
_.set(prevSchema.models[model], 'options', _.get(!isPlugin ? strapi.models[model] : strapi[model], 'options')); prevSchema.models[model],
'info',
_.get(!isPlugin ? strapi.models[model] : strapi[model], 'info')
);
_.set(
prevSchema.models[model],
'options',
_.get(!isPlugin ? strapi.models[model] : strapi[model], 'options')
);
}); });
await pluginStore.set({ key: 'schema', value: prevSchema }); await pluginStore.set({ key: 'schema', value: prevSchema });
} catch (err) { } catch (err) {
console.log('error', err); console.log('error', err); // eslint-disable-line no-console
} }
cb(); cb();

View File

@ -0,0 +1,30 @@
/**
*
* EditViewLink
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import NavLink from 'components/NavLink';
// Create link from content-type-builder to content-manager
function EditViewLink(props) {
// Retrieve URL from props
let url = `${props.getContentTypeBuilderBaseUrl()}${props.getModelName()}`;
// Add users-permissions to URL for permission, role and user content types
if (props.getSource() === 'users-permissions') {
url = `${url}&source=${props.getSource()}`;
}
return <NavLink {...props} url={url} />;
}
EditViewLink.propTypes = {
getContentTypeBuilderBaseUrl: PropTypes.func.isRequired,
getModelName: PropTypes.func.isRequired,
getSource: PropTypes.func.isRequired,
};
export default EditViewLink;

View File

@ -1,25 +0,0 @@
/**
*
* ContentManagerEditViewLink
*
*/
import React from 'react';
import { Link } from 'react-router-dom';
function ContentManagerEditViewLink(props) {
let url = props.getContentTypeBuilderBaseUrl() + props.getModelName();
if (
props.getModelName() === 'user' ||
props.getModelName() === 'role' ||
props.getModelName() === 'permission'
) {
url = url + '&source=users-permissions';
}
return <Link to={url}>Edit the fields</Link>;
}
ContentManagerEditViewLink.propTypes = {};
export default ContentManagerEditViewLink;

View File

@ -1,10 +1,16 @@
import Link from 'components/ContentManagerEditViewLink'; import Link from './InjectedComponents/ContentManager/EditViewLink';
export default [ export default [
{ {
plugin: 'content-manager.editPage', plugin: 'content-manager.editPage',
area: 'left.links', area: 'right.links',
component: Link, component: Link,
key: 'content-type-builder.link' key: 'content-type-builder.link',
} props: {
message: {
id: 'content-manager.containers.Edit.Link.Fields',
},
icon: 'fa-cog',
},
},
]; ];