Make list widget sortable

This commit is contained in:
Mathias Biilmann Christensen
2016-10-30 16:01:10 -07:00
parent 13cbf21159
commit c23b2fb531
7 changed files with 87 additions and 33 deletions

View File

@ -33,7 +33,7 @@
content: '{"site_title": "CMS Demo"}' content: '{"site_title": "CMS Demo"}'
}, },
"authors.yml": { "authors.yml": {
content: 'authors:\n - name: Mathias\n description: Co-founder @ Netlify\n' content: 'authors:\n - name: Mathias\n description: Co-founder @ Netlify\n - name: Chris\n description: Co-founder @ Netlify\n'
} }
} }
} }

View File

@ -91,7 +91,6 @@
"@kadira/storybook": "^1.36.0", "@kadira/storybook": "^1.36.0",
"autoprefixer": "^6.3.3", "autoprefixer": "^6.3.3",
"bricks.js": "^1.7.0", "bricks.js": "^1.7.0",
"textarea-caret-position": "^0.1.1",
"dateformat": "^1.0.12", "dateformat": "^1.0.12",
"fuzzy": "^0.1.1", "fuzzy": "^0.1.1",
"immutability-helper": "^2.0.0", "immutability-helper": "^2.0.0",
@ -120,6 +119,7 @@
"react-router": "^2.5.1", "react-router": "^2.5.1",
"react-router-redux": "^4.0.5", "react-router-redux": "^4.0.5",
"react-simple-dnd": "^0.1.2", "react-simple-dnd": "^0.1.2",
"react-sortable": "^1.2.0",
"react-toolbox": "^1.2.1", "react-toolbox": "^1.2.1",
"react-topbar-progress-indicator": "^1.0.0", "react-topbar-progress-indicator": "^1.0.0",
"react-waypoint": "^3.1.3", "react-waypoint": "^3.1.3",
@ -131,6 +131,7 @@
"semaphore": "^1.0.5", "semaphore": "^1.0.5",
"slate": "^0.14.14", "slate": "^0.14.14",
"slate-drop-or-paste-images": "^0.2.0", "slate-drop-or-paste-images": "^0.2.0",
"textarea-caret-position": "^0.1.1",
"uuid": "^2.0.3", "uuid": "^2.0.3",
"whatwg-fetch": "^1.0.0" "whatwg-fetch": "^1.0.0"
}, },

View File

