Design setting page

This commit is contained in:
cyril lopez 2018-07-04 16:49:15 +02:00
parent 4966c78f56
commit bc248b9f54
11 changed files with 279 additions and 56 deletions

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="10px" height="10px" viewBox="0 0 10 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Shape</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Content-Manager---Settings-view---Single" transform="translate(-668.000000, -604.000000)" fill="#007EFF" fill-rule="nonzero">
<g id="Container" transform="translate(257.000000, 84.000000)">
<g id="Forms" transform="translate(0.000000, 77.000000)">
<g id="Settings">
<g id="Attributes" transform="translate(12.000000, 327.000000)">
<g id="Order-attributes" transform="translate(0.000000, 23.000000)">
<g id="Link" transform="translate(35.000000, 0.000000)">
<path d="M366.39604,102.155116 L366.9967,101.554455 L365.445545,100.0033 L364.844884,100.60396 L364.844884,101.310231 L365.689769,101.310231 L365.689769,102.155116 L366.39604,102.155116 Z M369.848185,96.029703 C369.848185,95.9328933 369.79978,95.8844884 369.70297,95.8844884 C369.658966,95.8844884 369.621562,95.89989 369.590759,95.9306931 L366.013201,99.5082508 C365.982398,99.5390539 365.966997,99.5764576 365.966997,99.620462 C365.966997,99.7172717 366.015402,99.7656766 366.112211,99.7656766 C366.156216,99.7656766 366.193619,99.750275 366.224422,99.7194719 L369.80198,96.1419142 C369.832783,96.1111111 369.848185,96.0737074 369.848185,96.029703 Z M369.491749,94.7623762 L372.237624,97.5082508 L366.745875,103 L364,103 L364,100.254125 L369.491749,94.7623762 Z M374,95.3960396 C374,95.6292629 373.918592,95.8272827 373.755776,95.990099 L372.660066,97.0858086 L369.914191,94.339934 L371.009901,93.2508251 C371.168317,93.0836084 371.366337,93 371.60396,93 C371.837184,93 372.037404,93.0836084 372.20462,93.2508251 L373.755776,94.7953795 C373.918592,94.9669967 374,95.1672167 374,95.3960396 Z" id="Shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -13,6 +13,7 @@ import {
import { flow, upperFirst } from 'lodash';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import cn from 'classnames';
import styles from './styles.scss';
@ -29,7 +30,8 @@ const draggableAttrSource = {
},
endDrag: (props, monitor, component) => {
const el = findDOMNode(component);
el.className = styles.draggableAttr;
const className = props.isEditing ? `${styles.draggableAttr} ${styles.editingAttr}` : styles.draggableAttr;
el.className = className;
props.updateSiblingHoverState();
return {};
@ -115,7 +117,7 @@ class DraggableAttr extends React.Component {
}
render() {
const { label, name, isDragging, connectDragSource, connectDropTarget } = this.props;
const { label, name, isDragging, isEditing, connectDragSource, connectDropTarget } = this.props;
const { isHover } = this.state;
const opacity = isDragging ? 0.2 : 1;
@ -123,13 +125,13 @@ class DraggableAttr extends React.Component {
connectDragSource(
connectDropTarget(
<div
className={styles.draggableAttr}
className={cn(styles.draggableAttr, isEditing && styles.editingAttr)}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClickEdit}
style={{ opacity }}
>
<i className="fa fa-th" aria-hidden="true"></i>
<i className="fa fa-th" aria-hidden="true" />
<span>{name}</span>
{ isHover && !isDragging && (
<div className={styles.info}>
@ -141,7 +143,11 @@ class DraggableAttr extends React.Component {
{label}
</div>
)}
<span className={styles.removeIcon} onClick={this.handleRemove} />
{isEditing && !isHover? (
<span className={styles.editIcon} onClick={this.handleClickEdit} />
) : (
<span className={styles.removeIcon} onClick={this.handleRemove} />
)}
</div>
),
)
@ -150,6 +156,7 @@ class DraggableAttr extends React.Component {
}
DraggableAttr.defaultProps = {
isEditing: false,
onRemove: () => {},
};
@ -159,6 +166,7 @@ DraggableAttr.propTypes = {
index: PropTypes.number.isRequired,
isDragging: PropTypes.bool.isRequired,
isDraggingSibling: PropTypes.bool.isRequired,
isEditing: PropTypes.bool,
keys: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,

View File

@ -12,7 +12,7 @@
border-radius: 2px;
> i {
margin-right: 10px;
font-size: 10px;
font-size: 11px;
color: #B3B5B9;
}
&:hover {
@ -20,6 +20,10 @@
}
}
.editingAttr {
background: #E6F0FB!important;
}
.info {
position: absolute;
top: 0;
@ -47,6 +51,25 @@
}
}
.editIcon {
width: 30px;
background: #E6F0FB;
height: 26px;
cursor: pointer;
text-align: center;
float: right;
&:after {
display: inline-block;
content: '';
width: 10px;
height: 10px;
margin: auto;
margin-top: -3px;
background-image: url('../../assets/images/icon-edit-blue.svg');
}
}
.dragged {
position: relative;
height: 28px;

View File

@ -80,9 +80,24 @@ function appReducer(state = initialState, action) {
return state
.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']), list => list.push(fromJS(action.data)));
case ON_REMOVE:
return state.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']), list => (
list.delete(action.index)
));
return state.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']), list => {
// If the list is empty add the default Id attribute
if (list.size -1 === 0) {
const attrToAdd = state.getIn(['schema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']))
.filter(attr => {
return attr.get('name') === '_id' || attr.get('name') === 'id';
});
attrToAdd.setIn(['0', 'sortable'], () => true);
return list
.delete(action.index)
.push(attrToAdd.get('0'));
}
return list.delete(action.index);
});
case ON_RESET:
return state
.update('modifiedSchema', () => state.get('schema'));

