diff --git a/editor-packages/editor-canvas/math/center-of.ts b/editor-packages/editor-canvas/math/center-of.ts index 5391fb0e..8b23ee4c 100644 --- a/editor-packages/editor-canvas/math/center-of.ts +++ b/editor-packages/editor-canvas/math/center-of.ts @@ -55,8 +55,6 @@ export function centerOf( vbcenter[1] - boxcenter[1] * scale, ]; - console.log(translate, scale); - return { box: box, center: boxcenter, diff --git a/editor/components/codeui-code-options-control/code-options-control.tsx b/editor/components/codeui-code-options-control/code-options-control.tsx index 22776d79..acfd1913 100644 --- a/editor/components/codeui-code-options-control/code-options-control.tsx +++ b/editor/components/codeui-code-options-control/code-options-control.tsx @@ -49,6 +49,11 @@ export function CodeOptionsControl(props: CodeOptionsControlProps) { value: "react_with_styled_components", description: "with styled-component", }, + { + name: "React", + value: "react_with_inline_css", + description: "with inline-css", + }, { name: "Flutter", value: "flutter_default", diff --git a/editor/components/codeui-code-options-control/framework-options.ts b/editor/components/codeui-code-options-control/framework-options.ts index 433d548d..431bc50d 100644 --- a/editor/components/codeui-code-options-control/framework-options.ts +++ b/editor/components/codeui-code-options-control/framework-options.ts @@ -7,7 +7,7 @@ export enum Language { html = "html", } -export type ReactStylingStrategy = "css" | "styled-components" | "css-in-jsx"; +export type ReactStylingStrategy = "css" | "styled-components" | "inline-css"; export interface FlutterOption { framework: Framework.flutter; @@ -38,10 +38,10 @@ export const react_presets = { language: Language.tsx, styling: "styled-components", }, - react_with_css_in_jsx: { + react_with_inline_css: { framework: Framework.react, language: Language.tsx, - styling: "css-in-jsx", + styling: "inline-css", }, react_with_css: { framework: Framework.react, @@ -74,8 +74,8 @@ export const all_preset_options__prod = [ flutter_presets.flutter_default, react_presets.react_default, react_presets.react_with_styled_components, + react_presets.react_with_inline_css, vanilla_presets.vanilla_default, - // react_with_css_in_jsx // NOT ON PRODUCTION // react_with_css // NOT ON PRODUCTION ]; @@ -84,8 +84,8 @@ export const all_preset_options_map__prod = { flutter_default: flutter_presets.flutter_default, react_default: react_presets.react_default, react_with_styled_components: react_presets.react_with_styled_components, + react_with_inline_css: react_presets.react_with_inline_css, vanilla_default: vanilla_presets.vanilla_default, - // react_with_css_in_jsx // NOT ON PRODUCTION // react_with_css // NOT ON PRODUCTION }; @@ -97,7 +97,7 @@ export const lang_by_framework = { export const react_styles: ReactStylingStrategy[] = [ "styled-components", - "css-in-jsx", + "inline-css", "css", ]; diff --git a/editor/pages/figma/to-flutter.tsx b/editor/pages/figma/to-flutter.tsx deleted file mode 100644 index beffc034..00000000 --- a/editor/pages/figma/to-flutter.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { designTo } from "@designto/code"; -import styled from "@emotion/styled"; -import { RemoteImageRepositories } from "@design-sdk/figma-remote/lib/asset-repository/image-repository"; -import { - ImageRepository, - MainImageRepository, -} from "@design-sdk/core/assets-repository"; -import { output } from "@designto/config"; -import { tokenize } from "@designto/token"; -import { utils_dart } from "utils"; -import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; -import { LayerHierarchy } from "components/editor-hierarchy"; -import { - WorkspaceContentPanel, - WorkspaceContentPanelGridLayout, -} from "layouts/panel"; -import { PreviewAndRunPanel } from "components/preview-and-run"; -import { useDesign } from "hooks"; -import { CodeEditor } from "components/code-editor"; -import LoadingLayout from "layouts/loading-overlay"; - -export default function FigmaToFlutterPage() { - const design = useDesign({ type: "use-router" }); - const [result, setResult] = useState(); - - useEffect(() => { - if (design) { - const { reflect, url, node, file } = design; - - // ------------------------------------------------------------ - // other platforms are not supported yet - // set image repo for figma platform - MainImageRepository.instance = new RemoteImageRepositories(design.file); - MainImageRepository.instance.register( - new ImageRepository( - "fill-later-assets", - "grida://assets-reservation/images/" - ) - ); - // ------------------------------------------------------------ - designTo - .flutter({ - input: { - widget: tokenize(reflect), - }, - asset_config: { asset_repository: MainImageRepository.instance }, - }) - .then(setResult); - } - }, [design]); - - if (!result) { - return ; - } - - const widgetCode = utils_dart.format(result.code.raw); - const rootAppCode = utils_dart.format(result.scaffold.raw); - console.log("flutter result", result); - - return ( - <> - } - > - - - - - - - - - - - - - ); -} - -const InspectionPanelContentWrap = styled.div` - display: flex; - flex-direction: row; - align-items: stretch; -`; diff --git a/editor/pages/figma/to-react.tsx b/editor/pages/figma/to-react.tsx deleted file mode 100644 index 2a846010..00000000 --- a/editor/pages/figma/to-react.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { useState } from "react"; -import styled from "@emotion/styled"; -import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; -import { LayerHierarchy } from "components/editor-hierarchy"; -import { PreviewAndRunPanel } from "components/preview-and-run"; -import { - WorkspaceContentPanel, - WorkspaceContentPanelGridLayout, -} from "layouts/panel"; -import { WorkspaceBottomPanelDockLayout } from "layouts/panel/workspace-bottom-panel-dock-layout"; -import { WidgetTree } from "@code-editor/debugger/components/visualization/json-visualization/json-tree"; -import { CodeEditor } from "components/code-editor"; -import { tokenize } from "@designto/token"; -import * as react from "@designto/react"; -import { mapGrandchildren } from "@design-sdk/core/utils"; -import { JsxWidget } from "@web-builder/core"; -import * as core from "@reflect-ui/core"; -import { react as reactconfig } from "@designto/config"; -import { useReflectTargetNode } from "../../query/from-figma"; -import { react_presets } from "@grida/builder-config-preset"; - -export default function FigmaToReactDemoPage() { - const [targetSelectionNodeId, setTargetSelectionNodeId] = useState(); - - // - const targetNodeConfig = useReflectTargetNode(); - const figmaNode = targetNodeConfig?.figma; - const reflect = targetNodeConfig?.reflect; - // - - const handleOnSingleLayerSelect = (id: string) => { - const newTarget = mapGrandchildren(reflect).find((r) => r.id == id); - console.log("newTarget", id, newTarget); - if (newTarget) { - setTargetSelectionNodeId(id); - // setReflect(newTarget); - } - }; - - let reactComponent: reactconfig.ReactComponentOutput; - let reflectWidget: core.Widget; - let widgetTree: JsxWidget; - if (reflect) { - reflectWidget = tokenize(reflect); - widgetTree = react.buildReactWidget(reflectWidget); - const _stringfiedReactwidget = react.buildReactApp( - widgetTree, - react_presets.react_default - ); - - reactComponent = _stringfiedReactwidget; - } - - return ( -
- - } - > - - - - - - - - - - - -
-
- -
-
- -
-
- -
-
-
-
-
-
-
- ); -} - -const InspectionPanelContentWrap = styled.div` - display: flex; - flex-direction: row; - align-items: stretch; -`; diff --git a/editor/query/to-code-options-from-query.ts b/editor/query/to-code-options-from-query.ts index 5098776d..baffe80a 100644 --- a/editor/query/to-code-options-from-query.ts +++ b/editor/query/to-code-options-from-query.ts @@ -30,9 +30,13 @@ export function get_framework_config(framework: string) { case "react.default": return react_presets.react_default; case "react-with-styled-components": + case "react_with_styled_components": return react_presets.react_with_styled_components; case "react-with-emotion-styled": return react_presets.react_with_emotion_styled; + case "react_with_inline_css": + case "react-with-inline-css": + return react_presets.react_with_inline_css; case "flutter": case "flutter_default": case "flutter-default": diff --git a/editor/scaffolds/code/index.tsx b/editor/scaffolds/code/index.tsx index eb0cc829..f01e1b50 100644 --- a/editor/scaffolds/code/index.tsx +++ b/editor/scaffolds/code/index.tsx @@ -109,7 +109,6 @@ export function CodeSegment() { }, [targetted?.id, framework_config]); const { code, scaffold, name: componentName } = result ?? {}; - return ( @@ -117,7 +116,30 @@ export function CodeSegment() { initialPreset={router.query.framework as string} fallbackPreset="react_default" onUseroptionChange={(o) => { - set_framework_config(get_framework_config(o.framework)); + let c; + switch (o.framework) { + case "react": { + switch (o.styling) { + case "inline-css": + c = get_framework_config("react-with-inline-css"); + break; + case "styled-components": + c = get_framework_config("react-with-styled-components"); + break; + case "css": + // TODO: + break; + } + break; + } + case "flutter": + c = get_framework_config(o.framework); + break; + case "vanilla": + c = get_framework_config(o.framework); + break; + } + set_framework_config(c); }} /> { + react_with_inline_css: { framework: Framework.react, language: Language.tsx, styling: { - type: "css-in-jsx", + type: "inline-css", }, component_declaration_style: _react_component_declaration_style, }, @@ -109,9 +109,9 @@ export const react_styles: { module: "styled-components", }, ], - "css-in-jsx": [ + "inline-css": [ { - type: "css-in-jsx", + type: "inline-css", }, ], css: [ diff --git a/packages/builder-config/framework-react/react-config-styling.ts b/packages/builder-config/framework-react/react-config-styling.ts index 4eeca13d..5517af52 100644 --- a/packages/builder-config/framework-react/react-config-styling.ts +++ b/packages/builder-config/framework-react/react-config-styling.ts @@ -1,11 +1,12 @@ export type ReactStylingStrategy = - | CssInJsxConfig - | CssStylingConfig + | ReactInlineCssConfig + | ReactCssStylingConfig | ReactStyledComponentsConfig; -interface CssInJsxConfig { - type: "css-in-jsx"; -} +export type ReactStyledComponentsConfig = + | ReactTheStyledComponentsConfig + | ReactEmotionStyledConfig; +export type ReactCssStylingConfig = CssStylingConfig; type CssStylingConfig = VanillaCssStylingConfig | ScssStylingConfig; @@ -19,10 +20,6 @@ interface ScssStylingConfig { lang: "scss"; } -export type ReactStyledComponentsConfig = - | ReactTheStyledComponentsConfig - | ReactEmotionStyledConfig; - /** * "The" styled-components config - https://styled-components.com/ */ @@ -38,3 +35,16 @@ interface ReactEmotionStyledConfig { type: "styled-components"; module: "@emotion/styled"; } + +/** + * Inline css styling + * + * ```tsx + * // examples + *
+ *
+ * ``` + */ +export interface ReactInlineCssConfig { + type: "inline-css"; +} diff --git a/packages/builder-css-styles/_utils/css-json.ts b/packages/builder-css-styles/_utils/css-json.ts new file mode 100644 index 00000000..289dbb93 --- /dev/null +++ b/packages/builder-css-styles/_utils/css-json.ts @@ -0,0 +1,34 @@ +/// +/// css to js transformer +/// reference: +/// - https://github.com/postcss/postcss-js/blob/main/objectifier.js +/// +import { CSSProperties } from "@coli.codes/css"; +import camel from "camelcase-css"; + +/** + * convertes standard css object to react acceptable camel case object + * + * + * ```js + * // from + * { + * "background-color": "red", + * } + * // to + * { + * backgroundColor: "red", + * } + * ``` + * @param payload + */ +export function cssToJson(payload: CSSProperties) { + const obj = Object.keys(payload).reduce(function (previous, key) { + const camelkey = camel(key); + return { + ...previous, + [camelkey]: payload[key], + }; + }, {}); + return JSON.parse(JSON.stringify(obj)); +} diff --git a/packages/builder-css-styles/_utils/index.ts b/packages/builder-css-styles/_utils/index.ts new file mode 100644 index 00000000..b600b85e --- /dev/null +++ b/packages/builder-css-styles/_utils/index.ts @@ -0,0 +1 @@ +export * from "./css-json"; diff --git a/packages/builder-css-styles/index.ts b/packages/builder-css-styles/index.ts index cf1cc4ca..7453ef0b 100644 --- a/packages/builder-css-styles/index.ts +++ b/packages/builder-css-styles/index.ts @@ -34,3 +34,6 @@ export * from "./ellipse"; // export * from "./polygon"; // WIP export * from "./path"; // -------------------------------------------------------------------------------- + +// utils +export * as utils from "./_utils"; diff --git a/packages/builder-css-styles/package.json b/packages/builder-css-styles/package.json index bdc1a3ab..04ec694c 100644 --- a/packages/builder-css-styles/package.json +++ b/packages/builder-css-styles/package.json @@ -1,4 +1,10 @@ { "name": "@web-builder/styles", - "version": "0.0.0" + "version": "0.0.0", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "devDependencies": { + "@types/camelcase-css": "^2.0.0" + } } \ No newline at end of file diff --git a/packages/builder-web-core/builders/build-jsx-tree.ts b/packages/builder-web-core/builders/build-jsx-tree.ts index 94f1faaf..3e2ab37b 100644 --- a/packages/builder-web-core/builders/build-jsx-tree.ts +++ b/packages/builder-web-core/builders/build-jsx-tree.ts @@ -11,11 +11,13 @@ import { StyledComponentJSXElementConfig, } from "@web-builder/styled"; import { + JSXAttributes, JSXChildLike, JSXClosingElement, JSXElement, JSXIdentifier, JSXOpeningElement, + JSXSelfClosingElement, } from "coli"; //// @@ -60,6 +62,14 @@ export function buildContainingJsx( } } +/** + * + * A Utility-like general jsx builder globally used while building html tree. + * + * @param widget + * @param repository + * @returns + */ export function buildJsx( widget: JsxWidget, repository: { @@ -70,43 +80,59 @@ export function buildJsx( * required for id based styling strategy */ idTransformer?: (jsx, id: string) => void; + preprocess?: (jsx: JSXElementConfig) => JSXElementConfig; + }, + options: { + self_closing_if_possible?: boolean; } ): JSXChildLike { + const force_dont_self_close = options.self_closing_if_possible === false; const mapper = (widget: JsxWidget) => { - const _jsxcfg = widget.jsxConfig(); + let _jsxcfg = widget.jsxConfig(); if (_jsxcfg.type === "static-tree") { return _jsxcfg.tree; } + // preprocess + _jsxcfg = repository.preprocess?.(_jsxcfg) ?? _jsxcfg; + const children = widget.children?.map(mapper); if (widget instanceof StylableJsxWidget) { const styledconfig = repository.styledConfig(widget.key.id); + // region build jsx + let jsx; if (widget instanceof TextChildWidget) { - const jsx = buildTextChildJsx(widget, styledconfig); - repository.idTransformer?.(jsx, styledconfig.id); - return jsx; - } - const jsx = new JSXElement({ - openingElement: new JSXOpeningElement(styledconfig.tag, { + jsx = buildTextChildJsx(widget, styledconfig); + } else { + jsx = _jsx_element_with_self_closing_if_possible({ + tag: styledconfig.tag, attributes: styledconfig.attributes, - }), - closingElement: new JSXClosingElement(styledconfig.tag), - children: children, - }); + children: children, + options: { + force_dont_self_close: force_dont_self_close, + }, + }); + } + // endregion build jsx + + // apply injected transformer (if present) repository.idTransformer?.(jsx, styledconfig.id); + return jsx; } else { const config = widget.jsxConfig(); if (config.type === "tag-and-attr") { const _tag = handle(config.tag); - const jsx = new JSXElement({ - openingElement: new JSXOpeningElement(_tag, { - attributes: config.attributes, - }), - closingElement: new JSXClosingElement(_tag), + const jsx = _jsx_element_with_self_closing_if_possible({ + tag: _tag, + attributes: config.attributes, children: children, + options: { + force_dont_self_close: force_dont_self_close, + }, }); + return jsx; } return; @@ -115,3 +141,33 @@ export function buildJsx( return mapper(widget); } + +function _jsx_element_with_self_closing_if_possible({ + children, + tag, + attributes, + options = { + force_dont_self_close: false, + }, +}: { + tag: JSXIdentifier; + children?: any; + attributes: JSXAttributes; + options?: { + force_dont_self_close?: boolean; + }; +}) { + if (!options?.force_dont_self_close && (!children || children.length == 0)) { + return new JSXSelfClosingElement(tag, { + attributes: attributes, + }); + } else { + return new JSXElement({ + openingElement: new JSXOpeningElement(tag, { + attributes: attributes, + }), + closingElement: new JSXClosingElement(tag), + children: children, + }); + } +} diff --git a/packages/builder-web-react/index.ts b/packages/builder-web-react/index.ts index 044c6b5b..09a6892e 100644 --- a/packages/builder-web-react/index.ts +++ b/packages/builder-web-react/index.ts @@ -1,4 +1,5 @@ export * from "./react-styled-component-widget"; +export * from "./react-inline-css-widget"; export * from "./react-import-specifications"; export * from "./react-project"; export * from "./widgets-native"; diff --git a/packages/builder-web-react/react-css-in-js-widget/css-in-jsx.ts b/packages/builder-web-react/react-css-in-js-widget/css-in-jsx.ts deleted file mode 100644 index cc341e62..00000000 --- a/packages/builder-web-react/react-css-in-js-widget/css-in-jsx.ts +++ /dev/null @@ -1 +0,0 @@ -export function stringfyReactWidget_CSS_IN_JSX() {} diff --git a/packages/builder-web-react/react-css-in-js-widget/index.ts b/packages/builder-web-react/react-css-in-js-widget/index.ts deleted file mode 100644 index 249bddb7..00000000 --- a/packages/builder-web-react/react-css-in-js-widget/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./css-in-jsx"; diff --git a/packages/builder-web-react/react-inline-css-widget/from-static-widget-tree.ts b/packages/builder-web-react/react-inline-css-widget/from-static-widget-tree.ts new file mode 100644 index 00000000..2fd57ebe --- /dev/null +++ b/packages/builder-web-react/react-inline-css-widget/from-static-widget-tree.ts @@ -0,0 +1,20 @@ +import { JsxWidget } from "@web-builder/core"; +import { react as react_config } from "@designto/config"; +import { ReactCssInJSBuilder } from "./react-inline-css-module-builder"; + +export function finalizeReactWidget_InlineCss( + entry: JsxWidget, + { + styling, + exporting, + }: { + styling: react_config.ReactInlineCssConfig; + exporting: react_config.ReactComponentExportingCofnig; + } +) { + const builder = new ReactCssInJSBuilder({ + entry, + config: styling, + }); + return builder.asExportableModule().finalize(exporting); +} diff --git a/packages/builder-web-react/react-inline-css-widget/index.ts b/packages/builder-web-react/react-inline-css-widget/index.ts new file mode 100644 index 00000000..b4da182b --- /dev/null +++ b/packages/builder-web-react/react-inline-css-widget/index.ts @@ -0,0 +1,2 @@ +export * from "./react-inline-css-module-builder"; +export * from "./from-static-widget-tree"; diff --git a/packages/builder-web-react/react-inline-css-widget/react-inline-css-module-builder.ts b/packages/builder-web-react/react-inline-css-widget/react-inline-css-module-builder.ts new file mode 100644 index 00000000..ec92d4b8 --- /dev/null +++ b/packages/builder-web-react/react-inline-css-widget/react-inline-css-module-builder.ts @@ -0,0 +1,188 @@ +import { ReservedKeywordPlatformPresets } from "@coli.codes/naming/reserved"; +import { react as react_config } from "@designto/config"; +import type { JSXElementConfig, JsxWidget } from "@web-builder/core"; +import { + buildJsx, + getWidgetStylesConfigMap, + JSXWithoutStyleElementConfig, + JSXWithStyleElementConfig, + WidgetStyleConfigMap, +} from "@web-builder/core/builders"; +import { + BlockStatement, + Identifier, + ImportDeclaration, + JSXAttribute, + Literal, + ObjectLiteralExpression, + PropertyAssignment, + Return, + ScopedVariableNamer, + StringLiteral, + TemplateLiteral, +} from "coli"; +import * as css from "@web-builder/styles"; +import { react_imports } from "../react-import-specifications"; +import { ReactWidgetModuleExportable } from "../react-module"; +import { makeReactModuleFile, ReactModuleFile } from "../react-module-file"; +import { cssToJson } from "@web-builder/styles/_utils"; +import { CSSProperties } from "@coli.codes/css"; + +/** + * CSS In JS Style builder for React Framework + * + * + * css in js is a pattern that allows you to use css as a object in jsx, to property `style`. + * + * ```tsx + * // output be like... + *
+ * ``` + * + */ +export class ReactCssInJSBuilder { + private readonly entry: JsxWidget; + private readonly widgetName: string; + readonly config: react_config.ReactInlineCssConfig; + private readonly namer: ScopedVariableNamer; + private readonly styledConfigWidgetMap: WidgetStyleConfigMap; + + constructor({ + entry, + config, + }: { + entry: JsxWidget; + config: react_config.ReactInlineCssConfig; + }) { + this.entry = entry; + this.widgetName = entry.key.name; + this.config = config; + this.namer = new ScopedVariableNamer( + entry.key.id, + ReservedKeywordPlatformPresets.react + ); + this.styledConfigWidgetMap = getWidgetStylesConfigMap(entry, { + namer: this.namer, + rename_tag: false, + }); + } + + private styledConfig( + id: string + ): JSXWithStyleElementConfig | JSXWithoutStyleElementConfig { + return this.styledConfigWidgetMap.get(id); + } + + private jsxBuilder(widget: JsxWidget) { + return buildJsx( + widget, + { + styledConfig: (id) => { + const cfg = this.styledConfig(id); + const _default_attr = cfg.attributes; + + const existingstyleattr = _default_attr?.find( + // where style refers to react's jsx style attribute + (a) => a.name.name === "style" + ); + + let style: JSXAttribute; + if (existingstyleattr) { + // ignore this case. (element already with style attriibute may be svg element) + // this case is not supported. (should supported if the logic changes) + } else { + // + const styledata: CSSProperties = + (cfg as JSXWithStyleElementConfig).style ?? {}; + const reactStyleData = cssToJson(styledata); + const properties: PropertyAssignment[] = Object.keys( + reactStyleData + ).map( + (key) => + new PropertyAssignment({ + name: key as unknown as Identifier, + initializer: new TemplateLiteral(reactStyleData[key]), + }) + ); + + style = new JSXAttribute( + "style", + new BlockStatement( + new ObjectLiteralExpression({ + properties: properties, + }) + ) + ); + } + + const newattributes = [ + ...(_default_attr ?? []), + // + style, + ]; + + cfg.attributes = newattributes; + + return cfg; + }, + }, + { + self_closing_if_possible: true, + } + ); + } + + partImports() { + return [react_imports.import_react_from_react]; + } + + partBody(): BlockStatement { + let jsxTree = this.jsxBuilder(this.entry); + return new BlockStatement(new Return(jsxTree)); + } + + asExportableModule() { + const body = this.partBody(); + const imports = this.partImports(); + return new ReactInlineCssWidgetModuleExportable(this.widgetName, { + body, + imports, + }); + } +} + +export class ReactInlineCssWidgetModuleExportable extends ReactWidgetModuleExportable { + constructor( + name, + { + body, + imports, + }: { + body: BlockStatement; + imports: ImportDeclaration[]; + } + ) { + super({ + name, + body, + imports, + }); + } + + asFile({ + exporting, + }: { + exporting: react_config.ReactComponentExportingCofnig; + }) { + return makeReactModuleFile({ + name: this.name, + path: "src/components", + imports: this.imports, + declarations: [], + body: this.body, + config: { + exporting: exporting, + }, + }); + } +} diff --git a/packages/builder-web-react/react-module-file/index.ts b/packages/builder-web-react/react-module-file/index.ts index 76ff02c6..ba023f33 100644 --- a/packages/builder-web-react/react-module-file/index.ts +++ b/packages/builder-web-react/react-module-file/index.ts @@ -1,7 +1,111 @@ -import { SourceFile } from "coli"; +import { + SourceFile, + BlockStatement, + FunctionDeclaration, + Import, + ImportDeclaration, + Return, + Declaration, +} from "coli"; +import { SyntaxKind } from "@coli.codes/core-syntax-kind"; +import { react as react_config } from "@designto/config"; +import { + add_export_keyword_modifier_to_declaration, + wrap_with_export_assignment_react_component_identifier, +} from "../react-component-exporting"; export class ReactModuleFile extends SourceFile { constructor({ name, path }: { name: string; path: string }) { super({ name, path }); } } + +export function makeReactModuleFile({ + name, + path, + imports, + body, + declarations, + config, +}: { + name: string; + path: string; + imports: ImportDeclaration[]; + body: BlockStatement; + declarations: Declaration[]; + config: { + exporting: react_config.ReactComponentExportingCofnig; + }; +}) { + const { exporting } = config; + const file = new ReactModuleFile({ + name: `${name}.tsx`, + path: path, + }); + file.imports(...imports); + + // console.log("exporting", exporting); + switch (exporting.type) { + case "export-default-anonymous-functional-component": { + // exporting.declaration_syntax_choice; + // exporting.export_declaration_syntax_choice; + // exporting.exporting_position; + + const export_default_anaonymous_functional_component = + new FunctionDeclaration(undefined, { + body: body, + modifiers: { + default: SyntaxKind.DefaultKeyword, + export: SyntaxKind.ExportKeyword, + }, + }); + file.declare(export_default_anaonymous_functional_component); + file.declare(...declarations); + break; + } + case "export-named-functional-component": { + // exporting.declaration_syntax_choice; + // exporting.export_declaration_syntax_choice; + + const named_function_declaration = new FunctionDeclaration(name, { + body: body, + }); + + switch (exporting.exporting_position) { + case "after-declaration": + file.declare(named_function_declaration); + file.export( + wrap_with_export_assignment_react_component_identifier( + named_function_declaration.id + ) + ); + file.declare(...declarations); + break; + case "end-of-file": + file.declare(named_function_declaration); + file.declare(...declarations); + file.export( + wrap_with_export_assignment_react_component_identifier( + named_function_declaration.id + ) + ); + break; + case "with-declaration": + const _exported_named_function_declaration = + add_export_keyword_modifier_to_declaration( + named_function_declaration + ); + file.declare(_exported_named_function_declaration); + file.declare(...declarations); + break; + } + break; + } + case "export-named-class-component": + break; + case "export-anonymous-class-component": + throw new Error("Class component not supported"); + } + + return file; +} diff --git a/packages/builder-web-react/react-module/index.ts b/packages/builder-web-react/react-module/index.ts new file mode 100644 index 00000000..8fc1c8f5 --- /dev/null +++ b/packages/builder-web-react/react-module/index.ts @@ -0,0 +1 @@ +export * from "./react-exportable-module-declaration"; diff --git a/packages/builder-web-react/react-module/react-exportable-module-declaration.ts b/packages/builder-web-react/react-module/react-exportable-module-declaration.ts new file mode 100644 index 00000000..9563256a --- /dev/null +++ b/packages/builder-web-react/react-module/react-exportable-module-declaration.ts @@ -0,0 +1,46 @@ +import { BlockStatement, ImportDeclaration } from "coli"; +import { stringfy } from "@coli.codes/export-string"; +import { ReactModuleFile } from "../react-module-file"; +import { react as react_config } from "@designto/config"; + +export abstract class ReactWidgetModuleExportable { + readonly name: string; + readonly dependencies: string[]; + readonly body: BlockStatement; + readonly imports: ImportDeclaration[]; + + constructor({ + name, + body, + imports, + dependencies, + }: { + name; + body; + imports; + dependencies?: string[]; + }) { + this.name = name; + this.body = body; + this.imports = imports; + this.dependencies = dependencies; + } + + abstract asFile({ + exporting, + }: { + exporting: react_config.ReactComponentExportingCofnig; + }): ReactModuleFile; + + finalize(config: react_config.ReactComponentExportingCofnig) { + const file = this.asFile({ exporting: config }); + const final = stringfy(file.blocks, { + language: "tsx", + }); + return { + code: final, + name: this.name, + dependencies: this.dependencies, + }; + } +} diff --git a/packages/builder-web-react/react-styled-component-widget/from-reusable-widget-tree.ts b/packages/builder-web-react/react-styled-component-widget/from-reusable-widget-tree.ts index 0a070d79..8c633f84 100644 --- a/packages/builder-web-react/react-styled-component-widget/from-reusable-widget-tree.ts +++ b/packages/builder-web-react/react-styled-component-widget/from-reusable-widget-tree.ts @@ -31,9 +31,7 @@ export function finalizeReactReusable_StyledComponents__Experimental({ }; const token = hanlde(tree); - console.log("from-reusable-widget-tree::token", { token, tree }); const webwi = buildWebWidgetFromTokens(token, {}); - console.log("from-reusable-widget-tree::web-widget", webwi); const builder = new ReactStyledComponentsBuilder({ entry: webwi, config: { diff --git a/packages/builder-web-react/react-styled-component-widget/from-vanilla-widget-tree.ts b/packages/builder-web-react/react-styled-component-widget/from-static-widget-tree.ts similarity index 79% rename from packages/builder-web-react/react-styled-component-widget/from-vanilla-widget-tree.ts rename to packages/builder-web-react/react-styled-component-widget/from-static-widget-tree.ts index 54ebd17e..b9f4da3a 100644 --- a/packages/builder-web-react/react-styled-component-widget/from-vanilla-widget-tree.ts +++ b/packages/builder-web-react/react-styled-component-widget/from-static-widget-tree.ts @@ -1,6 +1,6 @@ import { JsxWidget, StylableJsxWidget } from "@web-builder/core"; import { ReactComponentExportResult } from "../export-result"; -import { react } from "@designto/config"; +import { react as react_config } from "@designto/config"; import { ReactStyledComponentsBuilder } from "./react-styled-components-module-builder"; /** @@ -15,8 +15,8 @@ export function finalizeReactWidget_StyledComponents( styling, exporting, }: { - styling: react.ReactStyledComponentsConfig; - exporting: react.ReactComponentExportingCofnig; + styling: react_config.ReactStyledComponentsConfig; + exporting: react_config.ReactComponentExportingCofnig; } ): ReactComponentExportResult { const builder = new ReactStyledComponentsBuilder({ entry, config: styling }); diff --git a/packages/builder-web-react/react-styled-component-widget/index.ts b/packages/builder-web-react/react-styled-component-widget/index.ts index 14097a8c..f260f304 100644 --- a/packages/builder-web-react/react-styled-component-widget/index.ts +++ b/packages/builder-web-react/react-styled-component-widget/index.ts @@ -1,2 +1,2 @@ -export * from "./from-vanilla-widget-tree"; +export * from "./from-static-widget-tree"; export * from "./from-reusable-widget-tree"; diff --git a/packages/builder-web-react/react-styled-component-widget/react-styled-components-module-builder.ts b/packages/builder-web-react/react-styled-component-widget/react-styled-components-module-builder.ts index deac26bd..8dcd95d8 100644 --- a/packages/builder-web-react/react-styled-component-widget/react-styled-components-module-builder.ts +++ b/packages/builder-web-react/react-styled-component-widget/react-styled-components-module-builder.ts @@ -1,54 +1,35 @@ -import { stringfy } from "@coli.codes/export-string"; import { ScopedVariableNamer } from "@coli.codes/naming"; import { ReservedKeywordPlatformPresets } from "@coli.codes/naming/reserved"; import { NoStyleJSXElementConfig, StyledComponentJSXElementConfig, } from "@web-builder/styled"; -import { - BlockStatement, - FunctionDeclaration, - Import, - ImportDeclaration, - JSXClosingElement, - JSXElement, - JSXOpeningElement, - Return, -} from "coli"; +import { BlockStatement, Import, ImportDeclaration, Return } from "coli"; import { react_imports } from "../react-import-specifications"; -import { - TextChildWidget, - StylableJsxWidget, - JsxWidget, -} from "@web-builder/core"; +import { JsxWidget } from "@web-builder/core"; import { buildJsx, getWidgetStylesConfigMap, WidgetStyleConfigMap, } from "@web-builder/core/builders"; -import { - add_export_keyword_modifier_to_declaration, - wrap_with_export_assignment_react_component_identifier, -} from "../react-component-exporting"; -import { react } from "@designto/config"; -import { ReactModuleFile } from "../react-module-file"; +import { react as react_config } from "@designto/config"; +import { makeReactModuleFile, ReactModuleFile } from "../react-module-file"; import { StyledComponentDeclaration } from "@web-builder/styled/styled-component-declaration"; -import { SyntaxKind } from "@coli.codes/core-syntax-kind"; -import { handle } from "@coli.codes/builder"; +import { ReactWidgetModuleExportable } from "../react-module"; export class ReactStyledComponentsBuilder { private readonly entry: JsxWidget; private readonly widgetName: string; private readonly styledConfigWidgetMap: WidgetStyleConfigMap; private readonly namer: ScopedVariableNamer; - readonly config: react.ReactStyledComponentsConfig; + readonly config: react_config.ReactStyledComponentsConfig; constructor({ entry, config, }: { entry: JsxWidget; - config: react.ReactStyledComponentsConfig; + config: react_config.ReactStyledComponentsConfig; }) { this.entry = entry; this.widgetName = entry.key.name; @@ -70,9 +51,15 @@ export class ReactStyledComponentsBuilder { } private jsxBuilder(widget: JsxWidget) { - return buildJsx(widget, { - styledConfig: (id) => this.styledConfig(id), - }); + return buildJsx( + widget, + { + styledConfig: (id) => this.styledConfig(id), + }, + { + self_closing_if_possible: true, + } + ); } partImports() { @@ -131,11 +118,7 @@ export class ReactStyledComponentsBuilder { } } -export class ReactStyledComponentWidgetModuleExportable { - readonly name: string; - readonly dependencies: string[]; - readonly body: BlockStatement; - readonly imports: ImportDeclaration[]; +export class ReactStyledComponentWidgetModuleExportable extends ReactWidgetModuleExportable { readonly declarations: StyledComponentDeclaration[]; constructor( @@ -155,96 +138,29 @@ export class ReactStyledComponentWidgetModuleExportable { dependencies?: string[]; } ) { - this.name = name; - this.body = body; - this.imports = imports; - this.declarations = declarations; - - this.dependencies = dependencies; - } - - asFile({ exporting }: { exporting: react.ReactComponentExportingCofnig }) { - const file = new ReactModuleFile({ - name: `${this.name}.tsx`, - path: "src/components", + super({ + name, + body, + imports, }); - file.imports(...this.imports); - - // console.log("exporting", exporting); - switch (exporting.type) { - case "export-default-anonymous-functional-component": { - // exporting.declaration_syntax_choice; - // exporting.export_declaration_syntax_choice; - // exporting.exporting_position; - - const export_default_anaonymous_functional_component = - new FunctionDeclaration(undefined, { - body: this.body, - modifiers: { - default: SyntaxKind.DefaultKeyword, - export: SyntaxKind.ExportKeyword, - }, - }); - file.declare(export_default_anaonymous_functional_component); - file.declare(...this.declarations); - break; - } - case "export-named-functional-component": { - // exporting.declaration_syntax_choice; - // exporting.export_declaration_syntax_choice; - - const named_function_declaration = new FunctionDeclaration(this.name, { - body: this.body, - }); - - switch (exporting.exporting_position) { - case "after-declaration": - file.declare(named_function_declaration); - file.export( - wrap_with_export_assignment_react_component_identifier( - named_function_declaration.id - ) - ); - file.declare(...this.declarations); - break; - case "end-of-file": - file.declare(named_function_declaration); - file.declare(...this.declarations); - file.export( - wrap_with_export_assignment_react_component_identifier( - named_function_declaration.id - ) - ); - break; - case "with-declaration": - const _exported_named_function_declaration = - add_export_keyword_modifier_to_declaration( - named_function_declaration - ); - file.declare(_exported_named_function_declaration); - file.declare(...this.declarations); - break; - } - break; - } - case "export-named-class-component": - break; - case "export-anonymous-class-component": - throw new Error("Class component not supported"); - } - return file; + this.declarations = declarations; } - finalize(config: react.ReactComponentExportingCofnig) { - const file = this.asFile({ exporting: config }); - const final = stringfy(file.blocks, { - language: "tsx", - }); - return { - code: final, + asFile({ + exporting, + }: { + exporting: react_config.ReactComponentExportingCofnig; + }) { + return makeReactModuleFile({ name: this.name, - dependencies: this.dependencies, - }; + path: "src/components", + imports: this.imports, + declarations: this.declarations, + body: this.body, + config: { + exporting: exporting, + }, + }); } } diff --git a/packages/builder-web-vanilla/export-inline-css-html-file/index.ts b/packages/builder-web-vanilla/export-inline-css-html-file/index.ts index 4ecb4544..e07ae79f 100644 --- a/packages/builder-web-vanilla/export-inline-css-html-file/index.ts +++ b/packages/builder-web-vanilla/export-inline-css-html-file/index.ts @@ -73,10 +73,16 @@ export function export_inlined_css_html_file( } function buildBodyHtml(widget: JsxWidget) { - return buildJsx(widget, { - styledConfig: (id) => getStyleConfigById(id), - idTransformer: (jsx, id) => injectIdToJsx(jsx, id), - }); + return buildJsx( + widget, + { + styledConfig: (id) => getStyleConfigById(id), + idTransformer: (jsx, id) => injectIdToJsx(jsx, id), + }, + { + self_closing_if_possible: false, + } + ); } const css_declarations = Array.from(styles_map.keys()) diff --git a/packages/designto-code/universal/design-to-code.ts b/packages/designto-code/universal/design-to-code.ts index b8c6d0af..582e3692 100644 --- a/packages/designto-code/universal/design-to-code.ts +++ b/packages/designto-code/universal/design-to-code.ts @@ -31,7 +31,7 @@ export type Result = output.ICodeOutput & { widget: Widget }; export async function designToCode({ input, - framework, + framework: framework_config, asset_config, build_config = config.default_build_configuration, }: { @@ -42,12 +42,12 @@ export async function designToCode({ }): Promise { assert(input, "input is required"); if (process.env.NODE_ENV === "development") { - if (framework.framework == "vanilla") { + if (framework_config.framework == "vanilla") { } else { console.info( "dev: starting designtocode with user input", input, - framework, + framework_config, build_config, asset_config ); @@ -89,7 +89,6 @@ export async function designToCode({ entry: vanilla_token, repository: input.repository, }); - console.log("reusable_widget_tree", reusable_widget_tree); // TODO: WIP } catch (_) { console.error("error while building reusable widget tree.", _); @@ -101,13 +100,13 @@ export async function designToCode({ reusable_widget_tree: reusable_widget_tree, }; - switch (framework.framework) { + switch (framework_config.framework) { case "vanilla": return { ...(await designToVanilla({ input: _tokenized_widget_input, build_config: build_config, - vanilla_config: framework, + vanilla_config: framework_config, asset_config: asset_config, })), ..._tokenized_widget_input, @@ -117,7 +116,7 @@ export async function designToCode({ ...(await designToReact({ input: _tokenized_widget_input, build_config: build_config, - react_config: framework, + react_config: framework_config, asset_config: asset_config, })), ..._tokenized_widget_input, @@ -127,13 +126,13 @@ export async function designToCode({ ...(await designToFlutter({ input: _tokenized_widget_input, build_config: build_config, - flutter_config: framework, + flutter_config: framework_config, asset_config: asset_config, })), ..._tokenized_widget_input, }; } - throw `The framework "${framework}" is not supported at this point.`; + throw `The framework "${framework_config}" is not supported at this point.`; return; } diff --git a/packages/designto-react/app/index.ts b/packages/designto-react/app/index.ts index 3a5994f7..7629b57b 100644 --- a/packages/designto-react/app/index.ts +++ b/packages/designto-react/app/index.ts @@ -2,6 +2,7 @@ import { Widget } from "@reflect-ui/core"; import { buildWebWidgetFromTokens } from "@designto/web/tokens-to-web-widget"; import { finalizeReactWidget_StyledComponents, + finalizeReactWidget_InlineCss, finalizeReactReusable_StyledComponents__Experimental, JsxWidget, } from "@web-builder/react"; @@ -24,10 +25,24 @@ export function buildReactApp( scaffold: { raw: res.code }, }; } + case "inline-css": { + const res = finalizeReactWidget_InlineCss(entry, { + styling: config.styling, + exporting: config.component_declaration_style.exporting_style, + }); + return { + id: entry.key.id, + name: entry.key.name, + code: { raw: res.code }, + scaffold: { raw: res.code }, + }; + break; + } case "css": - case "css-in-jsx": + default: { throw new Error(`${config.styling.type} not supported yet`); break; + } } } diff --git a/yarn.lock b/yarn.lock index b4937a98..293ab544 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6104,6 +6104,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/camelcase-css@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/camelcase-css/-/camelcase-css-2.0.0.tgz#9604ffb754861b348e1f361c40fbf4d94d43b890" + integrity sha512-HSD3U+7+1BneOuIcvn3SUJHmEhPZGM6h5wUBQ3L7LL8VAIx2THIlN8HMH2ylxF4yuxu93UAFTcmeb+MT2Dozkg== + "@types/clone@2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/clone/-/clone-2.1.1.tgz#9b880d0ce9b1f209b5e0bd6d9caa38209db34024" @@ -8174,7 +8179,7 @@ camel-case@^4.1.1: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase-css@2.0.1: +camelcase-css@2.0.1, camelcase-css@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==