├── README.md
├── package.json
├── public
└── index.html
└── src
├── Listing
└── index.js
├── base-react-component.js
├── index.js
├── mui-components
└── index.js
└── react-components.js
/README.md:
--------------------------------------------------------------------------------
1 | # grapesjs-react-components-by-artf
2 |
3 | Forked from https://codesandbox.io/s/grapesjs-react-components-n6sff
4 |
5 | Related issue: https://github.com/artf/grapesjs/issues/1970
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grapesjs-react-components",
3 | "version": "1.0.0",
4 | "description": "Use React components in GrapesJS",
5 | "keywords": [
6 | "react",
7 | "grapesjs"
8 | ],
9 | "main": "src/index.js",
10 | "dependencies": {
11 | "@material-ui/core": "4.12.3",
12 | "grapesjs": "0.17.22",
13 | "grapesjs-blocks-basic": "0.1.8",
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2"
16 | },
17 | "devDependencies": {
18 | "typescript": "3.3.3"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test --env=jsdom",
24 | "eject": "react-scripts eject"
25 | },
26 | "browserslist": [
27 | ">0.2%",
28 | "not dead",
29 | "not ie <= 11",
30 | "not op_mini all"
31 | ]
32 | }
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Listing/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Listing extends React.Component {
4 | constructor(props) {
5 | super();
6 | this.state = {
7 | counter: 0
8 | };
9 | }
10 |
11 | updateCounter() {
12 | const { counter } = this.state;
13 | this.setState({
14 | counter: counter + 1
15 | });
16 | }
17 |
18 | componentDidMount() {
19 | setInterval(this.updateCounter.bind(this), 1000);
20 | }
21 |
22 | render() {
23 | const { props, state } = this;
24 | const { mlsid, children } = props;
25 | const { counter } = state;
26 |
27 | return (
28 |
29 |
Mlsid: {mlsid}
30 |
{children}
31 |
Counter: {counter}
32 |
33 | );
34 | }
35 | }
36 |
37 | export default Listing;
38 |
--------------------------------------------------------------------------------
/src/base-react-component.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import React from 'react';
3 |
4 | export default (editor) => {
5 | const domc = editor.Components;
6 | const defType = domc.getType('default');
7 | const defModel = defType.model;
8 | const wrpChld = 'data-chld';
9 |
10 | // Main React component
11 | domc.addType('react-component', {
12 | model: {
13 | toHTML(opts = {}) {
14 | return defModel.prototype.toHTML.call(this, {
15 | ...opts,
16 | tag: this.get('type')
17 | });
18 | }
19 | },
20 | view: {
21 | tagName: 'div',
22 |
23 | init() {
24 | const { model } = this;
25 | this.listenTo(model, 'change:attributes', this.render);
26 | this.listenTo(model.components(), 'add remove reset', this.__upRender);
27 | },
28 |
29 | getChildrenContainer() {
30 | const { childrenContainer } = this;
31 | if (childrenContainer) return childrenContainer;
32 |
33 | this.childrenContainer = document.createElement('childc');
34 |
35 | return this.childrenContainer;
36 | },
37 |
38 | /**
39 | * We need this container to understand if the React component is able
40 | * to render children
41 | */
42 | createReactChildWrap() {
43 | return React.createElement('span', { [wrpChld]: true });
44 | },
45 |
46 | createReactEl(cmp, props) {
47 | return React.createElement(cmp, props, this.createReactChildWrap());
48 | },
49 |
50 | mountReact(cmp, el) {
51 | ReactDOM.render(cmp, el);
52 | },
53 |
54 | render() {
55 | const { model, el } = this;
56 | this.updateAttributes();
57 | this.renderChildren();
58 | const reactEl = this.createReactEl(model.get('component'), {
59 | ...model.get('attributes')
60 | });
61 | this.mountReact(reactEl, el);
62 | const chld = el.querySelector(`span[${wrpChld}]`);
63 |
64 | // If the container is found, the react component is able to render children
65 | if (chld) {
66 | const chldCont = this.getChildrenContainer();
67 | while (chldCont.firstChild) {
68 | chld.appendChild(chldCont.firstChild);
69 | }
70 | }
71 |
72 | return this;
73 | },
74 |
75 | __upRender() {
76 | clearTimeout(this._upr);
77 | this._upr = setTimeout(() => this.render());
78 | }
79 | }
80 | });
81 | };
82 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import grapesjs from 'grapesjs';
2 | import Basics from 'grapesjs-blocks-basic';
3 |
4 | import BaseReactComponent from './base-react-component';
5 | import ReactComponents from './react-components';
6 | import MuiComponents from './mui-components';
7 |
8 | const editor = grapesjs.init({
9 | container: '#gjs',
10 | height: '100%',
11 | storageManager: false,
12 | noticeOnUnload: false,
13 | plugins: [Basics, BaseReactComponent, ReactComponents, MuiComponents]
14 | });
15 |
16 | editor.setComponents(`
17 |
18 |
19 | Foo
20 |
21 |
22 |
23 | Bar
24 |
25 |
26 | Click Me
27 |
28 |
29 |
30 |
31 |
32 | Click Me
33 |
34 |
35 |
36 |
37 | `);
38 |
--------------------------------------------------------------------------------
/src/mui-components/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { create } from 'jss';
3 | import { StylesProvider, jssPreset } from '@material-ui/styles';
4 | import { Button, Slider, SnackbarContent } from '@material-ui/core';
5 |
6 | export default (editor) => {
7 | const { Blocks, Components } = editor;
8 | const sheetsManager = new Map();
9 |
10 | // Helper for MUI components
11 | const addCmp = ({ type, component, props }) => {
12 | Components.addType(type, {
13 | extend: 'react-component',
14 | model: {
15 | defaults: {
16 | ...props,
17 | component
18 | }
19 | },
20 | view: {
21 | /**
22 | * We need this in order to render MUI styles in the canvas
23 | */
24 | createReactEl(cmp, props) {
25 | const cmpMain = React.createElement(
26 | cmp,
27 | props,
28 | this.createReactChildWrap()
29 | );
30 | return React.createElement(
31 | StylesProvider,
32 | {
33 | sheetsManager,
34 | jss: create({
35 | plugins: [...jssPreset().plugins],
36 | insertionPoint: this.em.get('Canvas').getDocument().head
37 | })
38 | },
39 | cmpMain
40 | );
41 | }
42 | },
43 | isComponent: (el) => el.tagName === type.toUpperCase()
44 | });
45 |
46 | Blocks.add(type, {
47 | label: type,
48 | category: 'MUI',
49 | content: { type }
50 | });
51 | };
52 |
53 | addCmp({
54 | type: 'MuiButton',
55 | component: Button,
56 | props: {
57 | attributes: {
58 | color: 'primary',
59 | variant: 'contained'
60 | },
61 | components: 'Click me',
62 | traits: [
63 | {
64 | type: 'select',
65 | label: 'Variant',
66 | name: 'variant',
67 | options: [
68 | { value: 'contained', name: 'Contained' },
69 | { value: 'outlined', name: 'Outlined' }
70 | ]
71 | },
72 |
73 | {
74 | type: 'checkbox',
75 | label: 'Disabled',
76 | name: 'disabled'
77 | },
78 | {
79 | type: 'select',
80 | label: 'Color',
81 | name: 'color',
82 | options: [
83 | { value: 'primary', name: 'Primary' },
84 | { value: 'secondary', name: 'Secondary' }
85 | ]
86 | }
87 | ]
88 | }
89 | });
90 |
91 | addCmp({
92 | type: 'Slider',
93 | component: Slider,
94 | props: {
95 | stylable: false,
96 | editable: true,
97 | void: true,
98 | attributes: {
99 | min: 0,
100 | max: 100
101 | },
102 | traits: [
103 | {
104 | type: 'number',
105 | label: 'Min',
106 | name: 'min'
107 | },
108 | {
109 | type: 'number',
110 | label: 'Max',
111 | name: 'max'
112 | }
113 | ]
114 | }
115 | });
116 |
117 | addCmp({
118 | type: 'Snackbar',
119 | component: (props) =>
120 | React.createElement(SnackbarContent, {
121 | ...props,
122 | message: props.children
123 | }),
124 | props: {
125 | stylable: false,
126 | editable: true,
127 | traits: []
128 | }
129 | });
130 | };
131 |
--------------------------------------------------------------------------------
/src/react-components.js:
--------------------------------------------------------------------------------
1 | import Listing from './Listing';
2 |
3 | export default (editor) => {
4 | editor.Components.addType('Listing', {
5 | extend: 'react-component',
6 | model: {
7 | defaults: {
8 | component: Listing,
9 | stylable: true,
10 | resizable: true,
11 | editable: true,
12 | draggable: true,
13 | droppable: true,
14 | attributes: {
15 | mlsid: 'Default MLSID',
16 | editable: true
17 | },
18 | traits: [
19 | {
20 | type: 'number',
21 | label: 'MLS ID',
22 | name: 'mlsid'
23 | }
24 | ]
25 | }
26 | },
27 | isComponent: (el) => el.tagName === 'LISTING'
28 | });
29 |
30 | editor.BlockManager.add('listing', {
31 | label: "Listing
",
32 | category: 'React Components',
33 | content: 'Foo'
34 | });
35 | };
36 |
--------------------------------------------------------------------------------