Skip to content

Commit

Permalink
Merge pull request #447 from netlify/kitchen-sink
Browse files Browse the repository at this point in the history
Add kitchen sink example, restore object/list previews
  • Loading branch information
erquhart authored Jun 9, 2017
2 parents 2df1a83 + 4a7eabd commit 0adb3df
Show file tree
Hide file tree
Showing 21 changed files with 181 additions and 96 deletions.
86 changes: 86 additions & 0 deletions example/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,89 @@ collections: # A list of collections the CMS should be able to edit
fields:
- {label: "Name", name: "name", widget: "string"}
- {label: "Description", name: "description", widget: "markdown"}

- name: "kitchenSink" # all the things in one entry, for documentation and quick testing
label: "Kitchen Sink"
folder: "_sink"
create: true
fields:
- {label: "Title", name: "title", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean", default: true}
- {label: "Text", name: "text", widget: "text"}
- {label: "Number", name: "number", widget: "number"}
- {label: "Markdown", name: "markdown", widget: "markdown"}
- {label: "Datetime", name: "datetime", widget: "datetime"}
- {label: "Date", name: "date", widget: "date"}
- {label: "Image", name: "image", widget: "image"}
- {label: "File", name: "file", widget: "file"}
- {label: "Select", name: "select", widget: "select", options: ["a", "b", "c"]}
- label: "Object"
name: "object"
widget: "object"
fields:
- {label: "String", name: "string", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean", default: false}
- {label: "Text", name: "text", widget: "text"}
- {label: "Number", name: "number", widget: "number"}
- {label: "Markdown", name: "markdown", widget: "markdown"}
- {label: "Datetime", name: "datetime", widget: "datetime"}
- {label: "Date", name: "date", widget: "date"}
- {label: "Image", name: "image", widget: "image"}
- {label: "File", name: "file", widget: "file"}
- {label: "Select", name: "select", widget: "select", options: ["a", "b", "c"]}
- label: "List"
name: "list"
widget: "list"
fields:
- {label: "String", name: "string", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean"}
- {label: "Text", name: "text", widget: "text"}
- {label: "Number", name: "number", widget: "number"}
- {label: "Markdown", name: "markdown", widget: "markdown"}
- {label: "Datetime", name: "datetime", widget: "datetime"}
- {label: "Date", name: "date", widget: "date"}
- {label: "Image", name: "image", widget: "image"}
- {label: "File", name: "file", widget: "file"}
- {label: "Select", name: "select", widget: "select", options: ["a", "b", "c"]}
- label: "Object"
name: "object"
widget: "object"
fields:
- {label: "String", name: "string", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean"}
- {label: "Text", name: "text", widget: "text"}
- {label: "Number", name: "number", widget: "number"}
- {label: "Markdown", name: "markdown", widget: "markdown"}
- {label: "Datetime", name: "datetime", widget: "datetime"}
- {label: "Date", name: "date", widget: "date"}
- {label: "Image", name: "image", widget: "image"}
- {label: "File", name: "file", widget: "file"}
- {label: "Select", name: "select", widget: "select", options: ["a", "b", "c"]}
- label: "List"
name: "list"
widget: "list"
fields:
- {label: "String", name: "string", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean"}
- {label: "Text", name: "text", widget: "text"}
- {label: "Number", name: "number", widget: "number"}
- {label: "Markdown", name: "markdown", widget: "markdown"}
- {label: "Datetime", name: "datetime", widget: "datetime"}
- {label: "Date", name: "date", widget: "date"}
- {label: "Image", name: "image", widget: "image"}
- {label: "File", name: "file", widget: "file"}
- {label: "Select", name: "select", widget: "select", options: ["a", "b", "c"]}
- label: "Object"
name: "object"
widget: "object"
fields:
- {label: "String", name: "string", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean"}
- {label: "Text", name: "text", widget: "text"}
- {label: "Number", name: "number", widget: "number"}
- {label: "Markdown", name: "markdown", widget: "markdown"}
- {label: "Datetime", name: "datetime", widget: "datetime"}
- {label: "Date", name: "date", widget: "date"}
- {label: "Image", name: "image", widget: "image"}
- {label: "File", name: "file", widget: "file"}
- {label: "Select", name: "select", widget: "select", options: ["a", "b", "c"]}
5 changes: 5 additions & 0 deletions example/index.html

Large diffs are not rendered by default.

Binary file added example/moby-dick.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
"react-simple-dnd": "^0.1.2",
"react-sortable": "^1.2.0",
"react-split-pane": "^0.1.57",
"react-textarea-autosize-inputref": "^4.1.0",
"react-textarea-autosize": "^4.3.2",
"react-toolbox": "^1.2.1",
"react-topbar-progress-indicator": "^1.0.0",
"react-waypoint": "^3.1.3",
Expand Down
1 change: 1 addition & 0 deletions src/backends/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class Backend {
const format = resolveFormat(collectionOrEntity, entry);
if (entry && entry.raw !== undefined) {
const data = (format && attempt(format.fromFile.bind(null, entry.raw))) || {};
if (isError(data)) console.error(data);
return Object.assign(entry, { data: isError(data) ? {} : data });
}
return format.fromFile(entry);
Expand Down
49 changes: 44 additions & 5 deletions src/components/PreviewPane/PreviewPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export default class PreviewPane extends React.Component {
getWidget = (field, value, props) => {
const { fieldsMetaData, getAsset } = props;
const widget = resolveWidget(field.get('widget'));
return React.createElement(widget.preview, {

return !widget.preview ? null : React.createElement(widget.preview, {
field,
key: field.get('name'),
value: value && Map.isMap(value) ? value.get(field.get('name')) : value,
Expand All @@ -37,10 +38,23 @@ export default class PreviewPane extends React.Component {
if (authorField) this.inferedFields[authorField] = INFERABLE_FIELDS.author;
}

widgetFor = (name) => {
const { fields, entry } = this.props;
const field = fields.find(f => f.get('name') === name);
let value = entry.getIn(['data', field.get('name')]);
/**
* Returns the widget component for a named field, and makes recursive calls
* to retrieve components for nested and deeply nested fields, which occur in
* object and list type fields. Used internally to retrieve widgets, and also
* exposed for use in custom preview templates.
*/
widgetFor = (name, fields = this.props.fields, values = this.props.entry.get('data')) => {
// We retrieve the field by name so that this function can also be used in
// custom preview templates, where the field object can't be passed in.
let field = fields && fields.find(f => f.get('name') === name);
let value = values && values.get(field.get('name'));
let nestedFields = field.get('fields');

if (nestedFields) {
field = field.set('fields', this.getNestedWidgets(nestedFields, value));
}

const labelledWidgets = ['string', 'text', 'number'];
if (Object.keys(this.inferedFields).indexOf(name) !== -1) {
value = this.inferedFields[name].defaultPreview(value);
Expand All @@ -51,6 +65,31 @@ export default class PreviewPane extends React.Component {
return value ? this.getWidget(field, value, this.props) : null;
};

/**
* Retrieves widgets for nested fields (children of object/list fields)
*/
getNestedWidgets = (fields, values) => {
// Fields nested within a list field will be paired with a List of value Maps.
if (List.isList(values)) {
return values.map(value => this.widgetsForNestedFields(fields, value));
}
// Fields nested within an object field will be paired with a single Map of values.
return this.widgetsForNestedFields(fields, values);
};

/**
* Use widgetFor as a mapping function for recursive widget retrieval
*/
widgetsForNestedFields = (fields, values) => {
return fields.map(field => this.widgetFor(field.get('name'), fields, values));
};

/**
* This function exists entirely to expose nested widgets for object and list
* fields to custom preview templates.
*
* TODO: see if widgetFor can now provide this functionality for preview templates
*/
widgetsFor = (name) => {
const { fields, entry } = this.props;
const field = fields.find(f => f.get('name') === name);
Expand Down
3 changes: 3 additions & 0 deletions src/components/Widgets/BooleanControl.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.switch {
display: inline-block;
}
5 changes: 4 additions & 1 deletion src/components/Widgets/BooleanControl.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React, { PropTypes } from 'react';
import ImmutablePropTypes from "react-immutable-proptypes";
import Switch from 'react-toolbox/lib/switch';
import { isBoolean } from 'lodash';
import styles from './BooleanControl.css';

export default class BooleanControl extends React.Component {
render() {
const { value, field, forID, onChange } = this.props;
return (
<Switch
id={forID}
checked={value === undefined ? field.get('defaultValue', false) : value}
className={styles.switch}
checked={isBoolean(value) ? value : field.get('defaultValue', false)}
onChange={onChange}
/>
);
Expand Down
2 changes: 2 additions & 0 deletions src/components/Widgets/ControlHOC.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ class ControlHOC extends Component {
controlComponent: PropTypes.func.isRequired,
field: ImmutablePropTypes.map.isRequired,
value: PropTypes.oneOfType([
PropTypes.node,
PropTypes.object,
PropTypes.string,
PropTypes.bool,
]),
metadata: ImmutablePropTypes.map,
onChange: PropTypes.func.isRequired,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Widgets/DatePreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export default function DatePreview({ value }) {
}

DatePreview.propTypes = {
value: PropTypes.node,
value: PropTypes.object,
};
6 changes: 3 additions & 3 deletions src/components/Widgets/DateTimePreview.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { PropTypes } from 'react';
import previewStyle from './defaultPreviewStyle';

export default function DatePreview({ value }) {
export default function DateTimePreview({ value }) {
return <div style={previewStyle}>{value ? value.toString() : null}</div>;
}

DatePreview.propTypes = {
value: PropTypes.node,
DateTimePreview.propTypes = {
value: PropTypes.object,
};
File renamed without changes.
31 changes: 7 additions & 24 deletions src/components/Widgets/FileControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { PropTypes } from 'react';
import { truncateMiddle } from '../../lib/textHelper';
import { Loader } from '../UI';
import AssetProxy, { createAssetProxy } from '../../valueObjects/AssetProxy';
import styles from './FileControl.css';

const MAX_DISPLAY_LENGTH = 50;

Expand Down Expand Up @@ -77,52 +78,34 @@ export default class FileControl extends React.Component {
const fileName = this.renderFileName();
if (processing) {
return (
<div style={styles.imageUpload}>
<span style={styles.message}>
<div className={styles.imageUpload}>
<span className={styles.message}>
<Loader active />
</span>
</div>
);
}
return (
<div
style={styles.imageUpload}
className={styles.imageUpload}
onDragEnter={this.handleDragEnter}
onDragOver={this.handleDragOver}
onDrop={this.handleChange}
>
<span style={styles.message} onClick={this.handleClick}>
{fileName ? fileName : 'Tip: Click here to select a file to upload, or drag an image directly into this box from your desktop'}
<span className={styles.message} onClick={this.handleClick}>
{fileName ? fileName : 'Click here to upload a file from your computer, or drag and drop a file directly into this box'}
</span>
<input
type="file"
onChange={this.handleChange}
style={styles.input}
className={styles.input}
ref={this.handleFileInputRef}
/>
</div>
);
}
}

const styles = {
input: {
display: 'none',
},
message: {
padding: '20px',
display: 'block',
fontSize: '12px',
},
imageUpload: {
backgroundColor: '#fff',
textAlign: 'center',
color: '#999',
border: '1px dashed #eee',
cursor: 'pointer',
},
};

FileControl.propTypes = {
onAddAsset: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Widgets/ImageControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { PropTypes } from 'react';
import { truncateMiddle } from '../../lib/textHelper';
import { Loader } from '../UI';
import AssetProxy, { createAssetProxy } from '../../valueObjects/AssetProxy';
import styles from './ImageControl.css';
import styles from './FileControl.css';

const MAX_DISPLAY_LENGTH = 50;

Expand Down
32 changes: 4 additions & 28 deletions src/components/Widgets/ListPreview.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,12 @@
import React, { PropTypes, Component } from 'react';
import { resolveWidget } from '../Widgets';
import previewStyle from './defaultPreviewStyle';
import ObjectPreview from './ObjectPreview';

export default class ListPreview extends Component {
widgetFor = (field, value) => {
const { getAsset } = this.props;
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,
getAsset,
})}</div>);
};

render() {
const { field, value } = this.props;
const fields = field && field.get('fields');
if (fields) {
return value ? (<div style={previewStyle}>
{value.map((val, index) => <div key={index}>
{fields && fields.map(f => this.widgetFor(f, val))}
</div>)}
</div>) : null;
}

return <div style={previewStyle}>{value ? value.join(', ') : null}</div>;
}
}
const ListPreview = ObjectPreview;

ListPreview.propTypes = {
value: PropTypes.node,
field: PropTypes.node,
getAsset: PropTypes.func.isRequired,
};

export default ListPreview;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MarkupIt from 'markup-it';
import markdownSyntax from 'markup-it/syntaxes/markdown';
import htmlSyntax from 'markup-it/syntaxes/html';
import CaretPosition from 'textarea-caret-position';
import TextareaAutosize from 'react-textarea-autosize-inputref';
import TextareaAutosize from 'react-textarea-autosize';
import registry from '../../../../lib/registry';
import { createAssetProxy } from '../../../../valueObjects/AssetProxy';
import Toolbar from '../Toolbar/Toolbar';
Expand Down
7 changes: 6 additions & 1 deletion src/components/Widgets/ObjectControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export default class ObjectControl extends Component {
onAddAsset: PropTypes.func.isRequired,
onRemoveAsset: PropTypes.func.isRequired,
getAsset: PropTypes.func.isRequired,
value: PropTypes.node,
value: PropTypes.oneOfType([
PropTypes.node,
PropTypes.object,
PropTypes.bool,
]),
field: PropTypes.object,
forID: PropTypes.string,
className: PropTypes.string,
Expand All @@ -35,6 +39,7 @@ export default class ObjectControl extends Component {
onAddAsset,
onRemoveAsset,
getAsset,
forID: field.get('name'),
})
}
</div>
Expand Down
Loading

0 comments on commit 0adb3df

Please sign in to comment.