Handle add field item

This commit is contained in:
soupette 2019-07-23 17:37:07 +02:00
parent eed622a624
commit e8364a06b0
14 changed files with 313 additions and 101 deletions

View File

@ -1,13 +1,4 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import styled, { css } from 'styled-components';
import {
ButtonDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
const Wrapper = styled.div`
margin-left: 29px;
@ -17,10 +8,10 @@ const Wrapper = styled.div`
justify-content: space-between;
background: #ffffff;
color: #333740;
border: 1px solid #e3e9f3;
border-radius: 2px;
border: solid 1px #006ced;
> button {
position: relative;
cursor: pointer;
padding-left: 10px !important;
line-height: 30px;
@ -40,23 +31,24 @@ const Wrapper = styled.div`
color: #333740;
}
> p {
position: relative;
height: 100%;
margin-left: 20px;
text-align: center;
margin-bottom: 0;
margin-top: -1px;
color: #007eff !important;
font-size: 13px !important;
}
> span {
&:before {
position: absolute;
top: 0px;
bottom: 0;
margin-right: 10px;
content: '\f067';
font-family: FontAwesome;
font-size: 10px;
color: #007eff;
}
}
}
}
> div {
max-height: 180px;
min-width: calc(100% + 2px);
@ -114,49 +106,4 @@ const Wrapper = styled.div`
}}
`;
function Add({ data, onClick }) {
const [state, setState] = React.useState(false);
return (
<Wrapper isOpen={state} notAllowed={data.length === 0}>
<ButtonDropdown
isOpen={state}
toggle={() => {
if (data.length > 0) {
setState(prevState => !prevState);
}
}}
>
<DropdownToggle>
<FormattedMessage id="content-manager.containers.SettingPage.addField">
{msg => <p>{msg}</p>}
</FormattedMessage>
</DropdownToggle>
<DropdownMenu>
{data.map(item => (
<DropdownItem
key={item}
onClick={() => {
onClick(item);
}}
>
{item}
</DropdownItem>
))}
</DropdownMenu>
</ButtonDropdown>
</Wrapper>
);
}
Add.defaultProps = {
data: [],
onClick: () => {},
};
Add.propTypes = {
data: PropTypes.array,
onClick: PropTypes.func,
};
export default Add;
export { Wrapper };

View File

@ -0,0 +1,68 @@
import React, { memo, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import {
ButtonDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
import { Wrapper } from './components';
function Add({ data, onClick, pStyle, style }) {
const [state, setState] = useState(false);
return (
<Wrapper isOpen={state} notAllowed={data.length === 0} style={style}>
<ButtonDropdown
isOpen={state}
toggle={() => {
if (data.length > 0) {
setState(prevState => !prevState);
}
}}
>
<DropdownToggle>
<FormattedMessage id="content-manager.containers.SettingPage.addField">
{msg => (
<p style={pStyle}>
<span />
{msg}
</p>
)}
</FormattedMessage>
</DropdownToggle>
<DropdownMenu>
{data.map(item => (
<DropdownItem
key={item}
onClick={() => {
onClick(item);
}}
>
{item}
</DropdownItem>
))}
</DropdownMenu>
</ButtonDropdown>
</Wrapper>
);
}
Add.defaultProps = {
data: [],
onClick: () => {},
pStyle: {},
style: {},
};
Add.propTypes = {
data: PropTypes.array,
onClick: PropTypes.func,
pStyle: PropTypes.object,
style: PropTypes.object,
};
export default memo(Add);

View File

@ -40,7 +40,7 @@ const NameWrapper = styled.div`
position: relative;
height: 30px;
width: 100%;
margin-bottom: 10px;
display: flex;
padding-left: 10px;
justify-content: space-between;

View File