View File

@ -1,8 +1,8 @@
import { ON_CLICK_EDIT_LIST_ITEM } from './constants';
export function onClickEditListItem(index) {
export function onClickEditListItem(listItemToEdit) {
return {
type: ON_CLICK_EDIT_LIST_ITEM,
index,
listItemToEdit,
};
}

View File

@ -48,8 +48,17 @@ import styles from './styles.scss';
class SettingPage extends React.PureComponent {
state = { showWarning: false, isDraggingSibling: false, isOpen: false };
componentDidMount() {
this.handleClickEditAttr(0);
}
getDefaultSort = () => this.getValue(`${this.getPath()}.defaultSort`, 'string');
getDropDownItems = () => {
const attributes = get(this.props.schema, `models.${this.getPath()}.attributes`, []);
const name = get(this.props.schema, `models.${this.getPath()}.primaryKey`, 'id' );
const defaultAttr = { [name]: { name, label: 'Id', type: 'string', searchable: true, sortable: true } };
const attributes = Object.assign(get(this.props.schema, `models.${this.getPath()}.attributes`, {}), defaultAttr);
return Object.keys(attributes)
.filter(attr => {
return findIndex(this.getListDisplay(), ['name', attr]) === -1 && !attributes[attr].hasOwnProperty('collection') && !attributes[attr].hasOwnProperty('model');
@ -81,11 +90,18 @@ class SettingPage extends React.PureComponent {
}
getSelectOptions = (input) => {
const { schema: { models } } = this.props;
const currentAttributes = get(models, this.getModelName().concat(['attributes']), []);
const selectOptions = [get(models, this.getModelName().concat(['primaryKey']), 'id')]
.concat(Object.keys(currentAttributes)
.filter(attr => currentAttributes[attr].type !== 'json' && currentAttributes[attr].type !== 'array'));
const selectOptions = this.getListDisplay().reduce((acc, curr) => {
if (curr.sortable === true) {
return acc.concat([curr.name]);
}
return acc;
}, []);
if (selectOptions.length === 0) {
selectOptions.push(this.getPrimaryKey());
}
return input.name === 'defaultSort' ? selectOptions : input.selectOptions;
}
@ -107,6 +123,8 @@ class SettingPage extends React.PureComponent {
]
);
getPrimaryKey = () => get(this.props.schema, ['models', this.getModelName()].concat(['primaryKey']), 'id');
getValue = (keys, type) => {
const value = get(this.props.schema, ['models'].concat(keys.split('.')));
@ -114,11 +132,60 @@ class SettingPage extends React.PureComponent {
}
handleChange = (e) => {
const defaultSort = this.getDefaultSort();
const name = e.target.name.split('.');
name.pop();
const attrName = get(this.props.schema.models, name.concat(['name']));
const isDisablingDefaultSort = attrName === defaultSort && e.target.value === false;
if (isDisablingDefaultSort) {
const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrName);
if (enableAttrsSort.length === 0) {
strapi.notification.info('content-manager.notification.info.SettingPage.disableSort');
} else {
const newDefaultSort = enableAttrsSort.length === 0 ? this.getPrimaryKey() : enableAttrsSort[0];
const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort };
this.props.onChangeSettings({ target });
this.props.onChangeSettings(e);
}
} else {
this.props.onChangeSettings(e);
}
}
handleClickEditAttr = (index) => {
const attrToEdit = get(this.props.schema, ['models', this.getModelName()].concat(['listDisplay', index]), {});
this.props.onClickEditListItem(attrToEdit);
}
handleRemove = (index, keys) => {
const attrToRemove = get(this.getListDisplay(), index, {});
const defaultSort = this.getDefaultSort();
const isRemovingDefaultSort = defaultSort === attrToRemove.name;
if (isRemovingDefaultSort) {
const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrToRemove.name);
const newDefaultSort = enableAttrsSort.length > 1 ? enableAttrsSort[0] : this.getPrimaryKey();
const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort };
this.props.onChangeSettings({ target });
}
this.props.onRemove(index, keys);
}
handleSubmit = (e) => {
e.preventDefault();
this.setState({ showWarning: true });
}
findIndexListItemToEdit = () => {
const index = findIndex(this.getListDisplay(), ['name', get(this.props.settingPage, ['listItemToEdit', 'name'])]);
return index === -1 ? 0 : index;
}
updateSiblingHoverState = () => {
this.setState(prevState => ({ isDraggingSibling: !prevState.isDraggingSibling }));
};
@ -137,12 +204,7 @@ class SettingPage extends React.PureComponent {
moveAttr,
onChangeSettings,
onClickAddAttr,
onClickEditListItem,
onRemove,
onSubmit,
settingPage: {
indexListItemToEdit,
},
} = this.props;
const namePath = this.getPath();
@ -213,57 +275,67 @@ class SettingPage extends React.PureComponent {
<div>{index}.</div>
<DraggableAttr
index={index}
isDraggingSibling={isDraggingSibling}
isEditing={index === this.findIndexListItemToEdit()}
key={attr.name}
keys={this.getPath()}
label={attr.label}
name={attr.name}
moveAttr={moveAttr}
onClickEditListItem={onClickEditListItem}
onRemove={onRemove}
onClickEditListItem={this.handleClickEditAttr}
onRemove={this.handleRemove}
updateSiblingHoverState={this.updateSiblingHoverState}
isDraggingSibling={isDraggingSibling}
/>
</div>
))}
<ButtonDropdown isOpen={isOpen} toggle={this.toggleDropdown}>
<DropdownToggle caret>
Button Dropdown
</DropdownToggle>
<DropdownMenu>
{this.getDropDownItems().map((item, i) => {
if (i !== this.getDropDownItems().length - 1 && this.getDropDownItems().length > 0) {
return (
<React.Fragment key={item.name}>
<DropdownItem onClick={() => onClickAddAttr(item, this.getPath())}>
{item.label}
</DropdownItem>
<DropdownItem divider />
</React.Fragment>
);
}
<div className={styles.dropdownWrapper}>
<ButtonDropdown isOpen={isOpen} toggle={this.toggleDropdown}>
<DropdownToggle>
<FormattedMessage id="content-manager.containers.SettingPage.addField">
{msg => <p>{msg}</p>}
</FormattedMessage>
</DropdownToggle>
<DropdownMenu>
{this.getDropDownItems().map((item, i) => {
if (i !== this.getDropDownItems().length - 1 && this.getDropDownItems().length > 0) {
return (
<React.Fragment key={item.name}>
<DropdownItem onClick={() => onClickAddAttr(item, this.getPath())}>
{item.label}
</DropdownItem>
<DropdownItem divider />
</React.Fragment>
);
}
return (
<DropdownItem
key={item.name}
onClick={() => onClickAddAttr(item, this.getPath())}
>
{item.label}
</DropdownItem>
);
})}
</DropdownMenu>
</ButtonDropdown>
return (
<DropdownItem
key={item.name}
onClick={() => onClickAddAttr(item, this.getPath())}
>
{item.label}
</DropdownItem>
);
})}
</DropdownMenu>
</ButtonDropdown>
</div>
</div>
<div className="col-md-7">
<div className={styles.editWrapper}>
<div className="row">
{forms.editList.map(input => {
{forms.editList.map((input, i) => {
const indexListItemToEdit = this.findIndexListItemToEdit();
const inputName = `${namePath}.listDisplay.${indexListItemToEdit}.${input.name}`;
if (indexListItemToEdit === -1) {
return <div key={i} />;
}
return (
<Input
key={input.name}
onChange={onChangeSettings}
onChange={this.handleChange}
value={this.getValue(inputName, input.type)}
{...input}
name={inputName}

View File

@ -7,13 +7,15 @@ import { fromJS } from 'immutable';
import { ON_CLICK_EDIT_LIST_ITEM } from './constants';
const initialState = fromJS({
listItemToEdit: fromJS({}),
indexListItemToEdit: 0,
});
function settingPageReducer(state = initialState, action) {
switch (action.type) {
case ON_CLICK_EDIT_LIST_ITEM:
return state.update('indexListItemToEdit', () => action.index);
return state
.update('listItemToEdit', () => fromJS(action.listItemToEdit));
default:
return state;
}

View File

@ -28,8 +28,10 @@
.draggedWrapper {
display: flex;
> div:first-child {
margin-right: 10px;
height: 30px;
width: 20px;
margin-right: 10px;
text-align: right;
line-height: 30px;
}
}
@ -54,4 +56,72 @@
padding: 24px 30px;
background-color: #FAFAFB;
border-radius: 2px;
}
.dropdownWrapper {
margin-left: 30px;
> div {
height: 28px;
width: 100%;
justify-content: space-between;
background: #ffffff;
color: #333740;
border: 1px solid #E3E9F3;
border-radius: 2px;
> button {
position: relative;
cursor: pointer;
padding-left: 10px !important;
line-height: 28px;
width: 100%;
color: #333740;
text-align: left;
background-color: #ffffff;
border: none;
font-size: 13px;
&:focus, &:active, &:hover, &:visited {
background-color: transparent!important;
box-shadow: none;
color: #333740;
}
> p {
height: 100%;
margin-left: 20px;
margin-bottom: 0;
margin-top: -1px;
color: #007EFF !important;
font-size: 13px !important;
}
&:before {
position: absolute;
top: 0;
bottom: 0;
content: '\f067';
font-family: FontAwesome;
font-size: 10px;
color: #007EFF;
}
}
> div {
max-height: 180px;
min-width: calc(100% + 2px);
margin-left: -1px;
margin-top: -1px;
border-top-left-radius: 0 !important;
border-top-right-radius: 0;
overflow: scroll;
button {
height: 28px;
line-height: 28px;
div {
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}

View File

@ -7,6 +7,11 @@
.container {
padding-top: 18px;
> div:last-child {
> div {
padding-bottom: 0 !important;
}
}
}
.main_wrapper{

View File

@ -15,6 +15,7 @@
"components.LimitSelect.itemsPerPage": "Items per page",
"containers.List.errorFetchRecords": "Error",
"containers.SettingPage.addField": "Add new field",
"containers.SettingPage.attributes": "Attributes fields",
"containers.SettingPage.attributes.description": "Define the order of the attributes",
@ -98,6 +99,7 @@
"form.Input.defaultSort": "Default sort attribute",
"notification.error.relationship.fetch": "An error occurred during relationship fetch.",
"notification.info.SettingPage.disableSort": "You need to have one attribute with the sorting allowed",
"success.record.delete": "Deleted",
"success.record.save": "Saved",

View File

@ -15,6 +15,7 @@
"components.LimitSelect.itemsPerPage": "Éléments par page",
"containers.List.errorFetchRecords": "Erreur",
"containers.SettingPage.addField": "Ajouter un nouveau champs",
"containers.SettingPage.attributes": "Attributs",
"containers.SettingPage.attributes.description": "Organiser les attributs du modèle",
"containers.SettingPage.pluginHeaderDescription": "Configurez les paramètres de ce modèle",
@ -95,6 +96,7 @@
"form.Input.defaultSort": "Attribut de trie par défault",
"notification.error.relationship.fetch": "Une erreur est survenue en récupérant les relations.",
"notification.info.SettingPage.disableSort": "Vous devez avoir au moins un attribut de trie par défaut",
"success.record.delete": "Supprimé",
"success.record.save": "Sauvegardé",