mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 23:24:03 +00:00
add and delete view field
This commit is contained in:
parent
dfe8b64ebf
commit
4d552a7980
@ -0,0 +1,126 @@
|
||||
import React, { useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row } from '@strapi/parts/Row';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
import { ButtonText } from '@strapi/parts/Text';
|
||||
import { Stack } from '@strapi/parts/Stack';
|
||||
import EditIcon from '@strapi/icons/EditIcon';
|
||||
import CloseAlertIcon from '@strapi/icons/CloseAlertIcon';
|
||||
import Drag from '@strapi/icons/Drag';
|
||||
|
||||
const ActionButton = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: ${({ theme }) => theme.spaces[7]};
|
||||
|
||||
&:last-child {
|
||||
padding: 0 ${({ theme }) => theme.spaces[3]};
|
||||
}
|
||||
`;
|
||||
|
||||
const DragButton = styled(ActionButton)`
|
||||
padding: 0 ${({ theme }) => theme.spaces[3]};
|
||||
border-right: 1px solid ${({ theme }) => theme.colors.neutral150};
|
||||
cursor: all-scroll;
|
||||
|
||||
svg {
|
||||
width: ${12 / 16}rem;
|
||||
height: ${12 / 16}rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const FieldContainer = styled(Row)`
|
||||
min-width: ${200 / 16}rem;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
width: ${10 / 16}rem;
|
||||
height: ${10 / 16}rem;
|
||||
|
||||
path {
|
||||
fill: ${({ theme }) => theme.colors.neutral600};
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.colors.primary100};
|
||||
border-color: ${({ theme }) => theme.colors.primary200};
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: ${({ theme }) => theme.colors.primary600};
|
||||
}
|
||||
}
|
||||
|
||||
${ButtonText} {
|
||||
color: ${({ theme }) => theme.colors.primary600};
|
||||
}
|
||||
|
||||
${DragButton} {
|
||||
border-right: 1px solid ${({ theme }) => theme.colors.primary200};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const FieldWrapper = styled(Box)`
|
||||
&:last-child {
|
||||
padding-right: ${({ theme }) => theme.spaces[3]};
|
||||
}
|
||||
`;
|
||||
|
||||
const DraggableCard = ({ title, onRemoveField }) => {
|
||||
const editButtonRef = useRef();
|
||||
|
||||
const rowHandleClick = () => {
|
||||
if (editButtonRef.current) {
|
||||
editButtonRef.current.click();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldWrapper>
|
||||
<FieldContainer
|
||||
borderColor="neutral150"
|
||||
background="neutral100"
|
||||
hasRadius
|
||||
justifyContent="space-between"
|
||||
onClick={rowHandleClick}
|
||||
>
|
||||
<Stack horizontal size={3}>
|
||||
<DragButton aria-label={`move ${title}`} type="button">
|
||||
<Drag />
|
||||
</DragButton>
|
||||
<ButtonText>{title}</ButtonText>
|
||||
</Stack>
|
||||
<Row>
|
||||
<ActionButton
|
||||
ref={editButtonRef}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
console.log('edit');
|
||||
}}
|
||||
aria-label={`edit ${title}`}
|
||||
type="button"
|
||||
>
|
||||
<EditIcon />
|
||||
</ActionButton>
|
||||
<ActionButton onClick={onRemoveField} aria-label={`delete ${title}`} type="button">
|
||||
<CloseAlertIcon />
|
||||
</ActionButton>
|
||||
</Row>
|
||||
</FieldContainer>
|
||||
</FieldWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
DraggableCard.defaultProps = {
|
||||
onRemoveField: () => {},
|
||||
};
|
||||
|
||||
DraggableCard.propTypes = {
|
||||
onRemoveField: PropTypes.func,
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DraggableCard;
|
||||
@ -1,52 +1,68 @@
|
||||
import React, {
|
||||
memo,
|
||||
useContext,
|
||||
// useMemo,
|
||||
useReducer,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { memo, useContext, useMemo, useReducer, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import { useMutation } from 'react-query';
|
||||
import { isEqual, upperFirst, pick } from 'lodash';
|
||||
import { isEqual, upperFirst, pick, get } from 'lodash';
|
||||
import { stringify } from 'qs';
|
||||
import { useNotification, useTracking, ConfirmDialog } from '@strapi/helper-plugin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
import { Row } from '@strapi/parts/Row';
|
||||
import { Stack } from '@strapi/parts/Stack';
|
||||
import { Divider } from '@strapi/parts/Divider';
|
||||
import { Layout, HeaderLayout, ContentLayout } from '@strapi/parts/Layout';
|
||||
import { Link } from '@strapi/parts/Link';
|
||||
import { Main } from '@strapi/parts/Main';
|
||||
import { H3 } from '@strapi/parts/Text';
|
||||
import { Select, Option } from '@strapi/parts/Select';
|
||||
import { Button } from '@strapi/parts/Button';
|
||||
import CheckIcon from '@strapi/icons/CheckIcon';
|
||||
import BackIcon from '@strapi/icons/BackIcon';
|
||||
// import LayoutDndProvider from '../../components/LayoutDndProvider';
|
||||
import { checkIfAttributeIsDisplayable, getTrad } from '../../utils';
|
||||
import ModelsContext from '../../contexts/ModelsContext';
|
||||
import { usePluginsQueryParams } from '../../hooks';
|
||||
import putCMSettingsLV from './utils/api';
|
||||
import Settings from './components/Settings';
|
||||
// import LayoutDndProvider from '../../components/LayoutDndProvider';
|
||||
import DraggableCard from './components/DraggableCard';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
|
||||
const Flex = styled(Box)`
|
||||
flex: ${({ size }) => size};
|
||||
`;
|
||||
|
||||
const ScrollableContainer = styled(Flex)`
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
`;
|
||||
|
||||
const SelectContainer = styled(Flex)`
|
||||
max-width: ${200 / 16}rem;
|
||||
`;
|
||||
|
||||
const ListSettingsView = ({ layout, slug, updateLayout }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { trackUsage } = useTracking();
|
||||
const pluginsQueryParams = usePluginsQueryParams();
|
||||
const toggleNotification = useNotification();
|
||||
const { refetchData } = useContext(ModelsContext);
|
||||
const [showWarningSubmit, setWarningSubmit] = useState(false);
|
||||
const toggleWarningSubmit = () => setWarningSubmit(prevState => !prevState);
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
|
||||
init(initialState, layout)
|
||||
);
|
||||
// const [isOpen, setIsOpen] = useState(false);
|
||||
// const [isModalFormOpen, setIsModalFormOpen] = useState(false);
|
||||
// const [isDraggingSibling, setIsDraggingSibling] = useState(false);
|
||||
const { formatMessage } = useIntl();
|
||||
const { trackUsage } = useTracking();
|
||||
// const toggleModalForm = () => setIsModalFormOpen(prevState => !prevState);
|
||||
|
||||
const {
|
||||
// labelForm,
|
||||
// labelToEdit,
|
||||
initialData,
|
||||
modifiedData,
|
||||
} = reducerState;
|
||||
// const metadatas = get(modifiedData, ['metadatas'], {});
|
||||
|
||||
// const attributes = useMemo(() => {
|
||||
// return get(modifiedData, ['attributes'], {});
|
||||
@ -54,66 +70,12 @@ const ListSettingsView = ({ layout, slug, updateLayout }) => {
|
||||
|
||||
const { attributes } = layout;
|
||||
|
||||
// const displayedFields = useMemo(() => {
|
||||
// return get(modifiedData, ['layouts', 'list'], []);
|
||||
// }, [modifiedData]);
|
||||
const displayedFields = useMemo(() => {
|
||||
return get(modifiedData, ['layouts', 'list'], []);
|
||||
}, [modifiedData]);
|
||||
|
||||
const excludedSortOptions = ['media', 'richtext', 'dynamiczone', 'relation', 'component', 'json'];
|
||||
|
||||
const sortOptions = Object.entries(attributes).reduce((acc, cur) => {
|
||||
const [name, { type }] = cur;
|
||||
|
||||
if (!excludedSortOptions.includes(type)) {
|
||||
acc.push(name);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// const listRemainingFields = useMemo(() => {
|
||||
// return Object.keys(metadatas)
|
||||
// .filter(key => {
|
||||
// return checkIfAttributeIsDisplayable(get(attributes, key, {}));
|
||||
// })
|
||||
// .filter(field => {
|
||||
// return !displayedFields.includes(field);
|
||||
// })
|
||||
// .sort();
|
||||
// }, [displayedFields, attributes, metadatas]);
|
||||
|
||||
// console.log(displayedFields, listRemainingFields);
|
||||
|
||||
// const handleClickEditLabel = labelToEdit => {
|
||||
// dispatch({
|
||||
// type: 'SET_LABEL_TO_EDIT',
|
||||
// labelToEdit,
|
||||
// });
|
||||
// toggleModalForm();
|
||||
// };
|
||||
|
||||
// const handleClosed = () => {
|
||||
// dispatch({
|
||||
// type: 'UNSET_LABEL_TO_EDIT',
|
||||
// });
|
||||
// };
|
||||
|
||||
const handleChange = ({ target: { name, value } }) => {
|
||||
dispatch({
|
||||
type: 'ON_CHANGE',
|
||||
keys: name,
|
||||
value: name === 'settings.pageSize' ? parseInt(value, 10) : value,
|
||||
});
|
||||
};
|
||||
|
||||
const [showWarningSubmit, setWarningSubmit] = useState(false);
|
||||
const toggleWarningSubmit = () => setWarningSubmit(prevState => !prevState);
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
toggleWarningSubmit();
|
||||
trackUsage('willSaveContentTypeLayout');
|
||||
};
|
||||
|
||||
const goBackUrl = () => {
|
||||
const {
|
||||
settings: { pageSize, defaultSortBy, defaultSortOrder },
|
||||
@ -133,19 +95,59 @@ const ListSettingsView = ({ layout, slug, updateLayout }) => {
|
||||
return `/content-manager/${kind}/${uid}?${goBackSearch}`;
|
||||
};
|
||||
|
||||
// const handleChangeEditLabel = ({ target: { name, value } }) => {
|
||||
// dispatch({
|
||||
// type: 'ON_CHANGE_LABEL_METAS',
|
||||
// name,
|
||||
// value,
|
||||
// });
|
||||
// };
|
||||
const handleChange = ({ target: { name, value } }) => {
|
||||
dispatch({
|
||||
type: 'ON_CHANGE',
|
||||
keys: name,
|
||||
value: name === 'settings.pageSize' ? parseInt(value, 10) : value,
|
||||
});
|
||||
console.log('here');
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const body = pick(modifiedData, ['layouts', 'settings', 'metadatas']);
|
||||
submitMutation.mutateAsync(body);
|
||||
};
|
||||
|
||||
const handleAddField = item => {
|
||||
dispatch({
|
||||
type: 'ADD_FIELD',
|
||||
item,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveField = (e, index) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (displayedFields.length === 1) {
|
||||
toggleNotification({
|
||||
type: 'info',
|
||||
message: { id: getTrad('notification.info.minimumFields') },
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'REMOVE_FIELD',
|
||||
index,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
toggleWarningSubmit();
|
||||
trackUsage('willSaveContentTypeLayout');
|
||||
};
|
||||
|
||||
const sortOptions = Object.entries(attributes).reduce((acc, cur) => {
|
||||
const [name, { type }] = cur;
|
||||
|
||||
if (!excludedSortOptions.includes(type)) {
|
||||
acc.push(name);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const submitMutation = useMutation(body => putCMSettingsLV(body, slug), {
|
||||
onSuccess: async ({ data: { data } }) => {
|
||||
updateLayout(data);
|
||||
@ -164,9 +166,42 @@ const ListSettingsView = ({ layout, slug, updateLayout }) => {
|
||||
},
|
||||
refetchActive: true,
|
||||
});
|
||||
|
||||
const { isLoading: isSubmittingForm } = submitMutation;
|
||||
|
||||
const metadatas = get(modifiedData, ['metadatas'], {});
|
||||
const listRemainingFields = useMemo(() => {
|
||||
return Object.keys(metadatas)
|
||||
.filter(key => {
|
||||
return checkIfAttributeIsDisplayable(get(attributes, key, {}));
|
||||
})
|
||||
.filter(field => {
|
||||
return !displayedFields.includes(field);
|
||||
})
|
||||
.sort();
|
||||
}, [displayedFields, attributes, metadatas]);
|
||||
|
||||
// const handleClickEditLabel = labelToEdit => {
|
||||
// dispatch({
|
||||
// type: 'SET_LABEL_TO_EDIT',
|
||||
// labelToEdit,
|
||||
// });
|
||||
// toggleModalForm();
|
||||
// };
|
||||
|
||||
// const handleClosed = () => {
|
||||
// dispatch({
|
||||
// type: 'UNSET_LABEL_TO_EDIT',
|
||||
// });
|
||||
// };
|
||||
|
||||
// const handleChangeEditLabel = ({ target: { name, value } }) => {
|
||||
// dispatch({
|
||||
// type: 'ON_CHANGE_LABEL_METAS',
|
||||
// name,
|
||||
// value,
|
||||
// });
|
||||
// };
|
||||
|
||||
// const move = (originalIndex, atIndex) => {
|
||||
// dispatch({
|
||||
// type: 'MOVE_FIELD',
|
||||
@ -263,9 +298,47 @@ const ListSettingsView = ({ layout, slug, updateLayout }) => {
|
||||
onChange={handleChange}
|
||||
sortOptions={sortOptions}
|
||||
/>
|
||||
<Box padding={6}>
|
||||
<Box paddingTop={6} paddingBottom={6}>
|
||||
<Divider />
|
||||
</Box>
|
||||
<Box paddingBottom={4}>
|
||||
<H3 as="h2">
|
||||
{formatMessage({
|
||||
id: 'content-manager.containers.SettingPage.view',
|
||||
defaultMessage: 'View',
|
||||
})}
|
||||
</H3>
|
||||
</Box>
|
||||
<Row
|
||||
paddingTop={4}
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
borderColor="neutral300"
|
||||
borderStyle="dashed"
|
||||
borderWidth="1px"
|
||||
hasRadius
|
||||
>
|
||||
<ScrollableContainer size="1" paddingBottom={4}>
|
||||
<Stack horizontal size={3}>
|
||||
{displayedFields.map((field, index) => (
|
||||
<DraggableCard
|
||||
onRemoveField={e => handleRemoveField(e, index)}
|
||||
key={field}
|
||||
title={field}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollableContainer>
|
||||
<SelectContainer size="auto" paddingBottom={4}>
|
||||
<Select onChange={e => handleAddField(e)} value="" placeholder="Add a field">
|
||||
{listRemainingFields.map(field => (
|
||||
<Option value={field} key={field}>
|
||||
{field}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</SelectContainer>
|
||||
</Row>
|
||||
</Box>
|
||||
</ContentLayout>
|
||||
<ConfirmDialog
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import produce from 'immer'; // current
|
||||
import set from 'lodash/set';
|
||||
// import get from 'lodash/get';
|
||||
import get from 'lodash/get';
|
||||
// import { arrayMoveItem } from '../../utils';
|
||||
|
||||
const initialState = {
|
||||
@ -14,13 +14,13 @@ const initialState = {
|
||||
const reducer = (state = initialState, action) =>
|
||||
// eslint-disable-next-line consistent-return
|
||||
produce(state, draftState => {
|
||||
// const layoutFieldListPath = ['modifiedData', 'layouts', 'list'];
|
||||
const layoutFieldListPath = ['modifiedData', 'layouts', 'list'];
|
||||
switch (action.type) {
|
||||
// case 'ADD_FIELD': {
|
||||
// const layoutFieldList = get(state, layoutFieldListPath, []);
|
||||
// set(draftState, layoutFieldListPath, [...layoutFieldList, action.item]);
|
||||
// break;
|
||||
// }
|
||||
case 'ADD_FIELD': {
|
||||
const layoutFieldList = get(state, layoutFieldListPath, []);
|
||||
set(draftState, layoutFieldListPath, [action.item, ...layoutFieldList]);
|
||||
break;
|
||||
}
|
||||
// case 'MOVE_FIELD': {
|
||||
// const layoutFieldList = get(state, layoutFieldListPath, []);
|
||||
// const { originalIndex, atIndex } = action;
|
||||
@ -43,15 +43,15 @@ const reducer = (state = initialState, action) =>
|
||||
// draftState.modifiedData = state.initialData;
|
||||
// break;
|
||||
// }
|
||||
// case 'REMOVE_FIELD': {
|
||||
// const layoutFieldList = get(state, layoutFieldListPath, []);
|
||||
// set(
|
||||
// draftState,
|
||||
// layoutFieldListPath,
|
||||
// layoutFieldList.filter((_, index) => action.index !== index)
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
case 'REMOVE_FIELD': {
|
||||
const layoutFieldList = get(state, layoutFieldListPath, []);
|
||||
set(
|
||||
draftState,
|
||||
layoutFieldListPath,
|
||||
layoutFieldList.filter((_, index) => action.index !== index)
|
||||
);
|
||||
break;
|
||||
}
|
||||
// case 'SET_LABEL_TO_EDIT': {
|
||||
// const { labelToEdit } = action;
|
||||
// draftState.labelToEdit = labelToEdit;
|
||||
|
||||
@ -19,21 +19,21 @@ describe('CONTENT MANAGER | CONTAINERS | ListSettingsView | reducer', () => {
|
||||
expect(reducer(state, {})).toEqual(expected);
|
||||
});
|
||||
|
||||
// describe('ADD_FIELD', () => {
|
||||
// it('should add a field to the layout correctly', () => {
|
||||
// const expected = {
|
||||
// ...state,
|
||||
// modifiedData: {
|
||||
// layouts: {
|
||||
// list: ['title'],
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// const action = { type: 'ADD_FIELD', item: 'title' };
|
||||
describe('ADD_FIELD', () => {
|
||||
it('should add a field to the layout correctly', () => {
|
||||
const expected = {
|
||||
modifiedData: {
|
||||
layouts: {
|
||||
list: ['title'],
|
||||
},
|
||||
},
|
||||
...state,
|
||||
};
|
||||
const action = { type: 'ADD_FIELD', item: 'title' };
|
||||
|
||||
// expect(reducer(state, action)).toEqual(expected);
|
||||
// });
|
||||
// });
|
||||
expect(reducer(state, action)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
// describe('MOVE_FIELD', () => {
|
||||
// it('should replace the title by the description and vice-versa', () => {
|
||||
@ -103,32 +103,32 @@ describe('CONTENT MANAGER | CONTAINERS | ListSettingsView | reducer', () => {
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('REMOVE_FIELD', () => {
|
||||
// it('should remove the field', () => {
|
||||
// state.modifiedData = {
|
||||
// layouts: {
|
||||
// list: ['id', 'description', 'title'],
|
||||
// },
|
||||
// settings: {
|
||||
// defaultSortBy: 'id',
|
||||
// },
|
||||
// };
|
||||
// const expected = {
|
||||
// ...state,
|
||||
// modifiedData: {
|
||||
// layouts: {
|
||||
// list: ['id', 'title'],
|
||||
// },
|
||||
// settings: {
|
||||
// defaultSortBy: 'id',
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// const action = { type: 'REMOVE_FIELD', index: 1 };
|
||||
describe('REMOVE_FIELD', () => {
|
||||
it('should remove the field', () => {
|
||||
state.modifiedData = {
|
||||
layouts: {
|
||||
list: ['id', 'description', 'title'],
|
||||
},
|
||||
settings: {
|
||||
defaultSortBy: 'id',
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
...state,
|
||||
modifiedData: {
|
||||
layouts: {
|
||||
list: ['id', 'title'],
|
||||
},
|
||||
settings: {
|
||||
defaultSortBy: 'id',
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = { type: 'REMOVE_FIELD', index: 1 };
|
||||
|
||||
// expect(reducer(state, action)).toEqual(expected);
|
||||
// });
|
||||
// });
|
||||
expect(reducer(state, action)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
// describe('SET_LABEL_TO_EDIT', () => {
|
||||
// it('should set the label form data of the field to edit', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user