Liens utiles

Getting started : https://grapesjs.com/docs/getting-started.html

Installer Grapejs

npm install grapejs

Ci-dessous est une suggestion d’organisation de la structure du projet.

.
|
└── src
    ├── index.ts
    ├── styles
    │   ├── dist
    │   │   └── tailwind.css
    │   ├── grape-container.css
    │   ├── postcss.config.js
    │   └── tailwind.css
    └── webparts
        └── monSuperSite
            ├── MonSuperSiteWebPart.ts
            ├── grapejs         // Regroupe toute la logique du Builder Grapejs
            │   ├── components  // Regroupe les plugins des composants grapejs
            │   │   ├── Counter
            │   │   │   └── Counter.tsx
            │   └── index.tsx   // Fichier de configuration du Builder
            ├── components      // Regroupe les composants React
            │   ├── Counter
            │   │   └── Counter.tsx
            │   ├── MonSuperSite.tsx
            │   ├── Page        // Rendu des pages à partir des données GrapeJS
            │   │   └── Page.tsx
            │   └── Wrapper     // Rendu des composants à partir des données des composants GrapeJS
            │       └── Wrapper.tsx
            └── pnpjsConfig.ts

Les composants GrapeJS ne sont pas des composants React mais des plugins à ajouter dans la configuration de l’éditeur GrapeJS. Ils peuvent utiliser des composants React en les convertissant en HTML à travers ReactDOM.

Pour le code complet, voir le gitlab.

Initialisation de l’éditeur

Dans un fichier src/webparts/monSuperSite/grapejs/index.tsx :

import grapesjs from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';
import "../../../styles/dist/tailwind.css"; // N'est pas requis pour GrapeJS, mais utile.

export default function GrapeContainer({}: IGrapeContainerProps): ReactElement<IGrapeContainerProps> {

    const editorRef = useRef<any>(null);

    useEffect(() => {
        editorRef.current = grapesjs.init({
            // Indicate where to init the editor. You can also pass an HTMLElement
            container: '#gjs',
            // Get the content for the canvas directly from the element
            fromElement: true,
            // Size of the editor
            width: 'auto',

            canvas: {
                styles: [ // Injection des styles dont on a besoin dans l'éditeur
                    "https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
                ]
            },

            // Avoid any default panel
            blockManager: {
                appendTo: '#blocks',
                blocks: [
                    {
                        id: 'text',
                        label: 'Text', // You can use HTML/SVG inside labels - ex: <b>Text</b>
                        content: '<div data-gjs-type="text">Insert your text here</div>', // Composant Built-in
                        category: "Basic Blocks"
                    },
                    {
                        id: "counter", // 
                        label: "Counter",
                        select: true,
                        content: { type: 'counter-type' }, // Composant Custom
                        activate: true,
                        category: "Custom Blocks"
                    },
                ]
            },
            layerManager: {
                appendTo: '.layers-container'
            },
            panels: { // Configuration des Panels
                defaults: [
                    {
                        id: 'blocks', // id du Panel
                        el: '.panel__left', // Sélecteur de l'élément HTML où injecter le Panel
                        // Make the panel resizable
                        resizable: {
                            maxDim: 350,
                            minDim: 200,
                            tc: false, // Top handler
                            cl: true, // Left handler
                            cr: true, // Right handler
                            bc: false, // Bottom handler
                            // Being a flex child we need to change `flex-basis` property
                            // instead of the `width` (default)
                            keyWidth: 'flex-basis',
                        },
                    },
                    {
                        id: 'blocks-actions', // id du Panel
                        el: ".pannel__blocks-actions", // Sélecteur de l'élément HTML où injecter le Panel
                        buttons: [ // Boutons du Panel
                            {
                                id: 'show-blocks', // ID unique de l'élément HTML où injecter le bouton
                                active: true,
                                label: '+',
                                command: 'show-blocks', // Commande Custom ou Built-in
                                togglable: true,
                            },
                        ]
                    },
                    // ...
                ]
            },
            selectorManager: { // Configuration du Manager des sélecteurs des classes, états...
                appendTo: '.styles-container'
            },
            styleManager: { // Configuration du Manager des styles
                appendTo: '.styles-container',
                sectors: [{
                    name: 'Dimension',
                    open: false,
                    // Use built-in properties
                    buildProps: ['width', 'min-height', 'padding'],
                    // Use `properties` to define/override single property
                    properties: [
                        {
                            // Type of the input,
                            // options: integer | radio | select | color | slider | file | composite | stack
                            type: 'integer',
                            name: 'The width', // Label for the property
                            property: 'width', // CSS property (if buildProps contains it will be extended)
                            units: ['px', '%'], // Units, available only for 'integer' types
                            defaults: 'auto', // Default value
                            min: 0, // Min value, available only for 'integer' types
                        }
                    ]
                }, 
                // ...
                ]
            },
            storageManager: { // Configuration du manager de gestion des données du projets
                type: "remote-local", // Dans ce cas, c'est un manager personnalisé
                autosave: true,
                autoload: true,
                stepsBeforeSave: 1, // À augmenter selon les préférences : nombre d'étapes avant la sauvegarde
            },
            traitManager: { // Configuration du manager de gestion d'attributs des Components GrapeJS
                appendTo: '.traits-container',
            },
            deviceManager: { // Configuration du manager responsive
                devices: [{
                    name: 'Desktop',
                    width: '', // default size
                }, {
                    name: 'Mobile',
                    width: '320px', // this value will be used on canvas width
                    widthMedia: '480px', // this value will be used in CSS @media
                }]
            },
            pageManager: { // Configration du manager des pages
                pages: [
                    {
                        id: "first-page", // ID unique de la page
                        component: "<div id='first-page'>First page</div>" // Composant par défaut
                    },
                    // ...
                ]
            }
        });

        return () => {
            // Suppression de la ref de l'éditeur GrapeJS
            editorRef.current && editorRef.current.destroy();
        };
    }, []);


    return(
        <>
            {/* Panel Top : regroupe des des boutons liés à des commandes GrapJS au-dessus du Canvas */}
            <div className="panel__top">
                <div className="panel__basic-actions"></div>
                <div className="panel__devices"></div>
                <div className="panel__switcher"></div>
            </div>
            <div className="editor-row">

                {/* Panel Top : regroupe des des boutons liés à des commandes GrapJS à gauche du Canvas */}
                <div className="panel__left">
                    <div className='relative w-full flex'>
                        <div className='pannel__blocks-actions !relative'></div>
                    </div>
                    <div>
                        <div className="blocks-container" id="blocks"></div>
                    </div>
                </div>

                {/* Conteneur du Canvas *}
                <div className="editor-canvas">
                    {/* GrapeJS injecte l'éditeur dans la div suivante via l'id 'gjs' */}
                    <div id="gjs" className='max-h-full overflow-y-scroll'></div>
                </div>

                {/* Panel Top : regroupe des des boutons liés à des commandes GrapJS à droite du Canvas */}
                <div className="panel__right">
                    <div className="layers-container"></div>
                    <div className="styles-container"></div>
                    <div className="traits-container"></div>
                </div>
            </div>
        </>
    );

}