diff --git a/editor/core/states/editor-state.ts b/editor/core/states/editor-state.ts index e0441504..7d80175e 100644 --- a/editor/core/states/editor-state.ts +++ b/editor/core/states/editor-state.ts @@ -1,4 +1,5 @@ import type { ReflectSceneNode } from "@design-sdk/figma-node"; +import { ComponentNode } from "@design-sdk/figma-types"; import { DesignInput } from "@designto/config/input"; export interface EditorState { @@ -15,7 +16,7 @@ export interface EditorSnapshot { design: FigmaReflectRepository; } -interface FigmaReflectRepository { +export interface FigmaReflectRepository { /** * fileid; filekey */ @@ -23,5 +24,7 @@ interface FigmaReflectRepository { // TODO: pages: { id: string; name: string; children: ReflectSceneNode[] }[]; + components: { [key: string]: ComponentNode }; + // styles: { [key: string]: {} }; input: DesignInput; } diff --git a/editor/core/states/workspace-initial-state.ts b/editor/core/states/workspace-initial-state.ts index a54976be..b65a3304 100644 --- a/editor/core/states/workspace-initial-state.ts +++ b/editor/core/states/workspace-initial-state.ts @@ -12,8 +12,9 @@ export function createInitialWorkspaceState( return { history: createInitialHistoryState(editor), preferences: { - debug_mode: false, - enable_preview_feature_components_support: false, + // TODO: temporarily always true for components dev + debug_mode: true, + enable_preview_feature_components_support: true, preview_runner_framework_config: vanilla_presets.vanilla_default, framework_config: react_presets.react_default, }, @@ -24,8 +25,8 @@ export function createPendingWorkspaceState(): WorkspaceState { return { history: createPendingHistoryState(), preferences: { - debug_mode: false, - enable_preview_feature_components_support: false, + debug_mode: null, + enable_preview_feature_components_support: null, preview_runner_framework_config: null, framework_config: null, }, diff --git a/editor/layouts/panel/workspace-bottom-panel-dock-layout.tsx b/editor/layouts/panel/workspace-bottom-panel-dock-layout.tsx index 7c1e53d6..989924ee 100644 --- a/editor/layouts/panel/workspace-bottom-panel-dock-layout.tsx +++ b/editor/layouts/panel/workspace-bottom-panel-dock-layout.tsx @@ -40,9 +40,7 @@ export function WorkspaceBottomPanelDockLayout(props: { } const DockRootWrap = styled.div` - border: solid #d2d2d2; align-self: stretch; - border-width: 1px; display: flex; flex-direction: row; `; diff --git a/editor/pages/figma/inspect-component.tsx b/editor/pages/figma/inspect-component.tsx index fe5ea97e..b32be44f 100644 --- a/editor/pages/figma/inspect-component.tsx +++ b/editor/pages/figma/inspect-component.tsx @@ -87,26 +87,22 @@ export default function InspectComponent() { -
+ {/*
-
-
+
*/} + {/*
-
+
*/}
- {/*
- - -
*/} {/*
diff --git a/editor/pages/files/[key]/[id].tsx b/editor/pages/files/[key]/[id].tsx index e83d17c2..39bc740b 100644 --- a/editor/pages/files/[key]/[id].tsx +++ b/editor/pages/files/[key]/[id].tsx @@ -56,15 +56,8 @@ export default function Page() { if (file) { let val: EditorSnapshot; - const pages = file.document.children.map((page) => ({ - id: page.id, - name: page.name, - children: page["children"]?.map((child) => { - const _mapped = mapper.mapFigmaRemoteToFigma(child); - return convert.intoReflectNode(_mapped); - }), - type: "design", - })); + const components = warmup.componentsFrom(file); + const pages = warmup.pagesFrom(file); if (prevstate) { val = { @@ -102,6 +95,7 @@ export default function Page() { input: null, key: filekey, pages: pages, + components: components, }, selectedPage: warmup.selectedPage(prevstate, pages, null), }; diff --git a/editor/pages/files/[key]/index.tsx b/editor/pages/files/[key]/index.tsx index eae514c3..de184686 100644 --- a/editor/pages/files/[key]/index.tsx +++ b/editor/pages/files/[key]/index.tsx @@ -5,8 +5,7 @@ import { Editor, EditorDefaultProviders } from "scaffolds/editor"; import { EditorSnapshot, StateProvider } from "core/states"; import { WorkspaceAction } from "core/actions"; import { useDesignFile } from "hooks"; -import { convert } from "@design-sdk/figma-node-conversion"; -import { mapper } from "@design-sdk/figma-remote"; + import { warmup } from "scaffolds/editor"; export default function FileEntryEditor() { @@ -31,15 +30,11 @@ export default function FileEntryEditor() { if (file) { let val: EditorSnapshot; - const pages = file.document.children.map((page) => ({ - id: page.id, - name: page.name, - children: page["children"]?.map((child) => { - const _mapped = mapper.mapFigmaRemoteToFigma(child); - return convert.intoReflectNode(_mapped); - }), - type: "design", - })); + // TODO: seed this as well + // ->> file.styles; + + const components = warmup.componentsFrom(file); + const pages = warmup.pagesFrom(file); if (prevstate) { val = { @@ -60,6 +55,8 @@ export default function FileEntryEditor() { selectedLayersOnPreview: [], design: { input: null, + components: components, + // styles: null, key: filekey, pages: pages, }, @@ -75,7 +72,6 @@ export default function FileEntryEditor() { }, [filekey, file?.document?.children]); const safe_value = warmup.safestate(initialState); - return ( diff --git a/editor/pages/to-code/index.tsx b/editor/pages/to-code/index.tsx index 0adaa184..2770eaae 100644 --- a/editor/pages/to-code/index.tsx +++ b/editor/pages/to-code/index.tsx @@ -69,15 +69,8 @@ export default function Page() { if (file) { let val: EditorSnapshot; - const pages = file.document.children.map((page) => ({ - id: page.id, - name: page.name, - children: page["children"]?.map((child) => { - const _mapped = mapper.mapFigmaRemoteToFigma(child); - return convert.intoReflectNode(_mapped); - }), - type: "design", - })); + const components = warmup.componentsFrom(file); + const pages = warmup.pagesFrom(file); if (prevstate) { val = { @@ -115,6 +108,7 @@ export default function Page() { input: null, key: _input.file, pages: pages, + components: components, }, selectedPage: warmup.selectedPage(prevstate, pages, null), }; diff --git a/editor/scaffolds/editor/editor.tsx b/editor/scaffolds/editor/editor.tsx index cc010dde..89f3e054 100644 --- a/editor/scaffolds/editor/editor.tsx +++ b/editor/scaffolds/editor/editor.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import styled from "@emotion/styled"; import { useRouter } from "next/router"; import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout"; @@ -19,7 +19,6 @@ import { ImageRepository, MainImageRepository, } from "@design-sdk/core/assets-repository"; -import { personal } from "@design-sdk/figma-auth-store"; import { useFigmaAccessToken } from "hooks"; import { get_framework_config } from "query/to-code-options-from-query"; import { CodeOptionsControl } from "components/codeui-code-options-control"; @@ -31,8 +30,8 @@ import { } from "utils/design-query"; import { vanilla_presets } from "@grida/builder-config-preset"; import { EditorSkeleton } from "./skeleton"; -import { MonacoEmptyMock } from "components/code-editor/monaco-mock-empty"; import { colors } from "theme"; +import Link from "next/link"; export function Editor() { const router = useRouter(); @@ -61,7 +60,17 @@ export function Editor() { find_node_by_id_under_inpage_nodes(targetId, thisPageNodes) || null; const root = thisPageNodes - ? container_of_target && DesignInput.fromDesign(container_of_target) + ? container_of_target && + (container_of_target.origin === "COMPONENT" + ? DesignInput.forMasterComponent({ + master: container_of_target, + all: state.design.pages, + components: state.design.components, + }) + : DesignInput.fromDesignWithComponents({ + design: container_of_target, + components: state.design.components, + })) : state.design?.input; const targetted = @@ -129,12 +138,14 @@ export function Editor() { }).then(on_result); // build final code with asset fetch - designToCode({ - input: root, - framework: framework_config, - asset_config: { asset_repository: MainImageRepository.instance }, - build_config: build_config, - }).then(on_result); + if (!MainImageRepository.instance.empty) { + designToCode({ + input: root, + framework: framework_config, + asset_config: { asset_repository: MainImageRepository.instance }, + build_config: build_config, + }).then(on_result); + } } }, [targetted?.id, framework_config?.framework]); @@ -168,12 +179,14 @@ export function Editor() { }, }).then(on_preview_result); - designToCode({ - input: root, - build_config: build_config, - framework: vanilla_presets.vanilla_default, - asset_config: { asset_repository: MainImageRepository.instance }, - }).then(on_preview_result); + if (!MainImageRepository.instance.empty) { + designToCode({ + input: root, + build_config: build_config, + framework: vanilla_presets.vanilla_default, + asset_config: { asset_repository: MainImageRepository.instance }, + }).then(on_preview_result); + } } }, [targetted?.id] @@ -246,43 +259,14 @@ export function Editor() { {wstate.preferences.debug_mode && ( - -
-
- -
- {(root.entry.origin === "INSTANCE" || - root.entry.origin === "COMPONENT") && ( - - )} -
- -
- -
-
- -
-
+ +
)} @@ -292,6 +276,57 @@ export function Editor() { ); } +const Debugger = ({ + id, + file, + type, + entry, + widget, +}: { + type: string; + id: string; + file: string; + entry: any; + widget: any; +}) => { + const router = useRouter(); + + return ( +
+
+ +
+ {(type === "INSTANCE" || type === "COMPONENT") && ( + + inspect component + + )} +
+ +
+ +
+
+ +
+
+ ); +}; + const CodeEditorContainer = styled.div` display: flex; flex-direction: column; diff --git a/editor/scaffolds/editor/warmup.ts b/editor/scaffolds/editor/warmup.ts index 2b0994a8..906f57bd 100644 --- a/editor/scaffolds/editor/warmup.ts +++ b/editor/scaffolds/editor/warmup.ts @@ -1,6 +1,7 @@ import { createPendingWorkspaceState, EditorSnapshot, + FigmaReflectRepository, WorkspaceState, } from "core/states"; import { createInitialWorkspaceState } from "core/states"; @@ -9,6 +10,10 @@ import { PendingState } from "core/utility-types"; import { DesignInput } from "@designto/config/input"; import { TargetNodeConfig } from "query/target-node"; import { WorkspaceAction } from "core/actions"; +import { FileResponse } from "@design-sdk/figma-remote-types"; +import { convert } from "@design-sdk/figma-node-conversion"; +import { mapper } from "@design-sdk/figma-remote"; +import { find, visit } from "tree-visit"; const pending_workspace_state = createPendingWorkspaceState(); // @@ -38,6 +43,61 @@ export function initialReducer( } } +export function pagesFrom(file: FileResponse): FigmaReflectRepository["pages"] { + return file.document.children.map((page) => ({ + id: page.id, + name: page.name, + children: page["children"]?.map((child) => { + const _mapped = mapper.mapFigmaRemoteToFigma(child); + return convert.intoReflectNode(_mapped); + }), + type: "design", + })); +} + +/** + * only fetch in-file components. components from shared-library (external file) won't be loaded. + * @param file + * @returns + */ +export function componentsFrom( + file: FileResponse +): FigmaReflectRepository["components"] { + const tomap = (a, v) => ({ ...a, [v.id]: v }); + + // only fetch in-file components. components from shared-library (external file) won't be loaded. + const components_in_file = []; + visit<{ id: string; type: string }>(file.document, { + getChildren: (node) => { + if ("children" in node) return node["children"]; + return []; + }, + onEnter: (node) => { + if (node["type"] == "COMPONENT") { + components_in_file.push(node); + } + }, + }); + + // return components_in_file.reduce(tomap, {}); + + return Object.keys(file.components) + .map((k) => { + const id = k; + const meta = file.components[k]; + const master = components_in_file.find((c) => c.id === id); + if (!master) return; + return { + key: meta.key, // only available with api response. the hash key of current version of component for another api call. (not used) + id: master.id, + name: master.name, + ...master, + }; + }) + .filter((c) => c) + .reduce(tomap, {}); +} + export function initializeDesign(design: TargetNodeConfig): EditorSnapshot { return { selectedNodes: [design.node], @@ -45,6 +105,8 @@ export function initializeDesign(design: TargetNodeConfig): EditorSnapshot { selectedPage: null, design: { pages: [], + components: null, + // styles: null, key: design.file, input: DesignInput.fromApiResponse({ ...design, diff --git a/externals/coli b/externals/coli index 83d3cb54..7beb540c 160000 --- a/externals/coli +++ b/externals/coli @@ -1 +1 @@ -Subproject commit 83d3cb546b753859cb23ef2a79346ac438aae563 +Subproject commit 7beb540c885c7b40bf163004a182d60fbdf001ce diff --git a/externals/design-sdk b/externals/design-sdk index 34a45ef2..54d7aae0 160000 --- a/externals/design-sdk +++ b/externals/design-sdk @@ -1 +1 @@ -Subproject commit 34a45ef238a9fbc635cfdf5f66baab77c7a0cab9 +Subproject commit 54d7aae0b6b1b41ca7de23f063f09de2c32e3fc0 diff --git a/packages/builder-config/input/design-input.ts b/packages/builder-config/input/design-input.ts index f2e971b2..e1966a43 100644 --- a/packages/builder-config/input/design-input.ts +++ b/packages/builder-config/input/design-input.ts @@ -1,6 +1,7 @@ import type { ReflectSceneNode } from "@design-sdk/core"; import { mapGrandchildren } from "@design-sdk/core/utils"; import { NodeRepository } from "@design-sdk/figma"; +import type { ComponentNode } from "@design-sdk/figma"; import { RawNodeResponse } from "@design-sdk/figma-remote"; export interface IDesignInput { @@ -35,20 +36,50 @@ export class DesignInput implements IDesignInput { } static fromDesign(design: ReflectSceneNode): DesignInput { - const _allnodes = mapGrandchildren(design, 0, { - includeThis: true, - ignoreGroup: false, + const repository = new NodeRepository({ + // components not supported for `fromdesign` + components: null, + nodes: this._flat_all(design), }); + return new DesignInput({ entry: design, repository: repository }); + } + + static fromDesignWithComponents({ + design, + components, + }: { + design: ReflectSceneNode; + components: { [key: string]: ComponentNode } | ComponentNode[]; + }) { const repository = new NodeRepository({ - // TODO: components not supported for `fromdesign` - components: [], - nodes: [...(_allnodes as any)], + components: Object.values(components), + nodes: this._flat_all(design), }); return new DesignInput({ entry: design, repository: repository }); } + static forMasterComponent({ + all, + master, + components, + }: { + /** + * usually pages. Document#pages + */ + all: { id: string; name: string; children: ReflectSceneNode[] }[]; + master: ReflectSceneNode; + components: { [key: string]: ComponentNode } | ComponentNode[]; + }) { + const repository = new NodeRepository({ + components: Object.values(components), + nodes: all.map((p) => p.children.map(this._flat_all).flat()).flat(), + }); + + return new DesignInput({ entry: master, repository: repository }); + } + static fromApiResponse({ raw, entry, @@ -56,15 +87,17 @@ export class DesignInput implements IDesignInput { raw: RawNodeResponse; entry: ReflectSceneNode; }): DesignInput { - const _allnodes = mapGrandchildren(entry, 0, { - includeThis: true, - ignoreGroup: false, - }); - const repository = new NodeRepository({ components: [...(Object.values(raw.components) as any)], - nodes: [...(_allnodes as any)], + nodes: this._flat_all(entry), }); return new DesignInput({ entry: entry, repository: repository }); } + + private static _flat_all(entry) { + return mapGrandchildren(entry, 0, { + includeThis: true, + ignoreGroup: false, + }); + } } diff --git a/packages/builder-web-core/widget-instanciation/index.ts b/packages/builder-web-core/widget-instanciation/index.ts index 8a005e53..f615e97e 100644 --- a/packages/builder-web-core/widget-instanciation/index.ts +++ b/packages/builder-web-core/widget-instanciation/index.ts @@ -1,20 +1,53 @@ import { WidgetKey } from "../widget-key"; import { JSXElementConfig, JsxWidget } from "../widget-core"; -import { JSX } from "coli"; +import { JSX, JSXAttribute, Types } from "coli"; export class InstanciationElement extends JsxWidget { readonly identifier: string; - // TODO: support arguments + readonly arguments: { [key: string]: any }; - constructor({ key, identifier }: { key: WidgetKey; identifier: string }) { + constructor({ + key, + identifier, + arguments: _arguments, + }: { + key: WidgetKey; + identifier: string; + arguments: { [key: string]: any }; + }) { super({ key }); this.identifier = identifier; + this.arguments = _arguments ?? {}; } jsxConfig(): JSXElementConfig { + const _attrs = this.makeJsxAttributes(); return { type: "tag-and-attr", tag: JSX.identifier(this.identifier), + attributes: _attrs, }; } + + private makeJsxAttributes() { + const jsxValue = (value: any) => { + switch (typeof value) { + case "undefined": + return JSX.exp(undefined); + case "string": + return JSX.text(value, "template-literal"); + case "number": + return JSX.number(value); + default: + console.error(`Unsupported type of value: ${typeof value}`); + } + }; + + return Object.keys(this.arguments) + .map((key, index) => { + const rec = this.arguments[key]; + return new JSXAttribute(rec.key, jsxValue(rec.value)); + }) + .filter((a) => a); + } } 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 082cfdac..1d1a2811 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,9 @@ export function finalizeReactReusable_StyledComponents__Experimental({ }; const token = hanlde(tree); - console.log("token", token); + console.log("from-reusable-widget-tree::token", { token, tree }); const webwi = buildWebWidgetFromTokens(token); - console.log("webwi", webwi); + 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/react-styled-components-module-builder.ts b/packages/builder-web-react/react-styled-component-widget/react-styled-components-module-builder.ts index aab80cac..deac26bd 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 @@ -106,9 +106,9 @@ export class ReactStyledComponentsBuilder { partDeclarations() { return Array.from(this.styledConfigWidgetMap.keys()) .map((k) => { - return (this.styledConfigWidgetMap.get( - k - ) as StyledComponentJSXElementConfig).styledComponent; + return ( + this.styledConfigWidgetMap.get(k) as StyledComponentJSXElementConfig + ).styledComponent; }) .filter((s) => s); } @@ -170,23 +170,21 @@ export class ReactStyledComponentWidgetModuleExportable { }); file.imports(...this.imports); - console.log("exporting", exporting); + // 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, - { + 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; @@ -219,9 +217,10 @@ export class ReactStyledComponentWidgetModuleExportable { ); break; case "with-declaration": - const _exported_named_function_declaration = add_export_keyword_modifier_to_declaration( - named_function_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; diff --git a/packages/designto-code/universal/design-to-code.ts b/packages/designto-code/universal/design-to-code.ts index c6ab27c8..93d1ef93 100644 --- a/packages/designto-code/universal/design-to-code.ts +++ b/packages/designto-code/universal/design-to-code.ts @@ -37,13 +37,16 @@ export async function designToCode({ asset_config: AssetsConfig; }): Promise { if (process.env.NODE_ENV === "development") { - console.info( - "dev: starting designtocode with user input", - input, - framework, - build_config, - asset_config - ); + if (framework.framework == "vanilla") { + } else { + console.info( + "dev: starting designtocode with user input", + input, + framework, + build_config, + asset_config + ); + } } // post token processing @@ -72,7 +75,7 @@ export async function designToCode({ console.log("reusable_widget_tree", reusable_widget_tree); // TODO: WIP } catch (_) { - console.error(_); + console.error("error while building reusable widget tree.", _); } } @@ -143,6 +146,12 @@ export async function designToReact({ !input.reusable_widget_tree ) { const reactwidget = toreact.buildReactWidget(input.widget); + if (process.env.NODE_ENV === "development") { + console.info("dev::", "final web token composed", { + input: input.widget, + reactwidget, + }); + } const res = toreact.buildReactApp(reactwidget, react_config); // ------------------------------------------------------------------------ diff --git a/packages/designto-react/app/index.ts b/packages/designto-react/app/index.ts index 0104fcd6..214d0ed6 100644 --- a/packages/designto-react/app/index.ts +++ b/packages/designto-react/app/index.ts @@ -33,7 +33,7 @@ export function buildReactApp( export function buildReactWidget(widget: Widget) { assert( - widget, + widget instanceof Widget, "A valid reflect widget manifest should be passed as an input. none was passed." ); diff --git a/packages/designto-token/main.ts b/packages/designto-token/main.ts index aecc5e19..c9a14a00 100644 --- a/packages/designto-token/main.ts +++ b/packages/designto-token/main.ts @@ -163,10 +163,11 @@ function handleNode( } // - button - - const _detect_if_button = detectIf.button(node); - if (_detect_if_button.result) { - return tokenizeButton.fromManifest(node, _detect_if_button.data); - } + // TODO: temporarily disabled - remove comment after button widget is ready + // const _detect_if_button = detectIf.button(node); + // if (_detect_if_button.result) { + // return tokenizeButton.fromManifest(node, _detect_if_button.data); + // } // ------------------------------------------------------------------------- // --------------------------- Detected tokens ----------------------------- diff --git a/packages/designto-web/tokens-to-web-widget/compose-instanciation.ts b/packages/designto-web/tokens-to-web-widget/compose-instanciation.ts index 40c7bd61..cb9e8671 100644 --- a/packages/designto-web/tokens-to-web-widget/compose-instanciation.ts +++ b/packages/designto-web/tokens-to-web-widget/compose-instanciation.ts @@ -1,18 +1,24 @@ import { Composer } from "."; import * as reusable from "@code-features/component/tokens"; import * as web from "@web-builder/core"; +import { nameit, NameCases } from "coli"; export function compose_instanciation( widget: reusable.InstanceWidget, - child_composer: Composer + child_composer: Composer // not used ) { const masterkey = widget.meta.master.key; + + const identifier = nameit(widget.meta.master.key.originName, { + case: NameCases.pascal, + }).name; + return new web.InstanciationElement({ key: { - name: "foo", - id: masterkey.id, + name: "ExampleUsageOf_" + identifier, // FIXME: should not use identifier as name + id: widget.key.id, }, - // TODO: fix this - identifier: "foo", + identifier: identifier, + arguments: widget.meta.arguments, }); } diff --git a/packages/designto-web/tokens-to-web-widget/index.ts b/packages/designto-web/tokens-to-web-widget/index.ts index b7a10482..54d17d52 100644 --- a/packages/designto-web/tokens-to-web-widget/index.ts +++ b/packages/designto-web/tokens-to-web-widget/index.ts @@ -23,10 +23,6 @@ export function buildWebWidgetFromTokens(widget: core.Widget): JsxWidget { is_root: true, }); - if (process.env.NODE_ENV === "development") { - console.info("dev::", "final web token composed", composed); - } - return composed; } @@ -232,6 +228,10 @@ function compose( // end of logic gate // ------------------------------------- else { + if (thisWebWidget) + throw new Error( + "internal error. this final exception gate should not be entered since there is already a composed widget." + ); // todo - handle case more specific thisWebWidget = new web.ErrorWidget({ key: _key, @@ -239,6 +239,7 @@ function compose( widget.key.originName }" type of "${widget._type}" - ${JSON.stringify(widget.key)}`, }); + console.warn("not handled", widget); } // ------------------------------------- // ------------------------------------- diff --git a/packages/support-components/define.ts b/packages/support-components/define.ts index b0cf4d9e..d7656589 100644 --- a/packages/support-components/define.ts +++ b/packages/support-components/define.ts @@ -1,14 +1,20 @@ -import { Figma, ReflectSceneNode } from "@design-sdk/figma"; +import { ComponentNode, Figma, ReflectSceneNode } from "@design-sdk/figma"; import { compare_instance_with_master, - InstanceDiff, + InstanceDiff_1on1, + MultichildDiff, NodeDiff, } from "@design-sdk/diff"; import { ComponentsUsageRepository } from "./components-usage-repository"; -import { MasterComponentMetaToken } from "./tokens/token-master-component"; +import { + MasterComponentMetaToken, + Property, +} from "./tokens/token-master-component"; import { InstanceMetaToken } from "./tokens/token-instance"; import { keyFromNode } from "@designto/token/key"; - +import { NameCases, nameit, ScopedVariableNamer } from "coli"; +import { ReservedKeywordPlatformPresets } from "@coli.codes/naming/reserved"; +import { visit } from "tree-visit"; type IDMappable = | { [key: string]: T; @@ -23,6 +29,25 @@ function findIn(map: IDMappable, id: string) { } } +function findDeepUnderComponent(component: ComponentNode, id: string) { + let found = null; + visit<{ id; children }>(component, { + getChildren: (node) => { + if ("children" in node) { + return node.children; + } + return []; + }, + onEnter: (node) => { + if (node.id === id) { + found = node; + return "stop"; + } + }, + }); + return found; +} + // based on default strategy // WIP @@ -35,75 +60,155 @@ interface Input { references?: Figma.InstanceNode[]; } -interface Definition { +/** + * A single property definition + */ +interface PropertyDefinition { type: string; + /** + * id of the master + */ master: string; + /** + * id of the instance + */ use: string; + /** + * default value from master + */ default_value: string; + /** + * overrided value from instance + */ overrided_value: string; } +/** + * defines properties as array of PropertyDefinition from whole diff data between master/instance + * @param diff + * @returns + */ +function define_props(diff: NodeDiff): PropertyDefinition[] { + if (!diff.diff) return; + const masterId = diff.ids[0]; + const instanceId = diff.ids[1]; + switch (diff.type) { + case "instance-to-master": + return define_props__instance(diff); + case "text-node": + return [ + diff.characters.diff + ? { + type: "text.data", + default_value: diff.characters.values[0], + overrided_value: diff.characters.values[1], + master: masterId, + use: instanceId, + } + : null, + diff.fills.diff + ? { + type: "text.fill", + default_value: JSON.stringify(diff.fills.values[0]), + overrided_value: JSON.stringify(diff.fills.values[1]), + master: masterId, + use: instanceId, + } + : null, + // TODO: add text styles diff support + ].filter((d) => d); + case "multi-child": + return define_props_multichild(diff); + default: + throw "not handled yet - " + diff["type"]; + } +} + +const define_props_multichild = (diff: MultichildDiff) => { + return diff.values + .map((d) => { + return define_props(d); + }) + .flat() + .filter(Boolean); +}; + +const define_props__instance = (diff: InstanceDiff_1on1) => { + return diff.values + .map((d) => { + return define_props(d); + }) + .flat() + .filter(Boolean); +}; + export function make_instance_component_meta({ entry, components }: Input) { + const propertyNamer = new ScopedVariableNamer( + "property", + ReservedKeywordPlatformPresets.universal + ); + const property_meta = overrided_property_meta({ entry, components }); - const define = (diff: NodeDiff): Definition[] | Definition[][] => { - if (diff.diff) { - const master = diff.ids[0]; - const use = diff.ids[1]; - switch (diff.type) { - case "instance-to-master": - return define_instance(diff); - case "text-node": - return [ - diff.characters.diff - ? { - type: "text.data", - default_value: diff.characters.values[0], - overrided_value: diff.characters.values[1], - master: master, - use: use, - } - : null, - ]; - break; + const masterId = property_meta.ids[0]; + const master = findIn(components, masterId); + + const properties = define_props__instance(property_meta).flat(); + + const __name_cache = {}; + /** + * + * @param propertyOriginId - the origin node of the property will be targetted. e.g. in `master(group(text))`, the master's text's id will be used. + * @returns + */ + const get_property_key = (type: string, propertyOriginId: string) => { + const uid = type + "-" + propertyOriginId; + const originNodeName = findDeepUnderComponent( + master, + propertyOriginId + )?.name; + + if (originNodeName) { + const { name, register } = propertyNamer.nameit(originNodeName, { + case: NameCases.camel, + register: false, + }); + if (__name_cache[uid]) { + return __name_cache[uid]; + } else { + __name_cache[uid] = name; + register(); + return name; } } + throw new Error("origin layer does not contain a valid name"); }; - const define_instance = (diff: InstanceDiff) => { - const definitions = diff.values.map((d) => { - return define(d); - }); - return definitions.filter((d) => d) as any; - }; - - const properties = define_instance(property_meta); - - const master = new MasterComponentMetaToken({ - key: keyFromNode(findIn(components, property_meta.ids[0])), + const masterMeta = new MasterComponentMetaToken({ + key: keyFromNode(findIn(components, masterId)), properties: properties.map((p) => { - return { - key: p.type, + return >{ + key: get_property_key(p.type, p.master), type: p.type, defaultValue: p.default_value, link: { type: "design-link", linksto: { type: "path-property-link", - path: "", - property: p.type, + path: p.type, + property: [{ type: "name", value: p.type }], }, // TODO: }, }; }), - child: findIn(components, property_meta.ids[0]), + child: findIn(components, masterId), }); - const entryInstance = new InstanceMetaToken({ - master: master, + const entryInstanceMeta = new InstanceMetaToken({ + master: masterMeta, key: keyFromNode(entry), arguments: properties.reduce(function (result, item, index, array) { result[item.type] = { - key: item.type, + key: get_property_key(item.type, item.master), value: item.overrided_value, }; return result; @@ -111,8 +216,8 @@ export function make_instance_component_meta({ entry, components }: Input) { }); return new ComponentsUsageRepository({ - components: [master], - usage: { [entry.id]: entryInstance }, + components: [masterMeta], + usage: { [entry.id]: entryInstanceMeta }, }); } @@ -120,17 +225,24 @@ function overrided_property_meta({ entry, components }: Input) { if ( // TODO: needs cleanup "origin" in entry - ? ((entry as any) as ReflectSceneNode).origin !== "INSTANCE" + ? (entry as any as ReflectSceneNode).origin !== "INSTANCE" : entry.type !== "INSTANCE" ) { throw new Error("not a instance"); } const _master = findIn(components, entry.mainComponentId); + if (!_master) + throw new Error( + "cannot find master with `mainComponentId` - id " + + entry.mainComponentId + + `\nIn map provided - length of ${components.length}` + ); + const diff = compare_instance_with_master({ instance: entry, master: _master, components: Array.from(Object.values(components)), }); - // TODO: make meta based on diff + // TODO: make meta based on diff `{ diff, ... }` return diff; } diff --git a/packages/support-components/main.ts b/packages/support-components/main.ts index 14db3876..db965e77 100644 --- a/packages/support-components/main.ts +++ b/packages/support-components/main.ts @@ -37,15 +37,16 @@ export function reusable({ components: repository.components, }); - const components = component_use_repository.components.map( - composeComponentMeta - ); + const components = + component_use_repository.components.map(composeComponentMeta); - return { + const _ = { // asumming root is always a multi child widget tree: composeInstanciationTree(entry, repository, component_use_repository), components: components, }; + // console.log("reusable", _); + return _; } function composeInstanciationTree( @@ -56,15 +57,10 @@ function composeInstanciationTree( widget = unwrappedChild(widget); // unwrap child to reach original input. const { key, _type: _widget_type } = widget; const node = repository.get(key.id); - if (!node) { - console.warn( - "node not found", - key, - repository, - "this is a know issue when trying to find a masking group. this will be fixed in the future." - ); - return; - } + + // prettier-ignore + if (!node) { console.warn("node not found", key, repository, "this is a know issue when trying to find a masking group. this will be fixed in the future."); return; } + if (node.origin === "INSTANCE") { const instanceMeta = componentsUsageRepository.getUsageOf(node.id); const instance = new InstanceWidget({ @@ -77,25 +73,25 @@ function composeInstanciationTree( widget instanceof MultiChildRenderObjectWidget && widget.children.length > 0 ) { - return { - ...widget, - children: widget.children.map((c) => { - return composeInstanciationTree( - c, - repository, - componentsUsageRepository - ); - }), - }; - } else if (widget instanceof SingleChildRenderObjectWidget) { - return { - ...widget, - child: composeInstanciationTree( - widget.child, + // @ts-ignore + widget.children = widget.children.map((c) => { + return composeInstanciationTree( + c, repository, componentsUsageRepository - ), - }; + ); + }); + + return widget; + } else if (widget instanceof SingleChildRenderObjectWidget) { + // @ts-ignore + widget.child = composeInstanciationTree( + widget.child, + repository, + componentsUsageRepository + ); + + return widget; } else { return widget; } @@ -106,9 +102,8 @@ function composeComponentMeta( component: MasterComponentMetaToken ): MasterComponentWidget { const componentNode = component.body as ComponentNode; - const componentTokenizedBody = tokenizeComponent.fromComponentNode( - componentNode - ); + const componentTokenizedBody = + tokenizeComponent.fromComponentNode(componentNode); return new MasterComponentWidget({ key: component.key, diff --git a/packages/support-components/tokens/token-master-component.ts b/packages/support-components/tokens/token-master-component.ts index 950ea86b..7889cd74 100644 --- a/packages/support-components/tokens/token-master-component.ts +++ b/packages/support-components/tokens/token-master-component.ts @@ -46,25 +46,27 @@ export class MasterComponentMetaToken { } } -interface Property { +export interface Property { key: string; type: any; defaultValue: any; link: PropertyLink; } -type PropertyLink = InstanciationPropertyLink | DesignPropertyLink; +export type PropertyLink = + | InstanciationPropertyLink + | DesignPropertyLink; /** * Property link to a instanciation of (another) component. */ -interface InstanciationPropertyLink { +export interface InstanciationPropertyLink { type: "instanciation-link"; master: MasterComponentMetaToken; linksto: Link; } -interface DesignPropertyLink { +export interface DesignPropertyLink { type: "design-link"; /** * path to a property as indexpath.