@ -13,6 +13,7 @@ const FieldItem = forwardRef(
isDragging,
isEditing,
name,
onClickRemove,
showLeftCarret,
showRightCarret,
size,
@ -58,6 +59,7 @@ const FieldItem = forwardRef(
</div>
{!isHidden && (
<RemoveIcon
onClick={onClickRemove}
withLongerHeight={withLongerHeight}
isDragging={isEditing}
/>
@ -76,6 +78,7 @@ const FieldItem = forwardRef(
FieldItem.defaultProps = {
isDragging: false,
isEditing: false,
onClickRemove: () => {},
showLeftCarret: false,
showRightCarret: false,
type: 'string',
@ -85,6 +88,7 @@ FieldItem.propTypes = {
isDragging: PropTypes.bool,
isEditing: PropTypes.bool,
name: PropTypes.string.isRequired,
onClickRemove: PropTypes.func,
showLeftCarret: PropTypes.bool,
showRightCarret: PropTypes.bool,
size: PropTypes.number.isRequired,

View File

@ -7,7 +7,16 @@ import FieldItem from '../FieldItem';
import ItemTypes from '../../utils/itemsTypes';
const Item = ({ itemIndex, moveItem, moveRow, name, rowIndex, size, type }) => {
const Item = ({
itemIndex,
moveItem,
moveRow,
name,
removeField,
rowIndex,
size,
type,
}) => {
// console.log({ rowIndex });
const ref = useRef(null);
const [{ clientOffset, isOver }, drop] = useDrop({
@ -176,6 +185,7 @@ const Item = ({ itemIndex, moveItem, moveRow, name, rowIndex, size, type }) => {
<FieldItem
isDragging={isDragging}
name={name}
onClickRemove={() => removeField(rowIndex, itemIndex)}
showLeftCarret={showLeftCarret}
showRightCarret={showRightCarret}
size={size}
@ -194,6 +204,7 @@ Item.propTypes = {
moveItem: PropTypes.func.isRequired,
moveRow: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
removeField: PropTypes.func.isRequired,
rowIndex: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
type: PropTypes.string,

View File

@ -1 +1,8 @@
// import styled from 'styled-components';
import styled from 'styled-components';
const Wrapper = styled.div`
display: flex;
margin-bottom: 24px;
`;
export { Wrapper };

View File

@ -1,11 +1,24 @@
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { useLayoutDnd } from '../../contexts/LayoutDnd';
import Add from '../AddDropdown';
import SortWrapper from '../SortWrapper';
import { Wrapper } from './components';
import Item from './Item';
const FieldsReorder = ({ attributes, layout, moveItem, moveRow }) => {
const FieldsReorder = () => {
const {
attributes,
buttonData,
layout,
moveItem,
moveRow,
onAddData,
removeField,
} = useLayoutDnd();
const getType = attributeName => {
const attribute = get(attributes, [attributeName], {});
@ -20,9 +33,8 @@ const FieldsReorder = ({ attributes, layout, moveItem, moveRow }) => {
<div className="col-8">
<SortWrapper>
{layout.map((row, rowIndex) => {
//
return (
<div key={row.rowId} style={{ display: 'flex' }}>
<Wrapper key={row.rowId} style={{}}>
{row.rowContent.map((rowContent, index) => {
const { name, size } = rowContent;
@ -33,31 +45,27 @@ const FieldsReorder = ({ attributes, layout, moveItem, moveRow }) => {
moveRow={moveRow}
moveItem={moveItem}
name={name}
removeField={removeField}
rowIndex={rowIndex}
size={size}
type={getType(name)}
//
/>
);
})}
</div>
</Wrapper>
);
})}
<Wrapper>
<Add
data={buttonData}
onClick={onAddData}
style={{ width: '100%', margin: '0 10px' }}
pStyle={{ marginTop: '-2px' }}
/>
</Wrapper>
</SortWrapper>
</div>
);
};
FieldsReorder.defaultProps = {
attributes: {},
layout: [],
};
FieldsReorder.propTypes = {
attributes: PropTypes.object,
layout: PropTypes.array,
moveItem: PropTypes.func.isRequired,
moveRow: PropTypes.func.isRequired,
};
export default memo(FieldsReorder);

View File

@ -3,7 +3,7 @@ import styled from 'styled-components';
const SortWrapper = styled.div`
margin-top: 7px;
margin-bottom: 10px;
padding-top: 10px;
padding-top: 11px;
border: 1px dashed #e3e9f3;
border-radius: 2px;
> div {

View File

@ -10,7 +10,7 @@ import pluginId from '../../pluginId';
import FormWrapper from '../../components/SettingFormWrapper';
import { Wrapper } from './components';
import Add from './Add';
import Add from '../../components/AddDropdown';
import ListField from './ListField';
import ItemTypes from '../../utils/itemsTypes';

View File

@ -6,10 +6,12 @@ import {
GET_DATA_SUCCEEDED,
MOVE_FIELD_LIST,
MOVE_ROW,
ON_ADD_DATA,
ON_CHANGE,
ON_REMOVE_LIST_FIELD,
ON_RESET,
ON_SUBMIT,
REMOVE_FIELD,
REORDER_DIFF_ROW,
REORDER_ROW,
RESET_PROPS,
@ -66,6 +68,13 @@ export function moveRow(dragRowIndex, hoverRowIndex) {
};
}
export function onAddData(name) {
return {
type: ON_ADD_DATA,
name,
};
}
export function onChange({ target: { name, value } }) {
return {
type: ON_CHANGE,
@ -94,6 +103,14 @@ export function onSubmit(uid, emitEvent) {
};
}
export function removeField(rowIndex, fieldIndex) {
return {
type: REMOVE_FIELD,
rowIndex,
fieldIndex,
};
}
export function reorderDiffRow(
dragIndex,
hoverIndex,

View File

@ -7,11 +7,13 @@ export const GET_DATA_SUCCEEDED =
export const MOVE_FIELD_LIST =
'ContentManager/SettingViewModel/MOVE_FIELD_LIST';
export const MOVE_ROW = 'ContentManager/SettingViewModel/MOVE_ROW';
export const ON_ADD_DATA = 'ContentManager/SettingViewModel/ON_ADD_DATA';
export const ON_CHANGE = 'ContentManager/SettingViewModel/ON_CHANGE';
export const ON_REMOVE_LIST_FIELD =
'ContentManager/SettingViewModel/ON_REMOVE_LIST_FIELD';
export const ON_RESET = 'ContentManager/SettingViewModel/ON_RESET';
export const ON_SUBMIT = 'ContentManager/SettingViewModel/ON_SUBMIT';
export const REMOVE_FIELD = 'ContentManager/SettingViewModel/REMOVE_FIELD';
export const REORDER_ROW = 'ContentManager/SettingViewModel/REORDER_ROW';
export const REORDER_DIFF_ROW =
'ContentManager/SettingViewModel/REORDER_DIFF_ROW';

View File

@ -1,4 +1,4 @@
import React, { memo, useEffect, useState } from 'react';
import React, { memo, useEffect, useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
@ -12,7 +12,9 @@ import {
PopUpWarning,
LoadingIndicatorPage,
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import { LayoutDndProvider } from '../../contexts/LayoutDnd';
import Block from '../../components/Block';
import Container from '../../components/Container';
@ -30,10 +32,12 @@ import {
getData,
moveListField,
moveRow,
onAddData,
onChange,
onReset,
onSubmit,
onRemoveListField,
removeField,
reorderDiffRow,
reorderRow,
resetProps,
@ -64,10 +68,12 @@ function SettingViewModel({
modifiedData,
moveListField,
moveRow,
onAddData,
onChange,
onRemoveListField,
onReset,
onSubmit,
removeField,
reorderDiffRow,
reorderRow,
resetProps,
@ -103,6 +109,14 @@ function SettingViewModel({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [didDrop]);
const getAttributes = useCallback(() => {
return get(modifiedData, ['schema', 'attributes'], {});
}, [modifiedData]);
const getEditLayout = useCallback(() => {
return get(modifiedData, ['layouts', 'edit'], []);
}, [modifiedData]);
if (isLoading) {
return <LoadingIndicatorPage />;
}
@ -135,8 +149,22 @@ function SettingViewModel({
},
];
};
const getListDisplayedFields = () =>
get(modifiedData, ['layouts', 'list'], []);
const getEditRemainingFields = () => {
const attributes = getAttributes();
const displayedFields = getEditLayout().reduce(
(acc, curr) => [...acc, ...curr.rowContent],
[]
);
return Object.keys(attributes)
.filter(attr => get(attributes, [attr, 'type'], '') !== 'relation')
.filter(attr => {
return displayedFields.findIndex(el => el.name === attr) === -1;
});
};
const getListRemainingFields = () => {
const metadata = get(modifiedData, ['metadata'], {});
@ -179,7 +207,15 @@ function SettingViewModel({
};
return (
<>
<LayoutDndProvider
attributes={getAttributes()}
buttonData={getEditRemainingFields()}
layout={getEditLayout()}
moveItem={moveItem}
moveRow={moveRow}
onAddData={onAddData}
removeField={removeField}
>
<BackHeader onClick={() => goBack()} />
<Container className="container-fluid">
<form onSubmit={handleSubmit}>
@ -262,14 +298,7 @@ function SettingViewModel({
/>
)}
{settingType === 'edit-settings' && (
<FieldsReorder
attributes={get(modifiedData, ['schema', 'attributes'], {})}
layout={get(modifiedData, ['layouts', 'edit'], [])}
moveItem={moveItem}
moveRow={moveRow}
/>
)}
{settingType === 'edit-settings' && <FieldsReorder />}
</div>
</Block>
</div>
@ -302,7 +331,7 @@ function SettingViewModel({
popUpWarningType="danger"
onConfirm={() => onSubmit(name, emitEvent)}
/>
</>
</LayoutDndProvider>
);
}
@ -327,10 +356,12 @@ SettingViewModel.propTypes = {
modifiedData: PropTypes.object.isRequired,
moveListField: PropTypes.func.isRequired,
moveRow: PropTypes.func.isRequired,
onAddData: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onRemoveListField: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
removeField: PropTypes.func.isRequired,
reorderDiffRow: PropTypes.func.isRequired,
reorderRow: PropTypes.func.isRequired,
resetProps: PropTypes.func.isRequired,
@ -348,10 +379,12 @@ export function mapDispatchToProps(dispatch) {
getData,
moveListField,
moveRow,
onAddData,
onChange,
onRemoveListField,
onReset,
onSubmit,
removeField,
reorderDiffRow,
reorderRow,
resetProps,

View File

@ -12,9 +12,11 @@ import {
GET_DATA_SUCCEEDED,
MOVE_FIELD_LIST,
MOVE_ROW,
ON_ADD_DATA,
ON_CHANGE,
ON_REMOVE_LIST_FIELD,
ON_RESET,
REMOVE_FIELD,
REORDER_DIFF_ROW,
REORDER_ROW,
RESET_PROPS,
@ -31,6 +33,21 @@ export const initialState = fromJS({
shouldToggleModalSubmit: true,
});
const getSize = type => {
switch (type) {
case 'boolean':
case 'date':
case 'datetime':
return 4;
case 'json':
case 'group':
case 'WYSIWYG':
return 12;
default:
return 6;
}
};
function settingViewModelReducer(state = initialState, action) {
const layoutPath = ['modifiedData', 'layouts', 'edit'];
const { dragIndex, hoverIndex, dragRowIndex, hoverRowIndex } = action;
@ -66,6 +83,32 @@ function settingViewModelReducer(state = initialState, action) {
.delete(dragRowIndex)
.insert(hoverRowIndex, state.getIn([...layoutPath, dragRowIndex]));
});
case ON_ADD_DATA: {
const size = getSize(
state.getIn([
'modifiedData',
'schema',
'attributes',
action.name,
'type',
])
);
const listSize = state.getIn(layoutPath).size;
const newList = state
.getIn(layoutPath)
.updateIn([listSize - 1, 'rowContent'], list => {
return list.push({
name: action.name,
size,
});
});
const formattedList = formatLayout(newList.toJS());
// NOTE we could use the diddrop part here but it causes an unecessary rerender...
// NOTE2: it would be great later to remove the didDrop key that is used to reformat the layout with the _TEMP_ divs
return state.updateIn(layoutPath, () => fromJS(formattedList));
}
case ON_CHANGE:
return state.updateIn(action.keys, () => action.value);
@ -98,6 +141,25 @@ function settingViewModelReducer(state = initialState, action) {
return state
.update('modifiedData', () => state.get('initialData'))
.update('listFieldToEditIndex', () => 0);
case REMOVE_FIELD: {
const row = state.getIn([...layoutPath, action.rowIndex, 'rowContent']);
// Delete the entire row if length is one or if lenght is equal to 2 and the second element is the hidden div used to make the dnd exp smoother
if (
row.size === 1 ||
(row.size == 2 && row.getIn([1, 'name']) === '_TEMP_')
) {
return state
.updateIn(layoutPath, list => list.delete(action.rowIndex))
.update('didDrop', v => !v);
}
return state
.updateIn([...layoutPath, action.rowIndex, 'rowContent'], list =>
list.delete(action.fieldIndex)
)
.update('didDrop', v => !v);
}
case REORDER_DIFF_ROW:
return state
.updateIn([...layoutPath, dragRowIndex, 'rowContent'], list => {

View File

@ -0,0 +1,53 @@
import React, { createContext, useContext } from 'react';
import PropTypes from 'prop-types';
const LayoutDndContext = createContext();
export function LayoutDndProvider({
attributes,
buttonData,
children,
layout,
moveItem,
moveRow,
onAddData,
removeField,
}) {
return (
<LayoutDndContext.Provider
value={{
attributes,
buttonData,
layout,
moveItem,
moveRow,
onAddData,
removeField,
}}
>
{children}
</LayoutDndContext.Provider>
);
}
export function useLayoutDnd() {
return useContext(LayoutDndContext);
}
LayoutDndProvider.defaultProps = {
attributes: {},
buttonData: [],
layout: [],
onAddData: () => {},
};
LayoutDndProvider.propTypes = {
attributes: PropTypes.object,
buttonData: PropTypes.array,
children: PropTypes.node.isRequired,
layout: PropTypes.array,
moveItem: PropTypes.func.isRequired,
moveRow: PropTypes.func.isRequired,
onAddData: PropTypes.func,
removeField: PropTypes.func.isRequired,
};