@ -1,3 +1,7 @@
:global(.list-item-dragging) {
opacity: 0.5;
}
.addButton { .addButton {
display: block; display: block;
cursor: pointer; cursor: pointer;

View File

@ -1,17 +1,30 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { List, Map } from 'immutable'; import { List, Map, fromJS } from 'immutable';
import { sortable } from 'react-sortable';
import ObjectControl from './ObjectControl'; import ObjectControl from './ObjectControl';
import styles from './ListControl.css'; import styles from './ListControl.css';
function ListItem(props) {
return <div {...props} className={`list-item ${ props.className }`}>{props.children}</div>;
}
ListItem.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
};
ListItem.displayName = 'list-item';
const SortableListItem = sortable(ListItem);
export default class ListControl extends Component { export default class ListControl extends Component {
static propTypes = { static propTypes = {
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
value: PropTypes.node, value: PropTypes.node,
field: PropTypes.node,
}; };
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {itemStates: Map()}; this.state = { itemStates: Map() };
} }
handleChange = (e) => { handleChange = (e) => {
@ -55,36 +68,52 @@ export default class ListControl extends Component {
const fields = field.get('fields'); const fields = field.get('fields');
const first = fields.first(); const first = fields.first();
const value = item.get(first.get('name')); const value = item.get(first.get('name'));
return value || `No ${first.get('name')}`; return value || `No ${ first.get('name') }`;
} }
handleSort = (obj) => {
this.setState({ draggingIndex: obj.draggingIndex });
if ('items' in obj) {
this.props.onChange(fromJS(obj.items));
}
};
renderItem(item, index) { renderItem(item, index) {
const { field, getMedia, onAddMedia, onRemoveMedia } = this.props; const { value, field, getMedia, onAddMedia, onRemoveMedia } = this.props;
const { itemStates } = this.state; const { itemStates, draggedItem } = this.state;
const collapsed = itemStates.getIn([index, 'collapsed']); const collapsed = itemStates.getIn([index, 'collapsed']);
const classNames = [styles.item, collapsed ? styles.collapsed : styles.expanded]; const classNames = [styles.item, collapsed ? styles.collapsed : styles.expanded];
return (<div key={index} className={classNames.join(' ')}> return (<SortableListItem
<div className={styles.objectLabel}>{this.objectLabel(item)}</div> key={index}
<div className={styles.objectControl}> updateState={this.handleSort}
<ObjectControl items={value ? value.toJS() : []}
value={item} draggingIndex={this.state.draggingIndex}
field={field} sortId={index}
onChange={this.handleChangeFor(index)} outline="list"
getMedia={getMedia} >
onAddMedia={onAddMedia} <div className={classNames.join(' ')}>
onRemoveMedia={onRemoveMedia} <div className={styles.objectLabel}>{this.objectLabel(item)}</div>
/> <div className={styles.objectControl}>
<ObjectControl
value={item}
field={field}
onChange={this.handleChangeFor(index)}
getMedia={getMedia}
onAddMedia={onAddMedia}
onRemoveMedia={onRemoveMedia}
/>
</div>
<button className={styles.toggleButton} onClick={this.handleToggle(index)}>
{collapsed ? '+' : '-'}
</button>
<button className={styles.removeButton} onClick={this.handleRemove(index)}>x</button>
</div> </div>
<button className={styles.toggleButton} onClick={this.handleToggle(index)}> </SortableListItem>);
{collapsed ? '+' : '-'}
</button>
<button className={styles.removeButton} onClick={this.handleRemove(index)}>x</button>
</div>);
} }
renderListControl() { renderListControl() {
const { value } = this.props; const { value, field } = this.props;
return (<div> return (<div>
{value && value.map((item, index) => this.renderItem(item, index))} {value && value.map((item, index) => this.renderItem(item, index))}
<div><button className={styles.addButton} onClick={this.handleAdd}>new</button></div> <div><button className={styles.addButton} onClick={this.handleAdd}>new</button></div>
@ -93,7 +122,6 @@ export default class ListControl extends Component {
render() { render() {
const { value, field } = this.props; const { value, field } = this.props;
console.log('field: %o', field.toJS());
if (field.get('fields')) { if (field.get('fields')) {
return this.renderListControl(); return this.renderListControl();

View File

@ -1,11 +1,33 @@
import React, { PropTypes } from 'react'; import React, { PropTypes, Component } from 'react';
import { resolveWidget } from '../Widgets';
export default function ListPreview({ value }) { export default class ObjectPreview extends Component {
return (<ul> widgetFor = (field, value) => {
{ value && value.map(item => <li key={item}>{item}</li>) } const { getMedia } = this.props;
</ul>); const widget = resolveWidget(field.get('widget'));
return (<div key={field.get('name')}>{React.createElement(widget.preview, {
key: field.get('name'),
value: value && value.get(field.get('name')),
field,
getMedia,
})}</div>);
};
render() {
const { field, value } = this.props;
const fields = field && field.get('fields');
if (fields) {
return value ? (<div>{value.map((val, index) => <div key={index}>
{fields && fields.map(f => this.widgetFor(f, val))}
</div>)}</div>) : null;
}
return value ? value.join(', ') : null;
}
} }
ListPreview.propTypes = { ObjectPreview.propTypes = {
value: PropTypes.node, value: PropTypes.node,
field: PropTypes.node,
getMedia: PropTypes.func.isRequired,
}; };

View File

@ -18,7 +18,7 @@ export default class ObjectControl extends Component {
const widget = resolveWidget(field.get('widget') || 'string'); const widget = resolveWidget(field.get('widget') || 'string');
const fieldValue = value && value.get(field.get('name')); const fieldValue = value && value.get(field.get('name'));
return (<div className={controlStyles.widget}> return (<div className={controlStyles.widget} key={field.get('name')}>
<div className={controlStyles.control} key={field.get('name')}> <div className={controlStyles.control} key={field.get('name')}>
<label className={controlStyles.label}>{field.get('label')}</label> <label className={controlStyles.label}>{field.get('label')}</label>
{ {

View File

@ -33,7 +33,6 @@ export function resolveFormat(collectionOrEntity, entry) {
if (typeof collectionOrEntity === 'string') { if (typeof collectionOrEntity === 'string') {
return formatByType(collectionOrEntity); return formatByType(collectionOrEntity);
} }
console.log('entry: %o', entry);
const path = entry && entry.path; const path = entry && entry.path;
if (path) { if (path) {
return formatByExtension(path.split('.').pop()); return formatByExtension(path.split('.').pop());