Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1976 Document Builder Prototype #1999

Merged
merged 15 commits into from
Dec 3, 2021
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions scripts/webpack.scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,11 @@ module.exports = mergeWithShared({
: "self"
),
}),
// Don't fail on import of styles.
// Using an identity object instead of actual style sheet because styles are not needed for headers generations
new webpack.NormalModuleReplacementPlugin(
/\.module\.(css|scss)$/,
"identity-obj-proxy"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what is this for? I know we also have something similar in our Jest configuration? Add a comment for what this is needed for?

Copy link
Contributor Author

@BALEHOK BALEHOK Dec 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default webpack doesn't know how to load .scss files. The loader we use for the app will generate an object with class names (those weird hashes) as values, for Jest and Headers since we don't care about actual class names we can use "identity-obj-proxy" - an object that will return the property name for any property asked (obj.prop === 'prop'; obj.textSuccess === 'textSuccess').

),
],
});
56 changes: 9 additions & 47 deletions src/blocks/renderers/document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from "react";
import { Renderer } from "@/types";
import { BlockArg, BlockOptions, ComponentRef, Schema } from "@/core";
import { loadBrickYaml } from "@/runtime/brickYaml";
import InnerComponentContext from "@/blocks/renderers/documentView/InnerComponentContext";

import BootstrapStylesheet from "./BootstrapStylesheet";
import DocumentViewLazy from "./documentView/DocumentViewLazy";

export class DocumentRenderer extends Renderer {
constructor() {
Expand All @@ -35,17 +31,9 @@ export class DocumentRenderer extends Renderer {
inputSchema: Schema = {
properties: {
body: {
oneOf: [
{
type: "string",
description: "A YAML document config to render",
},
{
type: "object",
description: "A document configuration",
additionalProperties: true,
},
],
type: "object",
description: "A document configuration",
additionalProperties: true,
},
},
required: ["body"],
Expand All @@ -55,38 +43,12 @@ export class DocumentRenderer extends Renderer {
{ body }: BlockArg,
options: BlockOptions
): Promise<ComponentRef> {
const bodyObj =
typeof body === "string"
? // Using loadBrickYaml here in order to support !pipeline expressions. If there's any expressions (e.g., !var),
// etc. outside of a !pipeline expression, the rendering will probably crash
loadBrickYaml(body)
: body;

// Dynamic import because documentView has a transitive dependency of react-shadow-root which assumed a proper
// `window` variable is present on module load. This isn't available on header generation
const { default: ReactShadowRoot } = await import("react-shadow-root");
const { getComponent } = await import(
"@/blocks/renderers/documentView/documentView"
);

const { Component, props } = getComponent(bodyObj);

// Wrap in a React context provider that passes BlockOptions down to any embedded bricks
// ReactShadowRoot needs to be inside an HTMLElement so it has something to attach to
const WrappedComponent = (props: any) => (
<InnerComponentContext.Provider value={{ options }}>
<div className="h-100">
<ReactShadowRoot>
<BootstrapStylesheet />
<Component {...props} />
</ReactShadowRoot>
</div>
</InnerComponentContext.Provider>
);

return {
Component: WrappedComponent,
props,
Component: DocumentViewLazy,
props: {
body,
options,
},
};
}
}
41 changes: 41 additions & 0 deletions src/blocks/renderers/documentView/DocumentView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { buildDocumentBranch } from "@/components/documentBuilder/documentTree";
import React from "react";
import ReactShadowRoot from "react-shadow-root";
import BootstrapStylesheet from "@/blocks/renderers/BootstrapStylesheet";
import { DocumentViewProps } from "./DocumentViewProps";
import DocumentContext from "@/components/documentBuilder/DocumentContext";

const DocumentView: React.FC<DocumentViewProps> = ({ body, options }) => (
// Wrap in a React context provider that passes BlockOptions down to any embedded bricks
// ReactShadowRoot needs to be inside an HTMLElement so it has something to attach to
<DocumentContext.Provider value={{ options }}>
<div className="h-100">
<ReactShadowRoot>
<BootstrapStylesheet />
{body.map((documentElement, i) => {
const { Component, props } = buildDocumentBranch(documentElement);
return <Component key={i} {...props} />;
})}
</ReactShadowRoot>
</div>
</DocumentContext.Provider>
);

export default DocumentView;
37 changes: 37 additions & 0 deletions src/blocks/renderers/documentView/DocumentViewLazy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { Suspense } from "react";
import { DocumentViewProps } from "./DocumentViewProps";

// Dynamic import because documentView has a transitive dependency of react-shadow-root which assumed a proper
// `window` variable is present on module load. This isn't available on header generation
const DocumentView = React.lazy(
async () =>
import(
/* webpackChunkName: "document-view" */
"./DocumentView"
)
);

const DocumentViewLazy: React.FC<DocumentViewProps> = (props) => (
<Suspense fallback={<div className="text-muted">Loading...</div>}>
<DocumentView {...props} />
</Suspense>
);

export default DocumentViewLazy;
7 changes: 7 additions & 0 deletions src/blocks/renderers/documentView/DocumentViewProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { DocumentElement } from "@/components/documentBuilder/documentBuilderTypes";
import { BlockArgContext, BlockOptions } from "@/core";

export type DocumentViewProps = {
body: DocumentElement[];
options: BlockOptions<BlockArgContext>;
};
Loading