mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 23:24:03 +00:00
Relation modal ip
This commit is contained in:
parent
9fc54cd8f9
commit
a312d6da15
@ -14,6 +14,13 @@ const colors = {
|
||||
mediumGrey: '#f2f3f4',
|
||||
lightGrey: '#E9EAEB',
|
||||
},
|
||||
|
||||
// Specific colors for relations
|
||||
relations: {
|
||||
boxBkgd: '#fcfcfc',
|
||||
boxShadow: '#cad2df',
|
||||
headerBkgd: 'rgba(16,22,34,.04)',
|
||||
},
|
||||
};
|
||||
|
||||
export default colors;
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledFeaturePicker = styled.div`
|
||||
> div {
|
||||
width: 100%;
|
||||
> button {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
font-size: 1.4rem;
|
||||
line-height: 3.6rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-transform: capitalize;
|
||||
color: black;
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover,
|
||||
&:visited {
|
||||
background-color: transparent !important;
|
||||
box-shadow: none;
|
||||
color: black;
|
||||
}
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: calc(50% - 0.1rem);
|
||||
right: 10px;
|
||||
}
|
||||
> p {
|
||||
margin-top: -1px;
|
||||
margin-bottom: 0;
|
||||
padding: 0 20px 0 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
> i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
span {
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dropdown List
|
||||
> button + div {
|
||||
max-width: 100%;
|
||||
max-height: 180px;
|
||||
width: 100%;
|
||||
margin-top: -4px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
overflow: scroll;
|
||||
> button {
|
||||
height: 3.6rem;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
font-size: 1.3rem;
|
||||
font-family: Lato;
|
||||
font-weight: 400;
|
||||
font-family: Lato;
|
||||
text-transform: capitalize;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
cursor: pointer;
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0;
|
||||
background-color: rgb(255, 255, 255) !important;
|
||||
color: rgba(50, 55, 64, 0.75);
|
||||
}
|
||||
> p {
|
||||
color: rgba(50, 55, 64, 0.75);
|
||||
line-height: 3rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledFeaturePicker;
|
||||
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from 'reactstrap';
|
||||
|
||||
import StyledFeaturePicker from './StyledFeaturePicker';
|
||||
|
||||
const FeaturePicker = ({ features, onClick, plugin, selectFeature }) => {
|
||||
const [isOpen, setOpen] = React.useState(false);
|
||||
console.log(selectFeature);
|
||||
return (
|
||||
<StyledFeaturePicker>
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
toggle={() => {
|
||||
setOpen(!isOpen);
|
||||
}}
|
||||
>
|
||||
<DropdownToggle caret>
|
||||
<p>
|
||||
<i className="fa fa-caret-square-o-right" />
|
||||
{selectFeature}
|
||||
{!!plugin && <span> ({plugin})</span>}
|
||||
</p>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
{features.map(feature => {
|
||||
return (
|
||||
<DropdownItem key={feature.name} onClick={() => onClick(feature)}>
|
||||
<p>
|
||||
<i className="fa fa-caret-square-o-right" />
|
||||
{feature.name}
|
||||
{!!feature.source && (
|
||||
<span style={{ fontStyle: 'italic' }}>
|
||||
({feature.source})
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</DropdownItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</StyledFeaturePicker>
|
||||
);
|
||||
};
|
||||
|
||||
FeaturePicker.defaultProps = {
|
||||
features: [],
|
||||
onClick: () => {},
|
||||
plugin: null,
|
||||
selectFeature: '',
|
||||
};
|
||||
|
||||
FeaturePicker.propTypes = {
|
||||
features: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
plugin: PropTypes.string,
|
||||
selectFeature: PropTypes.string,
|
||||
};
|
||||
|
||||
export default FeaturePicker;
|
||||
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import Enzyme from 'enzyme';
|
||||
|
||||
import FeaturePicker from '../index';
|
||||
|
||||
describe('<FeaturePicker />', () => {
|
||||
let wrapper;
|
||||
const setOpen = jest.fn();
|
||||
const useStateSpy = jest.spyOn(React, 'useState');
|
||||
useStateSpy.mockImplementation(init => [init, setOpen]);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Enzyme.shallow(<FeaturePicker />);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('SetOpen', () => {
|
||||
it('calls setOpen with true param', () => {
|
||||
const buttonProps = wrapper.find('button').props();
|
||||
|
||||
buttonProps.onClick();
|
||||
expect(setOpen).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,28 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { colors } from 'strapi-helper-plugin';
|
||||
|
||||
const StyledRelationBox = styled.div`
|
||||
width: 20rem;
|
||||
height: 13.8rem;
|
||||
background-color: ${colors.relations.boxBkgd};
|
||||
box-shadow: 0 1px 2px ${colors.relations.boxShadow};
|
||||
border-radius: 2px;
|
||||
.box-header {
|
||||
height: 3.6rem;
|
||||
line-height: 3.6rem;
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
background-color: ${colors.relations.headerBkgd};
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.box-body {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledRelationBox;
|
||||
@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { InputTextWithErrors as InputText } from 'strapi-helper-plugin';
|
||||
|
||||
import StyledRelationBox from './StyledRelationBox';
|
||||
import FeaturePicker from '../FeaturePicker';
|
||||
|
||||
const RelationBox = ({
|
||||
autoFocus,
|
||||
didCheckErrors,
|
||||
errors,
|
||||
featureName,
|
||||
features,
|
||||
main,
|
||||
nature,
|
||||
onChange,
|
||||
selectedFeature,
|
||||
source,
|
||||
plugin,
|
||||
value,
|
||||
}) => {
|
||||
return (
|
||||
<StyledRelationBox>
|
||||
<div className="box-header">
|
||||
{main ? (
|
||||
<p>
|
||||
<i className="fa fa-caret-square-o-right" />
|
||||
{featureName}
|
||||
{!!source && <span> ({source})</span>}
|
||||
</p>
|
||||
) : (
|
||||
<FeaturePicker
|
||||
features={features}
|
||||
plugin={plugin}
|
||||
selectedFeature={selectedFeature}
|
||||
></FeaturePicker>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<InputText
|
||||
autoFocus={autoFocus}
|
||||
didCheckErrors={didCheckErrors}
|
||||
errors={errors}
|
||||
label="Field Name"
|
||||
disabled={value === '-' || nature === 'oneWay'}
|
||||
name={main ? 'name' : 'key'}
|
||||
onChange={onChange}
|
||||
type="text"
|
||||
value={nature === 'oneWay' ? '-' : value}
|
||||
/>
|
||||
</div>
|
||||
</StyledRelationBox>
|
||||
);
|
||||
};
|
||||
|
||||
RelationBox.defaultProps = {
|
||||
autoFocus: false,
|
||||
didCheckErrors: false,
|
||||
errors: [],
|
||||
main: false,
|
||||
featureName: '',
|
||||
features: [],
|
||||
nature: null,
|
||||
onClick: () => {},
|
||||
plugin: null,
|
||||
selectedModel: null,
|
||||
source: null,
|
||||
};
|
||||
|
||||
RelationBox.propTypes = {
|
||||
autoFocus: PropTypes.bool,
|
||||
didCheckErrors: PropTypes.bool,
|
||||
errors: PropTypes.array,
|
||||
main: PropTypes.bool,
|
||||
featureName: PropTypes.string,
|
||||
features: PropTypes.array,
|
||||
nature: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
plugin: PropTypes.string,
|
||||
selectedModel: PropTypes.string,
|
||||
source: PropTypes.string,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default RelationBox;
|
||||
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
describe('<RelationBox />', () => {
|
||||
it('should not crash', () => {
|
||||
expect(false).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { colors } from 'strapi-helper-plugin';
|
||||
|
||||
const RelationsWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 2.7rem 1.5rem 3.3rem 1.5rem;
|
||||
justify-content: space-between;
|
||||
> div {
|
||||
width: 200px;
|
||||
background-color: ${colors.relations.boxBkgd};
|
||||
box-shadow: 0 1px 2px ${colors.relations.boxShadow};
|
||||
}
|
||||
`;
|
||||
|
||||
export default RelationsWrapper;
|
||||
@ -7,6 +7,7 @@ import { cloneDeep, pick, set, camelCase } from 'lodash';
|
||||
import { fromJS, OrderedMap } from 'immutable';
|
||||
import {
|
||||
ADD_ATTRIBUTE_RELATION,
|
||||
ADD_ATTRIBUTE_RELATION_GROUP,
|
||||
ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE,
|
||||
ADD_ATTRIBUTE_TO_EXISTING_GROUP,
|
||||
ADD_ATTRIBUTE_TO_TEMP_CONTENT_TYPE,
|
||||
@ -32,6 +33,7 @@ import {
|
||||
ON_CHANGE_ATTRIBUTE,
|
||||
ON_CHANGE_ATTRIBUTE_GROUP,
|
||||
ON_CHANGE_RELATION,
|
||||
ON_CHANGE_RELATION_GROUP,
|
||||
ON_CHANGE_RELATION_NATURE,
|
||||
ON_CHANGE_RELATION_TARGET,
|
||||
RESET_NEW_CONTENT_TYPE_MAIN_INFOS,
|
||||
@ -47,6 +49,7 @@ import {
|
||||
SET_TEMPORARY_ATTRIBUTE,
|
||||
SET_TEMPORARY_ATTRIBUTE_GROUP,
|
||||
SET_TEMPORARY_ATTRIBUTE_RELATION,
|
||||
SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP,
|
||||
SUBMIT_CONTENT_TYPE,
|
||||
SUBMIT_CONTENT_TYPE_SUCCEEDED,
|
||||
SUBMIT_GROUP,
|
||||
@ -68,6 +71,14 @@ export function addAttributeRelation(isModelTemporary, modelName) {
|
||||
};
|
||||
}
|
||||
|
||||
export function addAttributeRelationGroup(isGroupTemporary, groupName) {
|
||||
return {
|
||||
type: ADD_ATTRIBUTE_RELATION_GROUP,
|
||||
isGroupTemporary,
|
||||
groupName,
|
||||
};
|
||||
}
|
||||
|
||||
export function addAttributeToExistingContentType(
|
||||
contentTypeName,
|
||||
attributeType
|
||||
@ -350,6 +361,17 @@ export function onChangeRelation({ target }) {
|
||||
};
|
||||
}
|
||||
|
||||
export function onChangeRelationGroup({ target }) {
|
||||
const value =
|
||||
target.name === 'unique' ? target.value : target.value.split(' ').join('');
|
||||
|
||||
return {
|
||||
type: ON_CHANGE_RELATION_GROUP,
|
||||
keys: target.name.split('.'),
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function onChangeRelationNature(nature, currentModel) {
|
||||
return {
|
||||
type: ON_CHANGE_RELATION_NATURE,
|
||||
@ -494,6 +516,23 @@ export function setTemporaryAttributeRelation(
|
||||
};
|
||||
}
|
||||
|
||||
export function setTemporaryAttributeRelationGroup(
|
||||
target,
|
||||
isGroupTemporary,
|
||||
source,
|
||||
attributeName,
|
||||
isEditing
|
||||
) {
|
||||
return {
|
||||
type: SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP,
|
||||
attributeName,
|
||||
isEditing,
|
||||
isGroupTemporary,
|
||||
source,
|
||||
target,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitContentType(oldContentTypeName, data, context, source) {
|
||||
const attributes = formatModelAttributes(data.attributes);
|
||||
const body = Object.assign(cloneDeep(data), { attributes });
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
export const ADD_ATTRIBUTE_RELATION =
|
||||
'ContentTypeBuilder/App/ADD_ATTRIBUTE_RELATION';
|
||||
export const ADD_ATTRIBUTE_RELATION_GROUP =
|
||||
'ContentTypeBuilder/App/ADD_ATTRIBUTE_RELATION_GROUP';
|
||||
export const ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE =
|
||||
'ContentTypeBuilder/App/ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE';
|
||||
export const ADD_ATTRIBUTE_TO_EXISTING_GROUP =
|
||||
@ -82,6 +84,8 @@ export const SET_TEMPORARY_ATTRIBUTE_GROUP =
|
||||
'ContentTypeBuilder/App/SET_TEMPORARY_ATTRIBUTE_GROUP';
|
||||
export const SET_TEMPORARY_ATTRIBUTE_RELATION =
|
||||
'ContentTypeBuilder/App/SET_TEMPORARY_ATTRIBUTE_RELATION';
|
||||
export const SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP =
|
||||
'ContentTypeBuilder/App/SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP';
|
||||
export const SUBMIT_CONTENT_TYPE = 'ContentTypeBuilder/App/SUBMIT_CONTENT_TYPE';
|
||||
export const SUBMIT_CONTENT_TYPE_SUCCEEDED =
|
||||
'ContentTypeBuilder/App/SUBMIT_CONTENT_TYPE_SUCCEEDED';
|
||||
|
||||
@ -8,6 +8,7 @@ import { fromJS, List, Map, OrderedMap } from 'immutable';
|
||||
import pluralize from 'pluralize';
|
||||
import {
|
||||
ADD_ATTRIBUTE_RELATION,
|
||||
ADD_ATTRIBUTE_RELATION_GROUP,
|
||||
ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE,
|
||||
ADD_ATTRIBUTE_TO_EXISTING_GROUP,
|
||||
ADD_ATTRIBUTE_TO_TEMP_CONTENT_TYPE,
|
||||
@ -32,6 +33,7 @@ import {
|
||||
ON_CHANGE_ATTRIBUTE,
|
||||
ON_CHANGE_ATTRIBUTE_GROUP,
|
||||
ON_CHANGE_RELATION,
|
||||
ON_CHANGE_RELATION_GROUP,
|
||||
ON_CHANGE_RELATION_TARGET,
|
||||
RESET_EXISTING_CONTENT_TYPE_MAIN_INFOS,
|
||||
RESET_EXISTING_GROUP_MAIN_INFOS,
|
||||
@ -46,6 +48,7 @@ import {
|
||||
SET_TEMPORARY_ATTRIBUTE,
|
||||
SET_TEMPORARY_ATTRIBUTE_GROUP,
|
||||
SET_TEMPORARY_ATTRIBUTE_RELATION,
|
||||
SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP,
|
||||
SUBMIT_CONTENT_TYPE_SUCCEEDED,
|
||||
SUBMIT_GROUP_SUCCEEDED,
|
||||
SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED,
|
||||
@ -90,6 +93,17 @@ export const initialState = fromJS({
|
||||
target: '',
|
||||
unique: false,
|
||||
},
|
||||
temporaryAttributeRelationGroup: {
|
||||
name: '',
|
||||
columnName: '',
|
||||
dominant: false,
|
||||
targetColumnName: '',
|
||||
key: '-',
|
||||
nature: 'oneWay',
|
||||
plugin: '',
|
||||
target: '',
|
||||
unique: false,
|
||||
},
|
||||
initialTemporaryAttributeRelation: {
|
||||
name: '',
|
||||
columnName: '',
|
||||
@ -173,6 +187,52 @@ function appReducer(state = initialState, action) {
|
||||
|
||||
return newState;
|
||||
}
|
||||
case ADD_ATTRIBUTE_RELATION_GROUP: {
|
||||
const { isGroupTemporary, groupName } = action;
|
||||
const basePath = isGroupTemporary
|
||||
? ['newGroup']
|
||||
: ['modifiedDataGroup', groupName];
|
||||
const { key, name, nature, target } = state
|
||||
.get('temporaryAttributeRelationGroup')
|
||||
.toJS();
|
||||
|
||||
let newState = state.updateIn(
|
||||
[...basePath, 'schema', 'attributes', name],
|
||||
() => {
|
||||
const newAttribute = state
|
||||
.get('temporaryAttributeRelationGroup')
|
||||
.remove('name');
|
||||
|
||||
return newAttribute;
|
||||
}
|
||||
);
|
||||
|
||||
if (target === groupName && nature !== 'oneWay') {
|
||||
newState = newState.updateIn([...basePath, 'attributes', key], () => {
|
||||
const newAttribute = state
|
||||
.get('temporaryAttributeRelationGroup')
|
||||
.set(
|
||||
'key',
|
||||
state.getIn(['temporaryAttributeRelationGroup', 'name'])
|
||||
)
|
||||
.update('dominant', () => false)
|
||||
.update('nature', value => {
|
||||
if (nature === 'oneToMany') {
|
||||
return 'manyToOne';
|
||||
} else if (nature === 'manyToOne') {
|
||||
return 'oneToMany';
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
})
|
||||
.remove('name');
|
||||
|
||||
return newAttribute;
|
||||
});
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
case ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE: {
|
||||
return state
|
||||
.updateIn(
|
||||
@ -431,6 +491,11 @@ function appReducer(state = initialState, action) {
|
||||
['temporaryAttributeRelation', ...action.keys],
|
||||
() => action.value
|
||||
);
|
||||
case ON_CHANGE_RELATION_GROUP:
|
||||
return state.updateIn(
|
||||
['temporaryAttributeRelationGroup', ...action.keys],
|
||||
() => action.value
|
||||
);
|
||||
case ON_CHANGE_RELATION_NATURE: {
|
||||
const { currentModel, nature } = action;
|
||||
|
||||
@ -744,6 +809,49 @@ function appReducer(state = initialState, action) {
|
||||
() => action.source || ''
|
||||
);
|
||||
}
|
||||
case SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP: {
|
||||
if (action.isEditing) {
|
||||
const basePath = action.isGroupTemporary
|
||||
? ['newGroup']
|
||||
: ['modifiedDataGroup', action.target];
|
||||
|
||||
return state
|
||||
.update('temporaryAttributeRelationGroup', () =>
|
||||
state
|
||||
.getIn([
|
||||
...basePath,
|
||||
'schema',
|
||||
'attributes',
|
||||
action.attributeName,
|
||||
])
|
||||
.set('name', action.attributeName)
|
||||
)
|
||||
.update('initialTemporaryAttributeRelationGroup', () =>
|
||||
state
|
||||
.getIn([
|
||||
...basePath,
|
||||
'schema',
|
||||
'attributes',
|
||||
action.attributeName,
|
||||
])
|
||||
.set('name', action.attributeName)
|
||||
);
|
||||
}
|
||||
|
||||
return state
|
||||
.updateIn(
|
||||
['temporaryAttributeRelationGroup', 'target'],
|
||||
() => action.target
|
||||
)
|
||||
.updateIn(
|
||||
['temporaryAttributeRelationGroup', 'name'],
|
||||
() => action.target
|
||||
)
|
||||
.updateIn(
|
||||
['temporaryAttributeRelationGroup', 'plugin'],
|
||||
() => action.source || ''
|
||||
);
|
||||
}
|
||||
case SUBMIT_CONTENT_TYPE_SUCCEEDED: {
|
||||
return state
|
||||
.update('isLoading', () => true)
|
||||
|
||||
@ -12,6 +12,7 @@ import ListRow from '../../components/ListRow';
|
||||
import AttributesModalPicker from '../AttributesPickerModal';
|
||||
import AttributeForm from '../AttributeForm';
|
||||
import ViewContainer from '../ViewContainer';
|
||||
import RelationFormGroup from '../RelationFormGroup';
|
||||
|
||||
import {
|
||||
BackHeader,
|
||||
@ -26,13 +27,16 @@ import {
|
||||
} from 'strapi-helper-plugin';
|
||||
|
||||
import {
|
||||
addAttributeRelationGroup,
|
||||
addAttributeToTempGroup,
|
||||
addAttributeToExistingGroup,
|
||||
clearTemporaryAttributeGroup,
|
||||
deleteGroupAttribute,
|
||||
onChangeAttributeGroup,
|
||||
onChangeRelationGroup,
|
||||
saveEditedAttributeGroup,
|
||||
setTemporaryAttributeGroup,
|
||||
setTemporaryAttributeRelationGroup,
|
||||
submitGroup,
|
||||
submitTempGroup,
|
||||
resetEditTempGroup,
|
||||
@ -145,7 +149,7 @@ export class GroupPage extends React.Component {
|
||||
/* istanbul ignore if */
|
||||
const title = this.isUpdatingTempFeature()
|
||||
? get(newGroup, 'name', null)
|
||||
: get(modifiedDataGroup, [name, 'schema', 'name'], null);
|
||||
: get(modifiedDataGroup, [name, 'name'], null);
|
||||
|
||||
return title;
|
||||
};
|
||||
@ -293,6 +297,7 @@ export class GroupPage extends React.Component {
|
||||
|
||||
handleSubmit = (shouldContinue = false) => {
|
||||
const {
|
||||
addAttributeRelationGroup,
|
||||
addAttributeToExistingGroup,
|
||||
addAttributeToTempGroup,
|
||||
history: { push },
|
||||
@ -300,10 +305,14 @@ export class GroupPage extends React.Component {
|
||||
|
||||
const attributeType = this.getAttributeType();
|
||||
|
||||
if (this.isUpdatingTempFeature()) {
|
||||
addAttributeToTempGroup(attributeType);
|
||||
if (this.getAttributeType() === 'relation') {
|
||||
addAttributeRelation(this.isUpdatingTempFeature(), this.getFeatureName());
|
||||
} else {
|
||||
addAttributeToExistingGroup(this.getFeatureName(), attributeType);
|
||||
if (this.isUpdatingTempFeature()) {
|
||||
addAttributeToTempGroup(attributeType);
|
||||
} else {
|
||||
addAttributeToExistingGroup(this.getFeatureName(), attributeType);
|
||||
}
|
||||
}
|
||||
|
||||
const nextSearch = shouldContinue ? 'modalType=chooseAttributes' : '';
|
||||
@ -382,11 +391,14 @@ export class GroupPage extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
clearTemporaryAttributeGroup,
|
||||
groups,
|
||||
history: { push },
|
||||
onChangeAttributeGroup,
|
||||
onChangeRelationGroup,
|
||||
temporaryAttributeGroup,
|
||||
temporaryAttributeRelationGroup,
|
||||
setTemporaryAttributeRelationGroup,
|
||||
} = this.props;
|
||||
|
||||
const { showWarning } = this.state;
|
||||
|
||||
const attributes = this.getFeatureAttributes();
|
||||
@ -472,7 +484,10 @@ export class GroupPage extends React.Component {
|
||||
attributeType={this.getAttributeType()}
|
||||
attributeToEditName={this.getAttributeName()}
|
||||
featureType={this.featureType}
|
||||
isOpen={this.getModalType() === 'attributeForm'}
|
||||
isOpen={
|
||||
this.getModalType() === 'attributeForm' &&
|
||||
this.getAttributeType() !== 'relation'
|
||||
}
|
||||
modifiedData={temporaryAttributeGroup}
|
||||
onCancel={clearTemporaryAttributeGroup}
|
||||
onChange={onChangeAttributeGroup}
|
||||
@ -481,6 +496,25 @@ export class GroupPage extends React.Component {
|
||||
push={push}
|
||||
/>
|
||||
|
||||
<RelationFormGroup
|
||||
actionType={this.getActionType()}
|
||||
activeTab={this.getSettingType()}
|
||||
alreadyTakenAttributes={this.getFeatureAttributesNames()}
|
||||
featureType={this.featureType}
|
||||
featureName={this.getFeatureName()}
|
||||
features={groups}
|
||||
isOpen={
|
||||
this.getModalType() === 'attributeForm' &&
|
||||
this.getAttributeType() === 'relation'
|
||||
}
|
||||
modifiedData={temporaryAttributeRelationGroup}
|
||||
onCancel={() => {}}
|
||||
onChange={onChangeRelationGroup}
|
||||
onSubmit={this.handleSubmit}
|
||||
setTempAttribute={setTemporaryAttributeRelationGroup}
|
||||
push={push}
|
||||
/>
|
||||
|
||||
<PopUpWarning
|
||||
isOpen={showWarning}
|
||||
toggleModal={this.toggleModalWarning}
|
||||
@ -504,6 +538,7 @@ GroupPage.defaultProps = {
|
||||
};
|
||||
|
||||
GroupPage.propTypes = {
|
||||
addAttributeRelationGroup: PropTypes.func.isRequired,
|
||||
addAttributeToExistingGroup: PropTypes.func.isRequired,
|
||||
addAttributeToTempGroup: PropTypes.func.isRequired,
|
||||
canOpenModal: PropTypes.bool,
|
||||
@ -526,24 +561,30 @@ GroupPage.propTypes = {
|
||||
modifiedDataGroup: PropTypes.object.isRequired,
|
||||
newGroup: PropTypes.object.isRequired,
|
||||
onChangeAttributeGroup: PropTypes.func.isRequired,
|
||||
onChangeRelationGroup: PropTypes.func.isRequired,
|
||||
resetEditTempGroup: PropTypes.func.isRequired,
|
||||
saveEditedAttributeGroup: PropTypes.func.isRequired,
|
||||
setTemporaryAttributeGroup: PropTypes.func.isRequired,
|
||||
setTemporaryAttributeRelationGroup: PropTypes.func.isRequired,
|
||||
submitGroup: PropTypes.func.isRequired,
|
||||
submitTempGroup: PropTypes.func.isRequired,
|
||||
temporaryAttributeGroup: PropTypes.object.isRequired,
|
||||
temporaryAttributeRelationGroup: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
addAttributeRelationGroup,
|
||||
addAttributeToExistingGroup,
|
||||
addAttributeToTempGroup,
|
||||
clearTemporaryAttributeGroup,
|
||||
deleteGroupAttribute,
|
||||
onChangeAttributeGroup,
|
||||
onChangeRelationGroup,
|
||||
saveEditedAttributeGroup,
|
||||
setTemporaryAttributeGroup,
|
||||
setTemporaryAttributeRelationGroup,
|
||||
submitGroup,
|
||||
submitTempGroup,
|
||||
resetEditTempGroup,
|
||||
|
||||
@ -38,6 +38,7 @@ const renderComponent = (props = {}) => {
|
||||
|
||||
const basePath = '/plugins/content-type-builder/groups';
|
||||
const props = {
|
||||
addAttributeRelationGroup: jest.fn(),
|
||||
addAttributeToExistingGroup: jest.fn(),
|
||||
addAttributeToTempGroup: jest.fn(),
|
||||
clearTemporaryAttributeGroup: jest.fn(),
|
||||
@ -125,6 +126,7 @@ const props = {
|
||||
resetEditTempGroup: jest.fn(),
|
||||
saveEditedAttributeGroup: jest.fn(),
|
||||
setTemporaryAttributeGroup: jest.fn(),
|
||||
setTemporaryAttributeRelationGroup: jest.fn(),
|
||||
submitTempGroup: jest.fn(),
|
||||
submitGroup: jest.fn(),
|
||||
temporaryAttributeGroup: {},
|
||||
|
||||
@ -309,23 +309,6 @@ class RelationForm extends React.Component {
|
||||
<hr />
|
||||
</section>
|
||||
</HeaderModal>
|
||||
|
||||
{/* <HeaderModal>
|
||||
<div style={{ fontSize: '1.8rem', fontWeight: 'bold' }}>
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.popUpForm.${actionType || 'create'}`}
|
||||
/>
|
||||
|
||||
<span style={{ fontStyle: 'italic', textTransform: 'capitalize' }}>
|
||||
{titleContent}
|
||||
</span>
|
||||
|
||||
<FormattedMessage id={`${pluginId}.popUpForm.field`} />
|
||||
</div>
|
||||
<HeaderModalNavContainer>
|
||||
{NAVLINKS.map(this.renderNavLink)}
|
||||
</HeaderModalNavContainer>
|
||||
</HeaderModal> */}
|
||||
<form onSubmit={this.handleSubmitAndContinue}>
|
||||
<BodyModal>{showForm && content}</BodyModal>
|
||||
<FooterModal>
|
||||
|
||||
@ -0,0 +1,334 @@
|
||||
/**
|
||||
*
|
||||
* RelationFormGroup
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { get, isEmpty, upperFirst } from 'lodash';
|
||||
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
import BodyModal from '../../components/BodyModal';
|
||||
import ButtonModalPrimary from '../../components/ButtonModalPrimary';
|
||||
import ButtonModalSecondary from '../../components/ButtonModalSecondary';
|
||||
import ButtonModalSuccess from '../../components/ButtonModalSuccess';
|
||||
import FooterModal from '../../components/FooterModal';
|
||||
import HeaderModal from '../../components/HeaderModal';
|
||||
import HeaderModalNavContainer from '../../components/HeaderModalNavContainer';
|
||||
import HeaderModalTitle from '../../components/HeaderModalTitle';
|
||||
import HeaderNavLink from '../../components/HeaderNavLink';
|
||||
import RelationBox from '../../components/RelationBox';
|
||||
import RelationsWrapper from '../../components/RelationsWrapper';
|
||||
import WrapperModal from '../../components/WrapperModal';
|
||||
|
||||
// import NaturePicker from './NaturePicker';
|
||||
// import RelationBox from './RelationBox';
|
||||
|
||||
import Icon from '../../assets/icons/icon_type_ct.png';
|
||||
import IconGroup from '../../assets/icons/icon_type_groups.png';
|
||||
|
||||
const NAVLINKS = [{ id: 'base', custom: 'relation' }, { id: 'advanced' }];
|
||||
|
||||
class RelationFormGroup extends React.Component {
|
||||
// eslint-disable-line react/prefer-stateless-function
|
||||
|
||||
state = { didCheckErrors: false, formErrors: {}, showForm: false };
|
||||
|
||||
getFormErrors = () => {
|
||||
const {
|
||||
actionType,
|
||||
alreadyTakenAttributes,
|
||||
attributeToEditName,
|
||||
modifiedData,
|
||||
} = this.props;
|
||||
const formValidations = {
|
||||
name: { required: true, unique: true },
|
||||
key: { required: true, unique: true },
|
||||
};
|
||||
|
||||
const alreadyTakenAttributesUpdated = alreadyTakenAttributes.filter(
|
||||
attribute => {
|
||||
if (actionType === 'edit') {
|
||||
return (
|
||||
attribute !== attributeToEditName && attribute !== modifiedData.key
|
||||
);
|
||||
}
|
||||
|
||||
return attribute !== attributeToEditName;
|
||||
}
|
||||
);
|
||||
|
||||
let formErrors = {};
|
||||
|
||||
if (modifiedData.name === modifiedData.key) {
|
||||
formErrors = { key: [{ id: `${pluginId}.error.attribute.key.taken` }] };
|
||||
}
|
||||
|
||||
formErrors = Object.keys(formValidations).reduce((acc, current) => {
|
||||
const { required, unique } = formValidations[current];
|
||||
const value = modifiedData[current];
|
||||
|
||||
if (required === true && !value) {
|
||||
acc[current] = [{ id: `${pluginId}.error.validation.required` }];
|
||||
}
|
||||
|
||||
if (unique === true && alreadyTakenAttributesUpdated.includes(value)) {
|
||||
acc[current] = [{ id: `${pluginId}.error.attribute.key.taken` }];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, formErrors);
|
||||
|
||||
this.setState(prevState => ({
|
||||
didCheckErrors: !prevState.didCheckErrors,
|
||||
formErrors,
|
||||
}));
|
||||
|
||||
return formErrors;
|
||||
};
|
||||
|
||||
getIcon = () => {
|
||||
const { featureType } = this.props;
|
||||
|
||||
return featureType === 'model' ? Icon : IconGroup;
|
||||
};
|
||||
|
||||
handleCancel = () => {
|
||||
const { push } = this.props;
|
||||
|
||||
push({ search: '' });
|
||||
};
|
||||
|
||||
handleOnClosed = () => {
|
||||
const { onCancel } = this.props;
|
||||
|
||||
onCancel();
|
||||
this.setState({ formErrors: {}, showForm: false }); // eslint-disable-line react/no-unused-state
|
||||
};
|
||||
|
||||
handleOnOpened = () => {
|
||||
const {
|
||||
actionType,
|
||||
attributeToEditName,
|
||||
isUpdatingTemporary,
|
||||
features,
|
||||
featureToEditName,
|
||||
setTempAttribute,
|
||||
} = this.props;
|
||||
const [{ name, source }] = features;
|
||||
const target = actionType === 'edit' ? featureToEditName : name;
|
||||
console.log(target);
|
||||
|
||||
setTempAttribute(
|
||||
target,
|
||||
isUpdatingTemporary,
|
||||
source,
|
||||
attributeToEditName,
|
||||
actionType === 'edit'
|
||||
);
|
||||
|
||||
this.setState({ showForm: true });
|
||||
};
|
||||
|
||||
handleToggle = () => {
|
||||
const { push } = this.props;
|
||||
|
||||
push({ search: '' });
|
||||
};
|
||||
|
||||
handleGoTo = to => {
|
||||
const { emitEvent } = this.context;
|
||||
const { actionType, attributeToEditName, push } = this.props;
|
||||
const attributeName =
|
||||
actionType === 'edit' ? `&attributeName=${attributeToEditName}` : '';
|
||||
|
||||
if (to === 'advanced') {
|
||||
emitEvent('didSelectContentTypeFieldSettings');
|
||||
}
|
||||
|
||||
push({
|
||||
search: `modalType=attributeForm&attributeType=relation&settingType=${to}&actionType=${actionType}${attributeName}`,
|
||||
});
|
||||
};
|
||||
|
||||
handleSubmitAndContinue = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isEmpty(this.getFormErrors())) {
|
||||
this.submit(true);
|
||||
}
|
||||
};
|
||||
|
||||
renderNavLink = (link, index) => {
|
||||
const { activeTab } = this.props;
|
||||
|
||||
return (
|
||||
<HeaderNavLink
|
||||
isActive={activeTab === link.id}
|
||||
key={link.id}
|
||||
{...link}
|
||||
onClick={this.handleGoTo}
|
||||
nextTab={index === NAVLINKS.length - 1 ? 0 : index + 1}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
submit = (shouldContinue = false) => {
|
||||
const { actionType, onSubmit, onSubmitEdit } = this.props;
|
||||
|
||||
if (actionType === 'edit') {
|
||||
onSubmitEdit(shouldContinue);
|
||||
} else {
|
||||
onSubmit(shouldContinue);
|
||||
}
|
||||
};
|
||||
|
||||
renderRelationForm = () => {
|
||||
const {
|
||||
featureName,
|
||||
modifiedData: { key, name, nature, plugin, target },
|
||||
features,
|
||||
onChange,
|
||||
onChangeRelationNature,
|
||||
source,
|
||||
} = this.props;
|
||||
const { formErrors, didCheckErrors } = this.state;
|
||||
|
||||
return (
|
||||
<RelationsWrapper>
|
||||
<RelationBox
|
||||
autoFocus
|
||||
didCheckErrors={didCheckErrors}
|
||||
errors={get(formErrors, 'name', [])}
|
||||
featureName={featureName}
|
||||
main
|
||||
onChange={onChange}
|
||||
source={source}
|
||||
value={name}
|
||||
/>
|
||||
<RelationBox
|
||||
autoFocus
|
||||
didCheckErrors={didCheckErrors}
|
||||
errors={get(formErrors, 'key', [])}
|
||||
features={features}
|
||||
nature={nature}
|
||||
onChange={onChange}
|
||||
plugin={plugin}
|
||||
selectedFeature={target}
|
||||
source={source}
|
||||
value={key}
|
||||
/>
|
||||
{/* <RelationBox
|
||||
autoFocus
|
||||
errors={get(formErrors, 'name', [])}
|
||||
didCheckErrors={didCheckErrors}
|
||||
main
|
||||
modelName={groupToEditName}
|
||||
onChange={onChange}
|
||||
source={source}
|
||||
value={name}
|
||||
/>
|
||||
<NaturePicker
|
||||
modelName={groupToEditName}
|
||||
nature={nature}
|
||||
name={name}
|
||||
target={target}
|
||||
onClick={onChangeRelationNature}
|
||||
/>
|
||||
<RelationBox
|
||||
errors={get(formErrors, 'key', [])}
|
||||
didCheckErrors={didCheckErrors}
|
||||
groups={groups}
|
||||
nature={nature}
|
||||
onChange={onChange}
|
||||
onClick={this.handleClick}
|
||||
selectedModel={target}
|
||||
plugin={plugin}
|
||||
value={key}
|
||||
/> */}
|
||||
</RelationsWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { actionType, activeTab, attributeToEditName, isOpen } = this.props;
|
||||
const { showForm } = this.state;
|
||||
const titleContent =
|
||||
actionType === 'create' ? 'relation' : attributeToEditName;
|
||||
const content =
|
||||
activeTab === 'base' || !activeTab
|
||||
? this.renderRelationForm()
|
||||
: this.renderAdvancedSettings();
|
||||
|
||||
return (
|
||||
<WrapperModal
|
||||
isOpen={isOpen}
|
||||
onClosed={this.handleOnClosed}
|
||||
onOpened={this.handleOnOpened}
|
||||
onToggle={this.handleToggle}
|
||||
>
|
||||
<HeaderModal>
|
||||
<section>
|
||||
<HeaderModalTitle>
|
||||
<img src={this.getIcon()} alt="ct" />
|
||||
<span>{titleContent}</span>
|
||||
</HeaderModalTitle>
|
||||
</section>
|
||||
<section>
|
||||
<HeaderModalTitle>
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.popUpForm.${actionType || 'create'}`}
|
||||
/>
|
||||
</HeaderModalTitle>
|
||||
<div className="settings-tabs">
|
||||
<HeaderModalNavContainer>
|
||||
{NAVLINKS.map(this.renderNavLink)}
|
||||
</HeaderModalNavContainer>
|
||||
</div>
|
||||
<hr />
|
||||
</section>
|
||||
</HeaderModal>
|
||||
<form onSubmit={this.handleSubmitAndContinue}>
|
||||
<BodyModal>{showForm && content}</BodyModal>
|
||||
<FooterModal>
|
||||
<section>
|
||||
<ButtonModalPrimary
|
||||
message={`${pluginId}.form.button.add`}
|
||||
type="submit"
|
||||
add
|
||||
/>
|
||||
</section>
|
||||
<section>
|
||||
<ButtonModalSecondary
|
||||
message={`${pluginId}.form.button.cancel`}
|
||||
onClick={this.handleCancel}
|
||||
/>
|
||||
<ButtonModalSuccess
|
||||
message={`${pluginId}.form.button.done`}
|
||||
type="button"
|
||||
onClick={this.handleSubmit}
|
||||
/>
|
||||
</section>
|
||||
</FooterModal>
|
||||
</form>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RelationFormGroup.contextTypes = {
|
||||
emitEvent: PropTypes.func,
|
||||
};
|
||||
|
||||
RelationFormGroup.defaultProps = {
|
||||
featureType: 'model',
|
||||
};
|
||||
|
||||
RelationFormGroup.propTypes = {
|
||||
featureType: PropTypes.string,
|
||||
};
|
||||
|
||||
export default RelationFormGroup;
|
||||
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
describe('<RelationFormGroup />', () => {
|
||||
it('should not crash', () => {
|
||||
expect(false).toBe(true);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user