prevwong / craft.js

🚀 A React Framework for building extensible drag and drop page editors
https://craft.js.org
MIT License
7.53k stars 729 forks source link

Display of created pages without editing options #447

Open LukasBlu1 opened 2 years ago

LukasBlu1 commented 2 years ago

Hey all!

CraftJs is really great and the perfect base for our project. Thanks for maintaining :)

We build something that is equivalent to a landing page builder. For example, a user has created his page and wants to show it to his customers after he is done. Our current solution is to create minified components with no editing capabilities and use the editor component to display them on the actual landing page.

Is there such a thing as a minimal version of Craftjs for when you only want to display the pages without editing capabilities. If not, what is the best way to achieve that?

Thanks in advance for your help :)

Kind regards Lukas

cederron commented 2 years ago

just set editor enabled = false

LukasBlu1 commented 2 years ago

just set editor enabled = false

Thank you for your reply, this is what we do. We were thinking of a lightweight, performance optimized version. If there is nothing like that, we will develop something. If there is interest, we will make it available here.

cederron commented 2 years ago

just set editor enabled = false

Thank you for your reply, this is what we do. We were thinking of a lightweight, performance optimized version. If there is nothing like that, we will develop something. If there is interest, we will make it available here.

There's nothing like that AFAIK, would love to hear if you find something.

hugominas commented 2 years ago

@LukasBlu1 This topic has been discussed a few times, I did try to implement a preview mode following issue 42 https://github.com/prevwong/craft.js/issues/42 but the outcome was always an non interactive versions of the website, so we are using the nextjs getstaticprops to get better performance, but ideally a lightweight version would be much better.

According to @prevwong https://discord.com/channels/665481114734952448/665481348311547924/964682224786100244 "you just need to start rendering a node starting from the Root node and for each node, recursively render any child nodes in the children and linkedNodes property"

All the best and if you don't mind sharing your code I am sure it would be a great addition to the project

LukasBlu1 commented 1 year ago

The following component maps a compressed Craftjs state to React components. Including support for React.lazy to make code splitting available. It is a complete replacement for the non-editable mode of Craftjs. To get this working, you need to create a presentation component from any Craftjs component without using Craftjs hooks. Many thanks to @Rdonn, large parts of the logic for this component came from his suggestions.

import React, { Suspense, useEffect, useState } from "react";
import lz from "lzutf8";
import { StandardSpinner } from "../../SharedUI/components/StandardSpinner";
import { CRAFT_ELEMENTS } from "../config/craftElements";
import Body from "../components/Body";
import Container from "../components/Container";

const CraftJsUserComponents = {
   [CRAFT_ELEMENTS.BODY]: Body,
   [CRAFT_ELEMENTS.CONTAINER]: Container,
   [CRAFT_ELEMENTS.BUTTON]: React.lazy(async () => await import("../components/Button")),
   [CRAFT_ELEMENTS.IMAGE]: React.lazy(async () => await import("../components/Image")),
   ...
};

interface Props {
   compressedCraftState: string;
}

const ResolvedComponentsFromCraftState = ({ compressedCraftState }: Props): React.ReactElement | null => {
   const [toRender, setToRender] = useState<React.ReactElement | null>(null);

   useEffect(() => {
      const craftState = JSON.parse(lz.decompress(lz.decodeBase64(compressedCraftState)));

      const resolvedComponents: React.ReactElement = (() => {
         const parse = (nodeId: string, parentNodeId?: string): React.ReactElement => {
            if (nodeId === null || nodeId === "") return <></>;

            const childNodeNames: string[] = craftState[nodeId]?.nodes || [];
            const ReactComponent = (CraftJsUserComponents as any)[craftState[nodeId].type.resolvedName];
            const extendedProps = {
               ...craftState[nodeId].props,
               parentNodeId,
               nodeId,
               key: nodeId,
            };

            if (childNodeNames.length === 0) return <ReactComponent {...extendedProps} />;

            const childNodes = childNodeNames.map((childNodeId) => {
               return parse(childNodeId, nodeId);
            });

            return <ReactComponent {...extendedProps}>{childNodes}</ReactComponent>;
         };

         return parse("ROOT");
      })();

      setToRender(resolvedComponents);
   }, []);

   return <Suspense fallback={<StandardSpinner />}>{toRender}</Suspense>;
};

export default ResolvedComponentsFromCraftState;

You can use it easily like a usual react component.

<ResolvedComponentsFromCraftState compressedCraftState={compressedCraftState} />