From a8fe2144399cdc4dc5afa45e6de9a7880e7b4187 Mon Sep 17 00:00:00 2001 From: Zach Silveira Date: Tue, 17 Apr 2018 13:32:35 -0400 Subject: [PATCH] 0.2.4, add in Repeat component with example --- README.md | 6 +- controls/package.json | 2 +- controls/src/base/inspector.js | 13 ++- controls/src/index.js | 4 + controls/src/repeat/index.js | 141 ++++++++++++++++++++++++++ controls/src/repeat/input.js | 32 ++++++ package-lock.json | 2 +- package.json | 2 +- plugin/package-lock.json | 6 +- plugin/package.json | 2 +- plugin/src/example/block.js | 5 +- plugin/src/example/components/note.js | 5 + plugin/src/example/components/tabs.js | 9 ++ plugin/src/example/edit.js | 14 ++- plugin/src/example/inspector.js | 13 +++ 15 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 controls/src/repeat/index.js create mode 100644 controls/src/repeat/input.js create mode 100644 plugin/src/example/components/note.js create mode 100644 plugin/src/example/components/tabs.js create mode 100644 plugin/src/example/inspector.js diff --git a/README.md b/README.md index 1890ff5..78be8e0 100644 --- a/README.md +++ b/README.md @@ -105,13 +105,13 @@ module.exports = { The configuration is the exact same as webpack with one extra piece: pass `babelOptions` with plugins and presets like a babelrc has to customize the babel loader. -# Coming Soon +# Future plans * Inspector tools for repeat actions (adding multiple items recursively, ex: tabs with products in each) * Automatic i18n * Complicated examples (tabs component, loading in data from wordpress) - -* Handling the `save` method +* Test coverage +* Batch updates when updating nested tabs that cause lots of rerenders in Gutenberg Currently, we don't use wordpress to render blocks, so we don't use this. But I will be working to make this part better if needed. diff --git a/controls/package.json b/controls/package.json index df083d8..2ea3d95 100644 --- a/controls/package.json +++ b/controls/package.json @@ -1,6 +1,6 @@ { "name": "gutenblock-controls", - "version": "0.1.0", + "version": "0.1.3", "description": "Useful inspector controls for gutenberg", "main": "dist/index.js", "scripts": { diff --git a/controls/src/base/inspector.js b/controls/src/base/inspector.js index 3ab3df8..1b8bef8 100644 --- a/controls/src/base/inspector.js +++ b/controls/src/base/inspector.js @@ -3,10 +3,19 @@ import { Consumer } from './context'; // Todo, use react context vs map when we add other inspector controls :) -export default ({ children, isSelected }) => ( +export default ({ children }) => ( {data => - data.isSelected ? {children} : null + data.isSelected ? ( + + {React.Children.map(children, child => + React.cloneElement(child, { + setAttributes: data.setAttributes, + attributes: data.attributes, + }) + )} + + ) : null } ); diff --git a/controls/src/index.js b/controls/src/index.js index c897950..31c7c35 100644 --- a/controls/src/index.js +++ b/controls/src/index.js @@ -7,3 +7,7 @@ export { default as Attributes } from './base/attributes'; //Form stuff export { default as Input } from './form/input'; export { default as RichText } from './form/rich-text'; + +//Repeat +export { default as Repeat } from './repeat'; +export { default as RepeatInput } from './repeat/input'; diff --git a/controls/src/repeat/index.js b/controls/src/repeat/index.js new file mode 100644 index 0000000..cdbb145 --- /dev/null +++ b/controls/src/repeat/index.js @@ -0,0 +1,141 @@ +import React from 'react'; + +const { Button } = wp.components; + +export default class Repeat extends React.Component { + constructor({ attribute, attributes }) { + super(); + this.update = this.update.bind(this); + this.delete = this.delete.bind(this); + + let items = + attributes && attributes[attribute] ? attributes[attribute] : []; + this.state = { items: items.length }; + } + + static getDerivedStateFromProps({ attributes, attribute }, state) { + let items = + attributes && attributes[attribute] ? attributes[attribute] : []; + if (items.length !== state.items) return { items: items.length }; + + return null; + } + + update(name, value, tabId, parentChange, customUpdate) { + let { attribute, attributes, setAttributes } = this.props; + + //if nested repeats, pass state up to top most one + if (parentChange) { + let currentAttributes = attributes[attribute] || []; + + let foundAttribute = currentAttributes.find( + (attr, index) => index === tabId + ); + let newAttributes; + if (!foundAttribute) + newAttributes = [...currentAttributes, { [name]: value }]; + else + newAttributes = currentAttributes.map( + (attr, index) => (index === tabId ? { ...attr, [name]: value } : attr) + ); + + return parentChange(attribute, newAttributes); + } + + let topLevel = [...attributes[attribute]]; + + topLevel[tabId] = { ...topLevel[tabId], [name]: value }; + + setAttributes({ [attribute]: topLevel }); + } + + delete(childAttributes, childAttribute, tabId, onDelete, customDelete) { + let { attribute, attributes, setAttributes } = this.props; + + let newAttributes; + if (!childAttributes) + newAttributes = attributes[attribute].filter( + (attr, index) => index !== tabId + ); + + if (childAttributes) { + let newItem = { + ...attributes[attribute][tabId], + [childAttribute]: childAttributes, + }; + newAttributes = attributes[attribute].map( + (item, index) => (index === tabId ? newItem : item) + ); + } + + if (onDelete) return onDelete(newAttributes, attribute); + + setAttributes({ [attribute]: newAttributes }); + } + + renderChildren(index) { + let { + attribute, + children, + setAttributes, + attributes, + onChange, + onDelete, + } = this.props; + + return React.Children.map(children, child => + React.cloneElement(child, { + key: index, + index, + setAttributes, + attributes: + attributes && attributes[attribute] && attributes[attribute][index] + ? attributes[attribute][index] + : undefined, + onChange: (name, value) => + this.update(name, value, index, onChange, child.onUpdate), + onDelete: (childAttributes, childAttribute) => + this.delete( + childAttributes, + childAttribute, + index, + onDelete, + child.onDelete + ), + style: { + marginLeft: '10px', + marginTop: '15px', + }, + }) + ); + } + + render() { + let { items } = this.state; + let { style, title, indent, addNew, max } = this.props; + + let repeats = []; + for (let item = 0; item < items; item++) { + repeats.push(this.renderChildren(item)); + } + + return ( +
+

{title}

+
+ {repeats} +
+ {max > items || !max ? ( + + ) : null} +
+
+
+ ); + } +} diff --git a/controls/src/repeat/input.js b/controls/src/repeat/input.js new file mode 100644 index 0000000..e73503d --- /dev/null +++ b/controls/src/repeat/input.js @@ -0,0 +1,32 @@ +import React from 'react'; + +const RepeatInput = ({ + name, + label, + onChange, + onDelete, + type, + attributes, + setAttributes, +}) => ( +
+ +
+ onChange(name, e.target.value)} + /> +
onDelete()}> + X +
+
+
+); + +export default RepeatInput; diff --git a/package-lock.json b/package-lock.json index 7753807..f3bfc2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenblock", - "version": "0.1.3", + "version": "0.2.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f06bd68..a18f310 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenblock", - "version": "0.2.3", + "version": "0.2.4", "description": "", "main": "index.js", "scripts": { diff --git a/plugin/package-lock.json b/plugin/package-lock.json index 2d0a3a2..62a8736 100644 --- a/plugin/package-lock.json +++ b/plugin/package-lock.json @@ -56,9 +56,9 @@ } }, "gutenblock-controls": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/gutenblock-controls/-/gutenblock-controls-0.1.0.tgz", - "integrity": "sha512-KsZ7zXz4znRwHzLwJex5AHhPxu8EPcUIw/wfiBcu53JlryoPlZVTCJZlXq9uGkUEpjvqxJ39VdeMSQt0Mxk3Pw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/gutenblock-controls/-/gutenblock-controls-0.1.3.tgz", + "integrity": "sha512-y0StFiXCahst9ZxrPL1QnCSSHzPBEavfO/5Ro80aNyuu7YGJBaL5EAOP1+NUnP5Zk/Y1SkxyIvmBiqQO/OIzSA==", "requires": { "react-broadcast": "0.7.0" } diff --git a/plugin/package.json b/plugin/package.json index ebb9cb7..52576dc 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -13,7 +13,7 @@ "author": "Crossfield", "license": "ISC", "dependencies": { - "gutenblock-controls": "^0.1.0", + "gutenblock-controls": "^0.1.3", "react": "^16.3.1", "react-hot-loader": "^4.0.1" } diff --git a/plugin/src/example/block.js b/plugin/src/example/block.js index ccb7c9a..24597b5 100644 --- a/plugin/src/example/block.js +++ b/plugin/src/example/block.js @@ -6,8 +6,9 @@ const Block = { title: { type: 'string', }, - description: { - type: 'string', + tabs: { + type: 'array', + default: [], }, }, }; diff --git a/plugin/src/example/components/note.js b/plugin/src/example/components/note.js new file mode 100644 index 0000000..6da2ac8 --- /dev/null +++ b/plugin/src/example/components/note.js @@ -0,0 +1,5 @@ +import React from 'react'; + +export default ({ note }) => ( +
  • {note.heading}
  • +); diff --git a/plugin/src/example/components/tabs.js b/plugin/src/example/components/tabs.js new file mode 100644 index 0000000..b1431a8 --- /dev/null +++ b/plugin/src/example/components/tabs.js @@ -0,0 +1,9 @@ +import Note from './note'; + +export default ({ tabs }) => + tabs.map(tab => ( +
    +

    Tab: {tab.title}

    + {tab.notes ? tab.notes.map(note => ) : null} +
    + )); diff --git a/plugin/src/example/edit.js b/plugin/src/example/edit.js index 89179da..b4ab72a 100644 --- a/plugin/src/example/edit.js +++ b/plugin/src/example/edit.js @@ -1,7 +1,17 @@ +import Inspector from './inspector'; +import Tabs from './components/tabs'; import { RichText } from 'gutenblock-controls'; -const Edit = () => ( +const Edit = ({ attributes }) => (
    - +
    + +
    + +
    ); diff --git a/plugin/src/example/inspector.js b/plugin/src/example/inspector.js new file mode 100644 index 0000000..80e43b2 --- /dev/null +++ b/plugin/src/example/inspector.js @@ -0,0 +1,13 @@ +import { Inspector, Repeat, RepeatInput } from 'gutenblock-controls'; + +export default () => ( + + + + + + + + + +);