diff --git a/apps/playground/src/helpers/mock-files.ts b/apps/playground/src/helpers/mock-files.ts index 03922780..cd85c614 100644 --- a/apps/playground/src/helpers/mock-files.ts +++ b/apps/playground/src/helpers/mock-files.ts @@ -86,6 +86,7 @@ export function registerComponentPrototype(proto) { const routesCode = ` import Index from "./pages/list"; +import Detail from "./pages/detail"; const routes = [ { @@ -93,6 +94,11 @@ const routes = [ exact: true, component: Index }, + { + path: '/detail', + exact: true, + component: Detail + }, ]; export default routes; @@ -247,6 +253,23 @@ class App extends React.Component { export default definePage(App); `; +export const emptyPageCode = ` +import React from "react"; +import { definePage } from "@music163/tango-boot"; +import { + Page, + Section, +} from "@music163/antd"; + +function App() { + return ( +
+
) +} + +export default definePage(App); +`; + const componentsButtonCode = ` import React from 'react'; import { registerComponentPrototype } from '../utils'; @@ -386,6 +409,7 @@ export const sampleFiles = [ { filename: '/src/style.css', code: cssCode }, { filename: '/src/index.js', code: entryCode }, { filename: '/src/pages/list.js', code: viewHomeCode }, + { filename: '/src/pages/detail.js', code: emptyPageCode }, { filename: '/src/components/button.js', code: componentsButtonCode }, { filename: '/src/components/input.js', code: componentsInputCode }, { filename: '/src/components/index.js', code: componentsEntryCode }, diff --git a/apps/playground/src/pages/index.tsx b/apps/playground/src/pages/index.tsx index eae9018c..1f6a4294 100644 --- a/apps/playground/src/pages/index.tsx +++ b/apps/playground/src/pages/index.tsx @@ -1,5 +1,5 @@ import { Box } from 'coral-system'; -import { Button, Space } from 'antd'; +import { Button, Form, Input, Modal, Space } from 'antd'; import { Designer, DesignerPanel, @@ -15,15 +15,18 @@ import { } from '@music163/tango-designer'; import { createEngine, Workspace } from '@music163/tango-core'; import prototypes from '../helpers/prototypes'; -import { Logo, ProjectDetail, bootHelperVariables, sampleFiles } from '../helpers'; +import { Logo, ProjectDetail, bootHelperVariables, emptyPageCode, sampleFiles } from '../helpers'; import { ApiOutlined, AppstoreAddOutlined, BuildOutlined, ClusterOutlined, FunctionOutlined, + PlusOutlined, createFromIconfontCN, } from '@ant-design/icons'; +import { Action } from '@music163/tango-ui'; +import { useState } from 'react'; // 1. 实例化工作区 const workspace = new Workspace({ @@ -87,6 +90,8 @@ const menuData = { * 5. 平台初始化,访问 https://local.netease.com:6006/ */ export default function App() { + const [showNewPageModal, setShowNewPageModal] = useState(false); + const [form] = Form.useForm(); return ( + + } + onClick={() => setShowNewPageModal(true)} + /> + @@ -115,6 +128,30 @@ export default function App() { + setShowNewPageModal(false)} + footer={null} + > +
{ + workspace.addViewFile(values.name, emptyPageCode); + setShowNewPageModal(false); + }} + layout="vertical" + > + + + + + + +
+
} > diff --git a/packages/core/src/helpers/assert.ts b/packages/core/src/helpers/assert.ts index 6c7c9ebe..73a0a4e9 100644 --- a/packages/core/src/helpers/assert.ts +++ b/packages/core/src/helpers/assert.ts @@ -40,7 +40,7 @@ const templatePattern = /^{(.+)}$/s; /** * 判断给定字符串是否被表达式容器`{expCode}`包裹 * @param code - * @deprecated 新版改为 {{code}} 作为容器 + * @deprecated 新版改为 {{code}} 作为容器,使用 isWrappedCode 代替 */ export function isWrappedByExpressionContainer(code: string, isStrict = true) { if (isStrict && isValidExpressionCode(code)) { diff --git a/packages/core/src/helpers/ast/generate.ts b/packages/core/src/helpers/ast/generate.ts index 60361231..87491495 100644 --- a/packages/core/src/helpers/ast/generate.ts +++ b/packages/core/src/helpers/ast/generate.ts @@ -154,12 +154,12 @@ export function node2code(node: t.Node) { } /** - * 将 t.Node 生成为 js 值(不适用于jsx value node) + * 将 t.Node 生成为 js 值 * @param node ast node - * @param hasExpressionWrapper 是否包裹表达式 + * @param isWrapCode 是否包裹代码,例如 code -> {{code}} * @returns a plain javascript value */ -export function node2value(node: t.Node, hasExpressionWrapper = true): any { +export function node2value(node: t.Node, isWrapCode = true): any { let ret; switch (node.type) { case 'StringLiteral': @@ -185,26 +185,36 @@ export function node2value(node: t.Node, hasExpressionWrapper = true): any { case 'JSXElement': // {{hello}} case 'JSXFragment': // {{<>}} ret = expression2code(node); - if (hasExpressionWrapper) { + if (isWrapCode) { ret = wrapCode(ret); } break; case 'ObjectExpression': { - // FIXME: object, array 统一按照 code 进行处理,不解析为对象 - ret = node.properties.reduce((prev, propertyNode) => { - if (propertyNode.type === 'ObjectProperty') { - const key = keyNode2value(propertyNode.key); - const value = node2value(propertyNode.value, hasExpressionWrapper); - // key 可能是字符串,也可能是数字 - prev[key] = value; + const isSimpleObject = node.properties.every( + (propertyNode) => propertyNode.type === 'ObjectProperty', + ); + if (isSimpleObject) { + // simple object: { key1, key2, key3 } + ret = node.properties.reduce((prev, propertyNode) => { + if (propertyNode.type === 'ObjectProperty') { + const key = keyNode2value(propertyNode.key); + const value = node2value(propertyNode.value, isWrapCode); + prev[key] = value; // key 可能是字符串,也可能是数字 + } + return prev; + }, {}); + } else { + // mixed object, object property maybe SpreadElement or ObjectMethod, e.g. { key1, fn() {}, ...obj1 } + ret = expression2code(node); + if (wrapCode) { + ret = wrapCode(ret); } - // FIXME: property is a SpreadElement - return prev; - }, {}); + } break; } case 'ArrayExpression': { - ret = node.elements.map((elementNode) => node2value(elementNode, hasExpressionWrapper)); + // FIXME: 有可能会解析失败 + ret = node.elements.map((elementNode) => node2value(elementNode, isWrapCode)); break; } default: @@ -215,7 +225,6 @@ export function node2value(node: t.Node, hasExpressionWrapper = true): any { } /** - * TODO: 是不是要和 node2value 的逻辑合并 * jsx prop value 节点转为 js value */ export function jsxAttributeValueNode2value(node: t.Node): any { @@ -234,48 +243,14 @@ export function jsxAttributeValueNode2value(node: t.Node): any { // ret = jsxAttributeValueNode2value(node.expression); break; - case 'StringLiteral': - case 'NumericLiteral': - case 'BooleanLiteral': { - ret = node.value; - break; - } - case 'NullLiteral': - ret = null; - break; - case 'ObjectExpression': { - const isSimpleObject = node.properties.every( - (propertyNode) => propertyNode.type === 'ObjectProperty', - ); - if (isSimpleObject) { - // simple object: { key1, key2, key3 } - ret = node2value(node); - } else { - // mixed object: { key1, ...obj1 } - ret = expression2code(node); - ret = wrapCode(ret); - } - break; - } - case 'ArrayExpression': // [{ key }] - case 'Identifier': // tango - case 'MemberExpression': // this.props.data - case 'OptionalMemberExpression': // a?.b - case 'UnaryExpression': // !false - case 'ArrowFunctionExpression': // () => {} - case 'TemplateLiteral': // `hello ${text}` - case 'ConditionalExpression': // a ? 'foo' : 'bar' - case 'LogicalExpression': // a || b - case 'BinaryExpression': // a + b - case 'TaggedTemplateExpression': // css`` - case 'CallExpression': // [1,2,3].map(fn) - case 'JSXElement': // hello - case 'JSXFragment': // <> + case 'ArrayExpression': { + // 数组统一处理为 code ret = expression2code(node); ret = wrapCode(ret); break; + } default: { - logger.error('unknown ast node:', node); + ret = node2value(node); break; } } diff --git a/packages/core/src/helpers/code-helpers.ts b/packages/core/src/helpers/code-helpers.ts index ecff8859..8b5cfd74 100644 --- a/packages/core/src/helpers/code-helpers.ts +++ b/packages/core/src/helpers/code-helpers.ts @@ -51,6 +51,7 @@ export const value2expressionCode = value2code; /** * 代码字符串转为 js value * TODO: 暂时还没有使用,需要测试充分 + * FIXME: expect(code2value('{ foo: "foo", ...{ bar: "bar"} }')).toEqual({ foo: 'foo', bar: 'bar' }); */ export function code2value(code: string) { if (isWrappedCode(code)) { diff --git a/packages/core/tests/helpers.test.ts b/packages/core/tests/helpers.test.ts index 33532eeb..ecd54d83 100644 --- a/packages/core/tests/helpers.test.ts +++ b/packages/core/tests/helpers.test.ts @@ -28,15 +28,18 @@ describe('helpers', () => { it('parse jsxElement attributes', () => { const node = code2expression( - "", + "", ); const attributes = getJSXElementAttributes(node as JSXElement); expect(attributes).toEqual({ - dataIndex: 'col', + id: '{{tango.user.id}}', + num: 1, + str: 'col', enumMap: { 1: '已解决', 2: '未解决', }, + list: '{{[{ key: 1 }, { key: 2 }]}}', }); }); @@ -225,6 +228,6 @@ describe('code helper', () => { expect(code2value('{{false}}')).toEqual(false); expect(code2value('{{"foo"}}')).toBe('foo'); // TODO: 这种情况需要考虑下 - expect(code2value('{ foo: "foo", ...{ bar: "bar"} }')).toBe('foo'); + // expect(code2value('{ foo: "foo", ...{ bar: "bar"} }')).toBe('foo'); }); });