Compare commits

..

17 Commits

Author SHA1 Message Date
wwsun fb26540f27 chore(release): publish
- @music163/tango-context@0.2.7
 - @music163/tango-core@0.2.6
 - @music163/tango-designer@0.5.6
 - @music163/tango-helpers@0.1.8
 - @music163/tango-sandbox@0.1.11
 - @music163/tango-setting-form@0.2.10
 - @music163/tango-ui@0.1.11
2023-11-23 15:56:48 +08:00
Wells 558f2cd0a6 fix: refactor update import specifiers (#68)
* fix: refactor update import declaration

* fix: rename module methods

* fix: update types

* fix: update

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-23 15:54:22 +08:00
Wells 8a1234e565 fix: prototype to import declaration (#67)
Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-22 20:00:32 +08:00
wwsun 630c486eb8 chore(release): publish
- @music163/tango-designer@0.5.5
2023-11-20 17:20:18 +08:00
wwsun 324f450851 docs: update timeline 2023-11-20 17:15:59 +08:00
wwsun d6ffb50e85 fix: pass down sandbox props 2023-11-20 17:05:44 +08:00
青岚 2906efc8fc docs: add seal d2c intro (#63) 2023-11-20 17:02:27 +08:00
wwsun 2259fe77fc chore(release): publish
- @music163/tango-context@0.2.6
 - @music163/tango-core@0.2.5
 - @music163/tango-designer@0.5.4
 - @music163/tango-helpers@0.1.7
 - @music163/tango-sandbox@0.1.10
 - @music163/tango-setting-form@0.2.9
 - @music163/tango-ui@0.1.10
2023-11-20 14:04:52 +08:00
Wells dbbd1dddc7 fix: refactor parse expression (#61)
* fix: update interfaces

* test: update

* fix: test if code has expression wrapper

* fix: parse code to expression node

* fix: update long text style in variableTree

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-20 14:01:01 +08:00
wwsun 14e17ebbed chore(release): publish
- @music163/tango-context@0.2.5
 - @music163/tango-core@0.2.4
 - @music163/tango-designer@0.5.3
 - @music163/tango-helpers@0.1.6
 - @music163/tango-sandbox@0.1.9
 - @music163/tango-setting-form@0.2.8
 - @music163/tango-ui@0.1.9
2023-11-16 14:35:52 +08:00
Wells 4a06eb20cb refactor: update workspace and viewModule
* docs: update example

* fix: refactor workspace

* fix: refactor viewModule

* fix: update interfaces

* fix: always switch to the view matched code file

* fix: keep sidebar state

* test: update

---------

Co-authored-by: wwsun <ww.sww@outlook.com>
2023-11-16 14:31:10 +08:00
wwsun 089a482f75 fix: parse variables from view 2023-11-14 11:30:45 +08:00
wwsun afb94d6a30 fix: fix sandbox props 2023-11-14 11:10:55 +08:00
wwsun b9403cfec6 fix: isVariableString 2023-11-07 20:20:07 +08:00
wwsun 6cf673e7f6 fix: allow override existing file via addFile 2023-11-07 17:23:02 +08:00
wwsun 653386dcc0 fix: update types 2023-11-06 19:21:35 +08:00
BoBoooooo 208e275bb1 fix: export isJSXElementById from traverse (#54)
* feat: traverse helper add findJSXParentsById func
* fix: update findJSXParentsById def
* feat: export isJSXElementById from traverse
2023-11-06 11:31:33 +08:00
57 changed files with 1082 additions and 585 deletions
+2 -1
View File
@@ -2,11 +2,12 @@
"extends": ["eslint-config-ali/typescript/react", "prettier"], "extends": ["eslint-config-ali/typescript/react", "prettier"],
"ignorePatterns": ["**/dist/**/*", "**/lib/**/*", "**/node_modules/**/*", "scripts/**/*"], "ignorePatterns": ["**/dist/**/*", "**/lib/**/*", "**/node_modules/**/*", "scripts/**/*"],
"rules": { "rules": {
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/dot-notation": "off", "@typescript-eslint/dot-notation": "off",
"@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-unused-vars": "warn",
"import/no-cycle": "off", "import/no-cycle": "off",
"no-nested-ternary": "off", "no-nested-ternary": "off",
"no-useless-return":"off", "no-useless-return": "off",
"no-param-reassign": "off", "no-param-reassign": "off",
"prefer-destructuring": "off" "prefer-destructuring": "off"
} }
+11
View File
@@ -96,6 +96,17 @@ Thanks to the NetEase Cloud Music Front-end team, Public Technology team, Live B
Thank you to CodeSandbox for providing the [Sandpack](https://sandpack.codesandbox.io/) project, which provides powerful online code execution capabilities for Tango. Thank you to CodeSandbox for providing the [Sandpack](https://sandpack.codesandbox.io/) project, which provides powerful online code execution capabilities for Tango.
## 📣 Product Promotion
![](https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/31629770956/da9e/3a74/4e00/7c69cf46a713f1b008bd1243b5b1ab1c.png)
Don't waste your time restoring the UI, try the NetEase Cloud Music "Seal D2C" development tool! Easily turn your design into code, support React, RN, Vue, WeChat apps and other multi-end scenarios, what you see is what you get!
Experience "Seal D2C" now:
- I'm a Figma user: <https://www.figma.com/community/plugin/1174548852019950797/seal-figma-to-code-d2c/>
- I'm a MasterGo user: <https://mastergo.com/community/plugin/98956774428196/>
## 📄 License ## 📄 License
This project is licensed under the terms of the [MIT license](./LICENSE) This project is licensed under the terms of the [MIT license](./LICENSE)
+11
View File
@@ -97,6 +97,17 @@ yarn start
感谢 CodeSandbox 提供的 [Sandpack](https://sandpack.codesandbox.io/) 项目,为 Tango 提供了强大的基于浏览器的代码构建与执行能力。 感谢 CodeSandbox 提供的 [Sandpack](https://sandpack.codesandbox.io/) 项目,为 Tango 提供了强大的基于浏览器的代码构建与执行能力。
## 📣 产品推广
![](https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/31629770956/da9e/3a74/4e00/7c69cf46a713f1b008bd1243b5b1ab1c.png)
不要再浪费时间还原 UI 啦,快来试试网易云音乐「海豹 D2C」研发工具吧!轻松将设计稿转为代码,支持 React、RN、Vue、微信小程序等多端场景,所见即所得!
立即体验「海豹 D2C」:
- 我是 Figma 用户:<https://www.figma.com/community/plugin/1174548852019950797/seal-figma-to-code-d2c/>
- 我是 MasterGo 用户:<https://mastergo.com/community/plugin/98956774428196/>
## 📄 开源协议 ## 📄 开源协议
此项目遵循 [MIT 开源协议](./LICENSE) 此项目遵循 [MIT 开源协议](./LICENSE)
+19 -10
View File
@@ -17,37 +17,43 @@ const tangoConfigJson = {
version: '17.0.2', version: '17.0.2',
library: 'React', library: 'React',
type: 'dependency', type: 'dependency',
resources: ['https://unpkg.com/react@{{version}}/umd/react.development.js'], resources: ['https://cdn.jsdelivr.net/npm/react@{{version}}/umd/react.development.js'],
}, },
'react-dom': { 'react-dom': {
version: '17.0.2', version: '17.0.2',
library: 'ReactDOM', library: 'ReactDOM',
type: 'dependency', type: 'dependency',
resources: ['https://unpkg.com/react-dom@{{version}}/umd/react-dom.development.js'], resources: [
'https://cdn.jsdelivr.net/npm/react-dom@{{version}}/umd/react-dom.development.js',
],
}, },
'react-is': { 'react-is': {
version: '16.13.1', version: '16.13.1',
library: 'ReactIs', library: 'ReactIs',
type: 'dependency', type: 'dependency',
resources: ['https://unpkg.com/react-is@{{version}}/umd/react-is.production.min.js'], resources: [
'https://cdn.jsdelivr.net/npm/react-is@{{version}}/umd/react-is.production.min.js',
],
}, },
'styled-components': { 'styled-components': {
version: '5.3.5', version: '5.3.5',
library: 'styled', library: 'styled',
type: 'dependency', type: 'dependency',
resources: ['https://unpkg.com/styled-components@{{version}}/dist/styled-components.min.js'], resources: [
'https://cdn.jsdelivr.net/npm/styled-components@{{version}}/dist/styled-components.min.js',
],
}, },
moment: { moment: {
version: '2.29.4', version: '2.29.4',
library: 'moment', library: 'moment',
type: 'dependency', type: 'dependency',
resources: ['https://unpkg.com/moment@{{version}}/moment.js'], resources: ['https://cdn.jsdelivr.net/npm/moment@{{version}}/moment.js'],
}, },
'@music163/tango-boot': { '@music163/tango-boot': {
version: '0.2.1', version: '0.2.1',
library: 'TangoBoot', library: 'TangoBoot',
type: 'baseDependency', type: 'baseDependency',
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'], resources: ['https://cdn.jsdelivr.net/npm/@music163/tango-boot@{{version}}/dist/boot.js'],
// resources: ['http://localhost:9001/boot.js'], // resources: ['http://localhost:9001/boot.js'],
description: '云音乐低代码运行时框架', description: '云音乐低代码运行时框架',
}, },
@@ -56,14 +62,14 @@ const tangoConfigJson = {
library: 'TangoAntd', library: 'TangoAntd',
type: 'baseDependency', type: 'baseDependency',
resources: [ resources: [
'https://unpkg.com/@music163/antd@{{version}}/dist/index.js', 'https://cdn.jsdelivr.net/npm/@music163/antd@{{version}}/dist/index.js',
'https://unpkg.com/antd@4.24.13/dist/antd.css', 'https://cdn.jsdelivr.net/npm/antd@4.24.13/dist/antd.css',
], ],
description: '云音乐低代码中后台应用基础物料', description: '云音乐低代码中后台应用基础物料',
designerResources: [ designerResources: [
'https://unpkg.com/@music163/antd@{{version}}/dist/designer.js', 'https://cdn.jsdelivr.net/npm/@music163/antd@{{version}}/dist/designer.js',
// 'http://localhost:9002/designer.js', // 'http://localhost:9002/designer.js',
'https://unpkg.com/antd@4.24.13/dist/antd.css', 'https://cdn.jsdelivr.net/npm/antd@4.24.13/dist/antd.css',
], ],
}, },
}, },
@@ -145,6 +151,7 @@ import {
Input, Input,
FormilyForm, FormilyForm,
} from "@music163/antd"; } from "@music163/antd";
import { Space } from '@music163/antd';
class App extends React.Component { class App extends React.Component {
render() { render() {
return ( return (
@@ -152,8 +159,10 @@ class App extends React.Component {
<Section title="Section Title"> <Section title="Section Title">
</Section> </Section>
<Section> <Section>
<Space>
<Button>button</Button> <Button>button</Button>
<Input /> <Input />
</Space>
</Section> </Section>
</Page> </Page>
); );
+9 -9
View File
@@ -8,14 +8,14 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script> <script src="https://cdn.jsdelivr.net/npm/react@16.14.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script> <script src="https://cdn.jsdelivr.net/npm/react-dom@16.14.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-is@16.13.1/umd/react-is.production.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/react-is@16.13.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/moment/min/moment-with-locales.js"></script> <script src="https://cdn.jsdelivr.net/npm/moment/min/moment-with-locales.js"></script>
<script src="https://unpkg.com/styled-components@5.3.11/dist/styled-components.js"></script> <script src="https://cdn.jsdelivr.net/npm/styled-components@5.3.11/dist/styled-components.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.15.6/babel.js"></script> <script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.15.6/babel.js"></script>
<script src="https://unpkg.com/prettier@2.6.0/standalone.js"></script> <script src="https://cdn.jsdelivr.net/npm/prettier@2.6.0/standalone.js"></script>
<script src="https://unpkg.com/prettier@2.6.0/parser-babel.js"></script> <script src="https://cdn.jsdelivr.net/npm/prettier@2.6.0/parser-babel.js"></script>
<script src="https://unpkg.com/antd@4.24.13/dist/antd-with-locales.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/antd@4.24.13/dist/antd-with-locales.min.js"></script>
</body> </body>
</html> </html>
-3
View File
@@ -29,9 +29,6 @@ import {
const workspace = new Workspace({ const workspace = new Workspace({
entry: '/src/index.js', entry: '/src/index.js',
files: sampleFiles, files: sampleFiles,
onFilesChange: (filenames) => {
console.log('onFilesChange', filenames, workspace.files);
},
}); });
// 2. 引擎初始化 // 2. 引擎初始化
+70
View File
@@ -0,0 +1,70 @@
import React from 'react';
import { Box } from 'coral-system';
import { CodeSandbox } from '@music163/tango-sandbox';
import { JsonView } from '@music163/tango-ui';
export default {
title: 'CodeSandbox',
};
const entryFilename = '/index.js';
const packageJson = {
name: 'demo',
private: true,
main: entryFilename,
dependencies: {
react: '^17.0.1',
'react-dom': '^17.0.1',
},
};
const entryFile = `
import React from 'react';
import ReactDOM from "react-dom";
import App from './src';
const rootElement = document.getElementById("root");
ReactDOM.render(
<App />,
rootElement
);
`;
const srcAppFile = `
import React from 'react';
export default function App() {
return <div style={{ background: 'yellow', color: 'red' }}>hello world</div>;
}
`;
const files = {
'/package.json': {
code: JSON.stringify(packageJson),
},
[entryFilename]: {
code: entryFile,
},
'/src/index.js': { code: srcAppFile },
};
export function Basic() {
return (
<Box display="flex" height="100vh">
<CodeSandbox
template="create-react-app"
bundlerURL="https://codesandbox.fn.netease.com"
files={files}
entry={entryFilename}
style={{
flex: 1,
}}
/>
<Box width="30%">
<JsonView src={files} />
</Box>
</Box>
);
}
+14 -1
View File
@@ -137,7 +137,7 @@ const config = {
], ],
}, },
{ {
title: '更多', title: '相关资源',
items: [ items: [
{ {
label: '博客', label: '博客',
@@ -149,6 +149,19 @@ const config = {
}, },
], ],
}, },
{
title: '更多产品',
items: [
{
label: '海豹 D2C - Figma 插件',
to: 'https://www.figma.com/community/plugin/1174548852019950797/seal-figma-to-code-d2c',
},
{
label: '海豹 D2C - MasterGo 插件',
to: 'https://mastergo.com/community/plugin/98956774428196/',
},
],
},
], ],
logo: { logo: {
alt: 'NetEase Cloud Music', alt: 'NetEase Cloud Music',
+2 -2
View File
@@ -24,7 +24,7 @@ const timelines = [
{ {
icon: null, icon: null,
title: '1.0 RC', title: '1.0 RC',
date: '2023.10.30', date: '2023.11.30',
description: translate({ description: translate({
id: 'homepage.timeline.rc', id: 'homepage.timeline.rc',
message: '核心 API 基本稳定,不再发生 BR,发布 1.0 RC 版本。', message: '核心 API 基本稳定,不再发生 BR,发布 1.0 RC 版本。',
@@ -33,7 +33,7 @@ const timelines = [
{ {
icon: null, icon: null,
title: '1.0', title: '1.0',
date: 'Before 2023.12.25', date: 'Before 2023.12.29',
description: translate({ description: translate({
id: 'homepage.timeline.stable', id: 'homepage.timeline.stable',
message: 'API 完全稳定,提供良好的社区支持,可用于生产环境。', message: 'API 完全稳定,提供良好的社区支持,可用于生产环境。',
+1 -1
View File
@@ -154,7 +154,7 @@ const config = {
// testLocationInResults: false, // testLocationInResults: false,
// The glob patterns Jest uses to detect test files // The glob patterns Jest uses to detect test files
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'], testMatch: ['**/tests/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [ // testPathIgnorePatterns: [
+1 -1
View File
@@ -7,7 +7,7 @@
], ],
"scripts": { "scripts": {
"start": "yarn workspace playground start", "start": "yarn workspace playground start",
"start:story": "yarn workspace storybook storybook", "start:docs": "yarn workspace storybook storybook",
"start:site": "yarn workspace website start", "start:site": "yarn workspace website start",
"deploy:site": "yarn workspace website deploy", "deploy:site": "yarn workspace website deploy",
"build": "lerna run build", "build": "lerna run build",
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.7](https://github.com/netease/tango/compare/@music163/tango-context@0.2.6...@music163/tango-context@0.2.7) (2023-11-23)
**Note:** Version bump only for package @music163/tango-context
## [0.2.6](https://github.com/netease/tango/compare/@music163/tango-context@0.2.5...@music163/tango-context@0.2.6) (2023-11-20)
**Note:** Version bump only for package @music163/tango-context
## [0.2.5](https://github.com/netease/tango/compare/@music163/tango-context@0.2.4...@music163/tango-context@0.2.5) (2023-11-16)
**Note:** Version bump only for package @music163/tango-context
## [0.2.4](https://github.com/netease/tango/compare/@music163/tango-context@0.2.3...@music163/tango-context@0.2.4) (2023-11-02) ## [0.2.4](https://github.com/netease/tango/compare/@music163/tango-context@0.2.3...@music163/tango-context@0.2.4) (2023-11-02)
### Bug Fixes ### Bug Fixes
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@music163/tango-context", "name": "@music163/tango-context",
"version": "0.2.4", "version": "0.2.7",
"description": "react context for tango-apps", "description": "react context for tango-apps",
"keywords": [ "keywords": [
"react", "react",
@@ -31,8 +31,8 @@
"react": ">= 16.8" "react": ">= 16.8"
}, },
"dependencies": { "dependencies": {
"@music163/tango-core": "^0.2.3", "@music163/tango-core": "^0.2.6",
"@music163/tango-helpers": "^0.1.5", "@music163/tango-helpers": "^0.1.8",
"mobx-react-lite": "4.0.2" "mobx-react-lite": "4.0.2"
}, },
"publishConfig": { "publishConfig": {
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.6](https://github.com/netease/tango/compare/@music163/tango-core@0.2.5...@music163/tango-core@0.2.6) (2023-11-23)
### Bug Fixes
- prototype to import declaration ([#67](https://github.com/netease/tango/issues/67)) ([8a1234e](https://github.com/netease/tango/commit/8a1234e565489c92f041356b7a06339eaeee48df))
- refactor update import specifiers ([#68](https://github.com/netease/tango/issues/68)) ([558f2cd](https://github.com/netease/tango/commit/558f2cd0a692c6bbc866d08250d25e2619af183f))
## [0.2.5](https://github.com/netease/tango/compare/@music163/tango-core@0.2.4...@music163/tango-core@0.2.5) (2023-11-20)
### Bug Fixes
- refactor parse expression ([#61](https://github.com/netease/tango/issues/61)) ([dbbd1dd](https://github.com/netease/tango/commit/dbbd1dddc75c532b7c9710ab0941c8680100f093))
## [0.2.4](https://github.com/netease/tango/compare/@music163/tango-core@0.2.3...@music163/tango-core@0.2.4) (2023-11-16)
### Bug Fixes
- allow override existing file via addFile ([6cf673e](https://github.com/netease/tango/commit/6cf673e7f6f330613e1d6331256a59900e8cbc68))
- export isJSXElementById from traverse ([#54](https://github.com/netease/tango/issues/54)) ([208e275](https://github.com/netease/tango/commit/208e275bb1cd98ed21d816c26179f9ebc11df223))
- parse variables from view ([089a482](https://github.com/netease/tango/commit/089a482f750e9d9a7743a6641d6e39989347d318))
- update types ([653386d](https://github.com/netease/tango/commit/653386dcc0b064b41915548129952eeabe53019f))
## [0.2.3](https://github.com/netease/tango/compare/@music163/tango-core@0.2.2...@music163/tango-core@0.2.3) (2023-11-02) ## [0.2.3](https://github.com/netease/tango/compare/@music163/tango-core@0.2.2...@music163/tango-core@0.2.3) (2023-11-02)
### Bug Fixes ### Bug Fixes
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@music163/tango-core", "name": "@music163/tango-core",
"version": "0.2.3", "version": "0.2.6",
"description": "tango core", "description": "tango core",
"author": "wwsun <ww.sun@outlook.com>", "author": "wwsun <ww.sun@outlook.com>",
"homepage": "", "homepage": "",
@@ -28,7 +28,7 @@
"@babel/parser": "^7.23.0", "@babel/parser": "^7.23.0",
"@babel/traverse": "^7.23.2", "@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0", "@babel/types": "^7.23.0",
"@music163/tango-helpers": "^0.1.5", "@music163/tango-helpers": "^0.1.8",
"@types/babel__generator": "^7.6.6", "@types/babel__generator": "^7.6.6",
"@types/babel__traverse": "^7.20.3", "@types/babel__traverse": "^7.20.3",
"mobx": "6.9.0", "mobx": "6.9.0",
+27
View File
@@ -1,3 +1,5 @@
import { isValidExpressionCode } from './ast';
const defineServiceHandlerNames = ['defineServices', 'createServices']; const defineServiceHandlerNames = ['defineServices', 'createServices'];
const sfHandlerPattern = new RegExp(`^(${defineServiceHandlerNames.join('|')})$`); const sfHandlerPattern = new RegExp(`^(${defineServiceHandlerNames.join('|')})$`);
@@ -20,3 +22,28 @@ const defineStoreHandlerName = 'defineStore';
export function isDefineStore(name: string) { export function isDefineStore(name: string) {
return defineStoreHandlerName === name; return defineStoreHandlerName === name;
} }
/**
* 是否是 tango 的变量引用
* @example tango.stores.app.name
* @example tango.stores?.app?.name
*
* @param name
* @returns
*/
export function isTangoVariable(name: string) {
return /^tango\??\.(stores|services)\??\./.test(name) && name.split('.').length > 2;
}
const templatePattern = /^{(.+)}$/s;
/**
* 判断给定字符串是否被表达式容器`{expCode}`包裹
* @param code
*/
export function isWrappedByExpressionContainer(code: string, isStrict = true) {
if (isStrict && isValidExpressionCode(code)) {
return false;
}
return templatePattern.test(code);
}
+8 -8
View File
@@ -6,10 +6,10 @@ import * as t from '@babel/types';
import { import {
logger, logger,
isValidObjectString, isValidObjectString,
isVariableString,
getVariableContent, getVariableContent,
isPlainObject, isPlainObject,
} from '@music163/tango-helpers'; } from '@music163/tango-helpers';
import { isWrappedByExpressionContainer } from '../assert';
// @see https://babeljs.io/docs/en/babel-parser#pluginss // @see https://babeljs.io/docs/en/babel-parser#pluginss
const babelParserConfig: ParserOptions = { const babelParserConfig: ParserOptions = {
@@ -22,7 +22,6 @@ const babelParserConfig: ParserOptions = {
'classProperties', 'classProperties',
'asyncGenerators', 'asyncGenerators',
'functionBind', 'functionBind',
'functionSent',
'dynamicImport', 'dynamicImport',
'optionalChaining', 'optionalChaining',
], ],
@@ -44,6 +43,7 @@ export function isValidCode(code: string) {
/** /**
* 检测代码是否是合法的表达式代码 * 检测代码是否是合法的表达式代码
* 表达式是一组代码的集合,它返回一个值;每一个合法的表达式都能计算成某个值
* @param code * @param code
* @returns * @returns
*/ */
@@ -91,8 +91,8 @@ export function code2expression(code: string) {
try { try {
expNode = t.cloneNode(parseExpression(code, babelParserConfig), false, true); expNode = t.cloneNode(parseExpression(code, babelParserConfig), false, true);
} catch (err) { } catch (err) {
console.error('invalid code', err); logger.error('invalid code', err);
expNode = t.identifier('undefined'); // expNode = t.identifier('undefined');
} }
return expNode; return expNode;
} }
@@ -103,7 +103,7 @@ export function code2expression(code: string) {
* @returns File * @returns File
*/ */
export function expressionCode2ast(code: string) { export function expressionCode2ast(code: string) {
if (isVariableString(code)) { if (isWrappedByExpressionContainer(code)) {
code = getVariableContent(code); code = getVariableContent(code);
} }
const node = code2expression(code); const node = code2expression(code);
@@ -130,7 +130,7 @@ export function value2node(
ret = t.numericLiteral(value); ret = t.numericLiteral(value);
break; break;
case 'string': case 'string':
if (isVariableString(value)) { if (isWrappedByExpressionContainer(value)) {
// 再检查是否是表达式容器,例如 {this.foo}, {1} // 再检查是否是表达式容器,例如 {this.foo}, {1}
const innerString = getVariableContent(value); const innerString = getVariableContent(value);
ret = code2expression(innerString); ret = code2expression(innerString);
@@ -201,7 +201,7 @@ export function value2jsxAttributeValueNode(value: any) {
if (isValidObjectString(value)) { if (isValidObjectString(value)) {
// 先检查是否是对象字符串 // 先检查是否是对象字符串
ret = t.jsxExpressionContainer(code2expression(value)); ret = t.jsxExpressionContainer(code2expression(value));
} else if (isVariableString(value)) { } else if (isWrappedByExpressionContainer(value)) {
// 再检查是否是表达式容器,例如 {this.foo}, {1} // 再检查是否是表达式容器,例如 {this.foo}, {1}
const innerString = getVariableContent(value); const innerString = getVariableContent(value);
ret = t.jsxExpressionContainer(code2expression(innerString)); ret = t.jsxExpressionContainer(code2expression(innerString));
@@ -221,7 +221,7 @@ export function value2jsxChildrenValueNode(value: any) {
let ret: t.JSXElement | t.JSXFragment | t.JSXExpressionContainer | t.JSXSpreadChild | t.JSXText; let ret: t.JSXElement | t.JSXFragment | t.JSXExpressionContainer | t.JSXSpreadChild | t.JSXText;
switch (typeof value) { switch (typeof value) {
case 'string': case 'string':
if (isVariableString(value)) { if (isWrappedByExpressionContainer(value)) {
const innerString = getVariableContent(value); const innerString = getVariableContent(value);
ret = t.jsxExpressionContainer(code2expression(innerString)); ret = t.jsxExpressionContainer(code2expression(innerString));
} else { } else {
+198 -18
View File
@@ -23,15 +23,15 @@ import {
object2node, object2node,
} from './parse'; } from './parse';
import { isValidComponentName } from '../string'; import { isValidComponentName } from '../string';
import { isDefineService, isDefineStore } from '../assert'; import { isDefineService, isDefineStore, isTangoVariable } from '../assert';
import type { import type {
IRouteData, IRouteData,
IStorePropertyData, IStorePropertyData,
IClassPropertyNodeData,
ITangoViewNodeData, ITangoViewNodeData,
IImportDeclarationPayload, IImportDeclarationPayload,
IServiceFunctionPayload, IServiceFunctionPayload,
InsertChildPositionType, InsertChildPositionType,
IImportSpecifierData,
} from '../../types'; } from '../../types';
import { IdGenerator } from '../id-generator'; import { IdGenerator } from '../id-generator';
@@ -194,7 +194,7 @@ export function queryXFormItemFields(ast: t.File | t.JSXElement) {
* @param node * @param node
* @param id * @param id
*/ */
function isJSXElementById(node: t.JSXElement, jsxElementNodeId: string) { export function isJSXElementById(node: t.JSXElement, jsxElementNodeId: string) {
let isTargetJSXElement = false; let isTargetJSXElement = false;
visitJSXElementAttributes(node, (name, value) => { visitJSXElementAttributes(node, (name, value) => {
if (name === SLOT.dnd && value === jsxElementNodeId) { if (name === SLOT.dnd && value === jsxElementNodeId) {
@@ -448,11 +448,12 @@ function getLastExportDeclarationIndex(node: t.Program) {
/** /**
* 生成新的 ImportDeclaration * 生成新的 ImportDeclaration
* @deprecated 使用 makeImportDeclaration2 代替
* @param importedModule * @param importedModule
* @param importedSourcePath * @param importedSourcePath
* @returns * @returns
*/ */
export function makeImportDeclaration(importedModule: IImportDeclarationPayload) { export function makeImportDeclarationLegacy(importedModule: IImportDeclarationPayload) {
const specifierNodes = const specifierNodes =
importedModule.specifiers?.map((localName) => importedModule.specifiers?.map((localName) =>
t.importSpecifier(t.identifier(localName), t.identifier(localName)), t.importSpecifier(t.identifier(localName), t.identifier(localName)),
@@ -467,6 +468,36 @@ export function makeImportDeclaration(importedModule: IImportDeclarationPayload)
return t.importDeclaration(newSpecifierNodes, t.stringLiteral(importedModule.sourcePath)); return t.importDeclaration(newSpecifierNodes, t.stringLiteral(importedModule.sourcePath));
} }
function specifierData2node(data: IImportSpecifierData) {
switch (data.type) {
case 'ImportDefaultSpecifier':
return t.importDefaultSpecifier(t.identifier(data.localName));
case 'ImportSpecifier':
return t.importSpecifier(
t.identifier(data.importedName || data.localName),
t.identifier(data.localName),
);
case 'ImportNamespaceSpecifier':
return t.importNamespaceSpecifier(t.identifier(data.localName));
default:
return;
}
}
function specifierDataList2nodes(specifiers: IImportSpecifierData[]) {
return specifiers.map((item) => specifierData2node(item)).filter((item) => !!item);
}
export function makeImportDeclaration(source: string, specifiers: IImportSpecifierData[]) {
const specifierNodes = specifierDataList2nodes(specifiers);
return t.importDeclaration(specifierNodes, t.stringLiteral(source));
}
/**
* @deprecated 使用 parseImportDeclaration 代替
* @param node
* @returns
*/
function getImportDeclarationData(node: t.ImportDeclaration): IImportDeclarationPayload { function getImportDeclarationData(node: t.ImportDeclaration): IImportDeclarationPayload {
const sourcePath = node2value(node.source); const sourcePath = node2value(node.source);
let defaultSpecifier; let defaultSpecifier;
@@ -487,16 +518,33 @@ function getImportDeclarationData(node: t.ImportDeclaration): IImportDeclaration
/** /**
* 添加新的导入语句 * 添加新的导入语句
* @deprecated 使用 addImportDeclaration2 代替
* @param ast * @param ast
* @param importedModule * @param importedModule
* @param importedSourcePath * @param importedSourcePath
* @returns * @returns
*/ */
export function addImportDeclaration(ast: t.File, importedModule: IImportDeclarationPayload) { export function addImportDeclarationLegacy(ast: t.File, importedModule: IImportDeclarationPayload) {
traverse(ast, { traverse(ast, {
Program(path) { Program(path) {
const lastIndex = getLastImportDeclarationIndex(path.node); const lastIndex = getLastImportDeclarationIndex(path.node);
const newImportDeclaration = makeImportDeclaration(importedModule); const newImportDeclaration = makeImportDeclarationLegacy(importedModule);
path.node.body.splice(lastIndex, 0, newImportDeclaration);
path.stop();
},
});
return ast;
}
export function addImportDeclaration(
ast: t.File,
source: string,
specifiers: IImportSpecifierData[],
) {
traverse(ast, {
Program(path) {
const lastIndex = getLastImportDeclarationIndex(path.node);
const newImportDeclaration = makeImportDeclaration(source, specifiers);
path.node.body.splice(lastIndex, 0, newImportDeclaration); path.node.body.splice(lastIndex, 0, newImportDeclaration);
path.stop(); path.stop();
}, },
@@ -506,17 +554,21 @@ export function addImportDeclaration(ast: t.File, importedModule: IImportDeclara
/** /**
* 更新已有的导入语句 * 更新已有的导入语句
* @deprecated 使用 updateImportDeclaration2 代替
* @param ast * @param ast
* @param importedModule * @param importedModule
* @param importedSourcePath * @param importedSourcePath
* @returns * @returns
*/ */
export function updateImportDeclaration(ast: t.File, importedModule: IImportDeclarationPayload) { export function updateImportDeclarationLegacy(
ast: t.File,
importedModule: IImportDeclarationPayload,
) {
traverse(ast, { traverse(ast, {
ImportDeclaration(path) { ImportDeclaration(path) {
const currentSourcePath = node2value(path.node.source); const currentSourcePath = node2value(path.node.source);
if (currentSourcePath === importedModule.sourcePath) { if (currentSourcePath === importedModule.sourcePath) {
const newImportDeclaration = makeImportDeclaration(importedModule); const newImportDeclaration = makeImportDeclarationLegacy(importedModule);
path.replaceWith(newImportDeclaration); path.replaceWith(newImportDeclaration);
path.stop(); // 只修改匹配到的第一条 path.stop(); // 只修改匹配到的第一条
} }
@@ -525,6 +577,50 @@ export function updateImportDeclaration(ast: t.File, importedModule: IImportDecl
return ast; return ast;
} }
export function updateImportDeclaration(
ast: t.File,
source: string,
specifiers: IImportSpecifierData[],
) {
traverse(ast, {
ImportDeclaration(path) {
const currentSourcePath = node2value(path.node.source);
if (currentSourcePath === source) {
const newImportDeclaration = makeImportDeclaration(source, specifiers);
path.replaceWith(newImportDeclaration);
path.stop(); // 只修改匹配到的第一条
}
},
});
return ast;
}
/**
* 再已有的导入语句中添加新的导入符号
* @param ast
* @param source
* @param newSpecifiers
* @returns
*/
export function insertImportSpecifiers(
ast: t.File,
source: string,
newSpecifiers: IImportSpecifierData[],
) {
traverse(ast, {
ImportDeclaration(path) {
const currentSourcePath = node2value(path.node.source);
if (currentSourcePath === source) {
const nodes = specifierDataList2nodes(newSpecifiers);
path.node.specifiers.push(...nodes);
// 只在匹配到的第一个导入声明语句添加即可,不再重复执行
path.stop();
}
},
});
return ast;
}
/** /**
* 新增类属性 * 新增类属性
* @param ast * @param ast
@@ -986,7 +1082,12 @@ export function traverseServiceFile(ast: t.File) {
const baseConfig: any = { const baseConfig: any = {
encryptFetch: false, encryptFetch: false,
}; };
const imports: Record<string, IImportSpecifierData[]> = {};
traverse(ast, { traverse(ast, {
ImportDeclaration(path) {
const { source, specifiers } = parseImportDeclaration(path.node);
imports[source] = specifiers;
},
CallExpression(path) { CallExpression(path) {
const calleeName = keyNode2value(path.node.callee) as string; const calleeName = keyNode2value(path.node.callee) as string;
if (isDefineService(calleeName)) { if (isDefineService(calleeName)) {
@@ -1003,6 +1104,7 @@ export function traverseServiceFile(ast: t.File) {
}, },
}); });
return { return {
imports,
services, services,
baseConfig, baseConfig,
}; };
@@ -1107,6 +1209,7 @@ export function cloneJSXElementWithoutTrackingData(node: t.JSXElement) {
} }
export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) { export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
const imports: Record<string, IImportSpecifierData[]> = {};
const importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]> = {}; const importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]> = {};
const nodes: Array<ITangoViewNodeData<t.JSXElement>> = []; const nodes: Array<ITangoViewNodeData<t.JSXElement>> = [];
const cloneAst = t.cloneNode(ast, true, true); const cloneAst = t.cloneNode(ast, true, true);
@@ -1115,12 +1218,43 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
traverse(ast, { traverse(ast, {
ImportDeclaration(path) { ImportDeclaration(path) {
const declarationData = getImportDeclarationData(path.node); const { source, specifiers } = parseImportDeclaration(path.node);
const exist = importedModules[declarationData.sourcePath]; if (imports[source]) {
if (!exist) { // 存在重复的导入语句,合并导入符号列表
importedModules[declarationData.sourcePath] = declarationData; imports[source] = imports[source].concat(specifiers);
} else { } else {
importedModules[declarationData.sourcePath] = Array.isArray(exist) imports[source] = specifiers;
}
// FIXME: 下面的逻辑兼容旧的逻辑,后续需要移除掉
const declarationData = specifiers.reduce(
(prev, cur) => {
switch (cur.type) {
case 'ImportDefaultSpecifier':
prev.defaultSpecifier = cur.localName;
break;
case 'ImportSpecifier':
prev.specifiers.push(cur.localName);
break;
case 'ImportNamespaceSpecifier':
prev.specifiers.push(cur.localName);
break;
default:
break;
}
return prev;
},
{
defaultSpecifier: undefined,
specifiers: [],
sourcePath: source,
},
);
const exist = importedModules[source];
if (!exist) {
importedModules[source] = declarationData;
} else {
importedModules[source] = Array.isArray(exist)
? exist.concat([declarationData]) ? exist.concat([declarationData])
: [exist, declarationData]; : [exist, declarationData];
} }
@@ -1128,11 +1262,14 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
MemberExpression(path) { MemberExpression(path) {
const variable = node2value(path.node, false); const variable = node2value(path.node, false);
if ( if (isTangoVariable(variable) && !variables.includes(variable)) {
/^tango\.(stores|services)\./.test(variable) && variables.push(variable);
variable.split('.').length === 3 && }
!variables.includes(variable) },
) {
OptionalMemberExpression(path) {
const variable = node2value(path.node, false);
if (isTangoVariable(variable) && !variables.includes(variable)) {
variables.push(variable); variables.push(variable);
} }
}, },
@@ -1183,7 +1320,50 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
ast, ast,
cleanAst, cleanAst,
nodes, nodes,
imports,
importedModules, importedModules,
variables, variables,
}; };
} }
/**
* 解析导入语句
*/
function parseImportDeclaration(node: t.ImportDeclaration) {
const source = node2value(node.source) as string;
const specifiers: IImportSpecifierData[] = [];
node.specifiers.forEach((specifierNode) => {
const data: IImportSpecifierData = {
localName: keyNode2value(specifierNode.local) as string,
type: specifierNode.type,
};
if (specifierNode.type === 'ImportSpecifier') {
data.importedName = keyNode2value(specifierNode.imported) as string;
}
specifiers.push(data);
});
return {
source,
specifiers,
};
}
/**
* 基本的文件解析过程
* @param ast
*/
export function traverseFile(ast: t.File) {
const imports: Record<string, IImportSpecifierData[]> = {};
traverse(ast, {
ImportDeclaration(path) {
const { source, specifiers } = parseImportDeclaration(path.node);
imports[source] = specifiers;
},
});
return {
imports,
};
}
+6 -3
View File
@@ -1,5 +1,6 @@
import { getVariableContent, isVariableString } from '@music163/tango-helpers'; import { getVariableContent } from '@music163/tango-helpers';
import { value2node, expression2code } from './ast'; import { value2node, expression2code, isValidExpressionCode } from './ast';
import { isWrappedByExpressionContainer } from './assert';
/** /**
* 将 js value 转换为代码字符串 * 将 js value 转换为代码字符串
@@ -36,7 +37,9 @@ export function value2expressionCode(val: any) {
switch (typeof val) { switch (typeof val) {
case 'string': { case 'string': {
if (isVariableString(val)) { if (isValidExpressionCode(val)) {
ret = val;
} else if (isWrappedByExpressionContainer(val, false)) {
ret = getVariableContent(val); ret = getVariableContent(val);
} else if (isStringCode(val)) { } else if (isStringCode(val)) {
ret = val; ret = val;
+5 -2
View File
@@ -1,5 +1,5 @@
import { Dict } from '@music163/tango-helpers'; import { Dict } from '@music163/tango-helpers';
import { IImportDeclarationPayload, IImportSpecifierData } from '../types'; import { IImportDeclarationPayload, IImportSpecifierSourceData } from '../types';
/** /**
* 导入列表解析为导入声明对象 * 导入列表解析为导入声明对象
@@ -7,7 +7,10 @@ import { IImportDeclarationPayload, IImportSpecifierData } from '../types';
* @param nameMap * @param nameMap
* @returns * @returns
*/ */
export function namesToImportDeclarations(names: string[], nameMap: Dict<IImportSpecifierData>) { export function namesToImportDeclarations(
names: string[],
nameMap: Dict<IImportSpecifierSourceData>,
) {
const map = {}; const map = {};
names.forEach((name) => { names.forEach((name) => {
const mod = nameMap[name]; const mod = nameMap[name];
+41 -3
View File
@@ -3,16 +3,51 @@ import {
ComponentPropType, ComponentPropType,
ComponentPrototypeType, ComponentPrototypeType,
isNil, isNil,
isVariableString,
logger, logger,
uuid, uuid,
} from '@music163/tango-helpers'; } from '@music163/tango-helpers';
import { getRelativePath, isFilepath } from './string'; import { getRelativePath, isFilepath } from './string';
import type { IImportDeclarationPayload } from '../types'; import type { IImportDeclarationPayload, IImportSpecifierData } from '../types';
import { code2expression } from './ast'; import { code2expression } from './ast';
import { isWrappedByExpressionContainer } from './assert';
export function prototype2importDeclarationData(
prototype: ComponentPrototypeType,
relativeFilepath?: string,
): { source: string; specifiers: IImportSpecifierData[] } {
let source = prototype.package;
if (relativeFilepath && isFilepath(source)) {
source = getRelativePath(relativeFilepath, source);
}
if (source.endsWith('.js')) {
source = source.slice(0, -3);
}
const specifiers: IImportSpecifierData[] = [];
if (prototype.exportType === 'defaultExport') {
specifiers.push({
localName: prototype.name,
type: 'ImportDefaultSpecifier',
});
} else {
[prototype.name, ...(prototype.relatedImports || [])].forEach((item) => {
specifiers.push({
localName: item,
type: 'ImportSpecifier',
});
});
}
return {
source,
specifiers,
};
}
/** /**
* 根据组件的 prototype 生成 ImportDeclarationPayload * 根据组件的 prototype 生成 ImportDeclarationPayload
* @deprecated
*/ */
export function getImportDeclarationPayloadByPrototype( export function getImportDeclarationPayloadByPrototype(
prototype: ComponentPrototypeType, prototype: ComponentPrototypeType,
@@ -35,6 +70,9 @@ export function getImportDeclarationPayloadByPrototype(
if (relativeFilepath && isFilepath(sourcePath)) { if (relativeFilepath && isFilepath(sourcePath)) {
sourcePath = getRelativePath(relativeFilepath, sourcePath); sourcePath = getRelativePath(relativeFilepath, sourcePath);
} }
if (sourcePath.endsWith('.js')) {
sourcePath = sourcePath.slice(0, -3);
}
return { return {
defaultSpecifier, defaultSpecifier,
@@ -82,7 +120,7 @@ function getPropKeyValuePair(item: ComponentPropType, generateValue: (...args: a
break; break;
} }
case 'string': { case 'string': {
if (!isVariableString(value)) { if (!isWrappedByExpressionContainer(value)) {
// 不是变量字符串 // 不是变量字符串
value = `"${value}"`; value = `"${value}"`;
} else { } else {
+4
View File
@@ -2,3 +2,7 @@ export * from './models';
export * from './factory'; export * from './factory';
export * from './helpers'; export * from './helpers';
export * from './types'; export * from './types';
export { default as generator } from '@babel/generator';
export { default as traverse } from '@babel/types';
export * from '@babel/parser';
+54 -39
View File
@@ -5,11 +5,11 @@ import { DragSource } from './drag-source';
import { import {
IFileConfig, IFileConfig,
FileType, FileType,
IImportDeclarationPayload,
InsertChildPositionType, InsertChildPositionType,
ITangoConfigPackages, ITangoConfigPackages,
IPageConfigData, IPageConfigData,
IServiceFunctionPayload, IServiceFunctionPayload,
IImportSpecifierSourceData,
IImportSpecifierData, IImportSpecifierData,
} from '../types'; } from '../types';
import { TangoFile, TangoJsonFile } from './file'; import { TangoFile, TangoJsonFile } from './file';
@@ -25,7 +25,7 @@ export interface IViewFile {
/** /**
* 通过导入组件名查找组件来自的包 * 通过导入组件名查找组件来自的包
*/ */
importMap?: Dict<IImportSpecifierData>; importMap?: Dict<IImportSpecifierSourceData>;
listImportSources?: () => string[]; listImportSources?: () => string[];
listModals?: () => Array<{ label: string; value: string }>; listModals?: () => Array<{ label: string; value: string }>;
@@ -33,6 +33,14 @@ export interface IViewFile {
update: (code?: string, isFormatCode?: boolean, refreshWorkspace?: boolean) => void; update: (code?: string, isFormatCode?: boolean, refreshWorkspace?: boolean) => void;
/**
* 添加新的导入符号
* @param source 导入来源
* @param newSpecifiers 新导入符号列表
* @returns this
*/
addImportSpecifiers: (source: string, newSpecifiers: IImportSpecifierData[]) => IViewFile;
getNode: (targetNodeId: string) => IViewNode; getNode: (targetNodeId: string) => IViewNode;
removeNode: (targetNodeId: string) => IViewFile; removeNode: (targetNodeId: string) => IViewFile;
@@ -41,31 +49,15 @@ export interface IViewFile {
targetNodeId: string, targetNodeId: string,
newNode: any, newNode: any,
position?: InsertChildPositionType, position?: InsertChildPositionType,
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile; ) => IViewFile;
insertAfter: ( insertAfter: (targetNodeId: string, newNode: any) => IViewFile;
targetNodeId: string,
newNode: any,
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile;
insertBefore: ( insertBefore: (targetNodeId: string, newNode: any) => IViewFile;
targetNodeId: string,
newNode: any,
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile;
replaceNode: ( replaceNode: (targetNodeId: string, newNode: any) => IViewFile;
targetNodeId: string,
newNode: any,
sourcePrototype?: string | ComponentPrototypeType,
) => IViewFile;
replaceViewChildren: ( replaceViewChildren: (rawNodes: any[]) => IViewFile;
rawNodes: any[],
importDeclarations?: IImportDeclarationPayload[],
) => IViewFile;
updateNodeAttribute: ( updateNodeAttribute: (
nodeId: string, nodeId: string,
@@ -148,8 +140,8 @@ export interface IWorkspace {
storeModules?: Record<string, TangoStoreModule>; storeModules?: Record<string, TangoStoreModule>;
serviceModules?: Record<string, TangoServiceModule>; serviceModules?: Record<string, TangoServiceModule>;
refresh: (names: string[]) => void;
ready: () => void; ready: () => void;
refresh: (names: string[]) => void;
setActiveRoute: (path: string) => void; setActiveRoute: (path: string) => void;
setActiveFile: (filename: string) => void; setActiveFile: (filename: string) => void;
@@ -159,22 +151,25 @@ export interface IWorkspace {
onFilesChange?: (filenames: string[]) => void; onFilesChange?: (filenames: string[]) => void;
getFile: (filename: string) => TangoFile; // ----------------- 文件操作 -----------------
listFiles: () => Record<string, string>;
addFile: (filename: string, code: string, fileType?: FileType) => void;
addFiles: (files: IFileConfig[]) => void; addFiles: (files: IFileConfig[]) => void;
updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void; addFile: (filename: string, code: string, fileType?: FileType) => void;
addServiceFile: (serviceName: string, code: string) => void;
addStoreFile: (storeName: string, code: string) => void;
addViewFile: (viewName: string, code: string) => void;
removeFile: (filename: string) => void; removeFile: (filename: string) => void;
renameFile: (oldFilename: string, newFilename: string) => void; renameFile: (oldFilename: string, newFilename: string) => void;
renameFolder: (oldFoldername: string, newFoldername: string) => void; renameFolder: (oldFoldername: string, newFoldername: string) => void;
/** updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void;
* 查询节点
* @param id 节点 ID listFiles: () => Record<string, string>;
* @param module 节点所在的模块名 getFile: (filename: string) => TangoFile;
* @returns 返回节点对象
*/ // ----------------- 节点操作 -----------------
getNode: (id: string, module?: string) => IViewNode;
removeSelectedNode: () => void; removeSelectedNode: () => void;
cloneSelectedNode: () => void; cloneSelectedNode: () => void;
@@ -195,6 +190,16 @@ export interface IWorkspace {
relatedImports?: string[], relatedImports?: string[],
) => void; ) => void;
/**
* 查询节点
* @param id 节点 ID
* @param module 节点所在的模块名
* @returns 返回节点对象
*/
getNode: (id: string, module?: string) => IViewNode;
// ----------------- 服务函数文件操作 -----------------
getServiceFunction?: (serviceKey: string) => object; getServiceFunction?: (serviceKey: string) => object;
listServiceFunctions?: () => Record<string, object>; listServiceFunctions?: () => Record<string, object>;
removeServiceFunction?: (serviceName: string, modName?: string) => void; removeServiceFunction?: (serviceName: string, modName?: string) => void;
@@ -205,21 +210,25 @@ export interface IWorkspace {
updateServiceFunction?: (payload: IServiceFunctionPayload, modName?: string) => void; updateServiceFunction?: (payload: IServiceFunctionPayload, modName?: string) => void;
updateServiceBaseConfig?: (IServiceFunctionPayload: object, modName?: string) => void; updateServiceBaseConfig?: (IServiceFunctionPayload: object, modName?: string) => void;
addStoreModule?: (storeName: string, code: string) => void; // ----------------- 状态管理文件操作 -----------------
removeStoreModule?: (storeName: string) => void;
addStoreState?: (storeName: string, stateName: string, initValue: string) => void; addStoreState?: (storeName: string, stateName: string, initValue: string) => void;
removeStoreState?: (storeName: string, stateName: string) => void; removeStoreState?: (storeName: string, stateName: string) => void;
removeStoreModule?: (storeName: string) => void;
updateModuleCodeByVariablePath?: (variablePath: string, code: string) => void; // ----------------- 视图文件操作 -----------------
// TODO: 是否需要,和 addFile 有啥区别 ???
addViewPage: (name: string, code: string) => void;
removeViewModule: (routePath: string) => void; removeViewModule: (routePath: string) => void;
copyViewPage: (sourceRoutePath: string, targetPageData: IPageConfigData) => void; copyViewPage: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
// ----------------- 路由文件操作 -----------------
updateRoute: (sourceRoutePath: string, targetPageData: IPageConfigData) => void; updateRoute: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
// ----------------- 依赖包操作 -----------------
addDependency?: (data: any) => void;
listDependencies?: () => any; listDependencies?: () => any;
getDependency?: (pkgName: string) => object;
updateDependency?: ( updateDependency?: (
name: string, name: string,
@@ -243,6 +252,12 @@ export interface IWorkspace {
removeBizComp?: (name: string) => void; removeBizComp?: (name: string) => void;
// ----------------- 其他操作 -----------------
updateModuleCodeByVariablePath?: (variablePath: string, code: string) => void;
// ----------------- getter -----------------
get activeViewModule(): IViewFile; get activeViewModule(): IViewFile;
get pages(): any[]; get pages(): any[];
get bizComps(): string[]; get bizComps(): string[];
+28 -3
View File
@@ -1,9 +1,16 @@
import * as t from '@babel/types'; import * as t from '@babel/types';
import { action, computed, makeObservable, observable } from 'mobx'; import { action, computed, makeObservable, observable } from 'mobx';
import { isNil } from '@music163/tango-helpers'; import { isNil } from '@music163/tango-helpers';
import { code2ast, ast2code, formatCode } from '../helpers'; import {
code2ast,
ast2code,
formatCode,
traverseFile,
addImportDeclaration,
updateImportDeclaration,
} from '../helpers';
import { TangoFile } from './file'; import { TangoFile } from './file';
import { IFileConfig } from '../types'; import { IFileConfig, IImportSpecifierData, ImportDeclarationDataType } from '../types';
import { IWorkspace } from './interfaces'; import { IWorkspace } from './interfaces';
/** /**
@@ -14,6 +21,11 @@ import { IWorkspace } from './interfaces';
export class TangoModule extends TangoFile { export class TangoModule extends TangoFile {
ast: t.File; ast: t.File;
/**
* 导入语句
*/
imports: ImportDeclarationDataType;
constructor(workspace: IWorkspace, props: IFileConfig, isSyncCode = true) { constructor(workspace: IWorkspace, props: IFileConfig, isSyncCode = true) {
super(workspace, props, isSyncCode); super(workspace, props, isSyncCode);
} }
@@ -39,6 +51,16 @@ export class TangoModule extends TangoFile {
} }
} }
addImportDeclaration(source: string, specifiers: IImportSpecifierData[]) {
this.ast = addImportDeclaration(this.ast, source, specifiers);
return this;
}
updateImportDeclaration(source: string, specifiers: IImportSpecifierData[]) {
this.ast = updateImportDeclaration(this.ast, source, specifiers);
return this;
}
/** /**
* 基于最新的 ast 进行源码同步 * 基于最新的 ast 进行源码同步
*/ */
@@ -69,7 +91,10 @@ export class TangoModule extends TangoFile {
this.ast = code2ast(code); this.ast = code2ast(code);
} }
_analysisAst() {} _analysisAst() {
const { imports } = traverseFile(this.ast);
this.imports = imports;
}
} }
/** /**
+2 -1
View File
@@ -58,7 +58,8 @@ export class TangoServiceModule extends TangoModule {
} }
_analysisAst() { _analysisAst() {
const { services, baseConfig } = traverseServiceFile(this.ast); const { imports, services, baseConfig } = traverseServiceFile(this.ast);
this.imports = imports;
this._serviceFunctions = services; this._serviceFunctions = services;
this._baseConfig = baseConfig; this._baseConfig = baseConfig;
if (baseConfig.namespace) { if (baseConfig.namespace) {
+86 -100
View File
@@ -1,6 +1,6 @@
import * as t from '@babel/types'; import * as t from '@babel/types';
import { action, computed, makeObservable, observable, toJS } from 'mobx'; import { action, computed, makeObservable, observable, toJS } from 'mobx';
import { ComponentPrototypeType, Dict } from '@music163/tango-helpers'; import { Dict } from '@music163/tango-helpers';
import { import {
ast2code, ast2code,
traverseViewFile, traverseViewFile,
@@ -8,15 +8,17 @@ import {
insertSiblingAfterJSXElement, insertSiblingAfterJSXElement,
appendChildToJSXElement, appendChildToJSXElement,
addImportDeclaration, addImportDeclaration,
updateImportDeclaration,
replaceJSXElement, replaceJSXElement,
getImportDeclarationPayloadByPrototype,
removeUnusedImportSpecifiers, removeUnusedImportSpecifiers,
insertSiblingBeforeJSXElement, insertSiblingBeforeJSXElement,
replaceRootJSXElementChildren, replaceRootJSXElementChildren,
IdGenerator, IdGenerator,
updateJSXAttributes, updateJSXAttributes,
queryXFormItemFields, queryXFormItemFields,
prototype2importDeclarationData,
insertImportSpecifiers,
addImportDeclarationLegacy,
updateImportDeclarationLegacy,
} from '../helpers'; } from '../helpers';
import { TangoNode } from './node'; import { TangoNode } from './node';
import { import {
@@ -24,11 +26,59 @@ import {
ITangoViewNodeData, ITangoViewNodeData,
IImportDeclarationPayload, IImportDeclarationPayload,
InsertChildPositionType, InsertChildPositionType,
IImportSpecifierSourceData,
ImportDeclarationDataType,
IImportSpecifierData, IImportSpecifierData,
} from '../types'; } from '../types';
import { IViewFile, IWorkspace } from './interfaces'; import { IViewFile, IWorkspace } from './interfaces';
import { TangoModule } from './module'; import { TangoModule } from './module';
/**
* 导入信息转为 变量名->来源 的 map 结构
* @param importedModules
* @returns
*/
function buildImportMap(importedModules: ImportDeclarationDataType) {
const map: Dict<IImportSpecifierSourceData> = {};
Object.keys(importedModules).forEach((source) => {
const specifiers = importedModules[source];
specifiers?.forEach((specifier) => {
map[specifier.localName] = {
source,
isDefault: specifier.type === 'ImportDefaultSpecifier',
};
});
});
return map;
}
/**
* 将节点列表转换为 tree data 嵌套数组
* @param list
*/
function nodeListToTreeData(list: ITangoViewNodeData[]) {
const map: Record<string, ITangoViewNodeData> = {};
list.forEach((item) => {
// 如果不存在,则初始化
if (!map[item.id]) {
map[item.id] = {
...item,
children: [],
};
}
// 是否找到父节点,找到则塞进去
if (item.parentId && map[item.parentId]) {
map[item.parentId].children.push(map[item.id]);
}
});
// 保留根节点
const ret = Object.values(map).filter((item) => !item.parentId);
return ret;
}
/** /**
* 视图模块 * 视图模块
*/ */
@@ -38,7 +88,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
/** /**
* 通过导入组件名查找组件来自的包 * 通过导入组件名查找组件来自的包
*/ */
importMap: Dict<IImportSpecifierData>; importMap: Dict<IImportSpecifierSourceData>;
/** /**
* 视图中依赖的 tango 变量,仅 stores 和 services * 视图中依赖的 tango 变量,仅 stores 和 services
@@ -51,6 +101,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
private _nodes: Map<string, TangoNode>; private _nodes: Map<string, TangoNode>;
/** /**
* 导入的模块 * 导入的模块
* @deprecated
*/ */
private _importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>; private _importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>;
@@ -98,6 +149,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
ast: newAst, ast: newAst,
cleanAst, cleanAst,
nodes, nodes,
imports,
importedModules, importedModules,
variables, variables,
} = traverseViewFile(this.ast, this._idGenerator); } = traverseViewFile(this.ast, this._idGenerator);
@@ -107,7 +159,8 @@ export class TangoViewModule extends TangoModule implements IViewFile {
this._cleanCode = ast2code(cleanAst); this._cleanCode = ast2code(cleanAst);
this._importedModules = importedModules; this._importedModules = importedModules;
this.importMap = this.buildImportMap(importedModules); this.imports = imports;
this.importMap = buildImportMap(imports);
this.variables = variables; this.variables = variables;
this._nodes.clear(); this._nodes.clear();
@@ -127,7 +180,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
* 依赖列表 * 依赖列表
*/ */
listImportSources() { listImportSources() {
return Object.keys(this._importedModules); return Object.keys(this.imports);
} }
/** /**
@@ -168,23 +221,30 @@ export class TangoViewModule extends TangoModule implements IViewFile {
} }
/** /**
* 基于组件的 prototype 信息更新导入信息 * 添加导入符号
* @param prototype * @param source
* @param shouldUpdateCode * @param newSpecifiers
* @returns
*/ */
updateImportSpecifiersByPrototype(sourcePrototype: string | ComponentPrototypeType) { addImportSpecifiers(source: string, newSpecifiers: IImportSpecifierData[]) {
const prototype = this.workspace.getPrototype(sourcePrototype); const existSpecifiers = this.imports[source];
if (prototype) { if (existSpecifiers) {
const importDeclaration = getImportDeclarationPayloadByPrototype(prototype, this.filename); const insertedSpecifiers = newSpecifiers.filter((item) => {
this.updateImportSpecifiers(importDeclaration); return !existSpecifiers.find((existItem) => existItem.localName === item.localName);
});
this.ast = insertImportSpecifiers(this.ast, source, insertedSpecifiers);
} else {
// 不存在导入来源,直接添加新的导入语句
this.ast = addImportDeclaration(this.ast, source, newSpecifiers);
} }
return this; return this;
} }
/** /**
* 更新导入的变量(新版) * 更新导入的变量
* @deprecated 使用 updateImportDeclaration 代替
*/ */
updateImportSpecifiers(importDeclaration: IImportDeclarationPayload) { updateImportSpecifiersLegacy(importDeclaration: IImportDeclarationPayload) {
const mods = this._importedModules[importDeclaration.sourcePath]; const mods = this._importedModules[importDeclaration.sourcePath];
let ast; let ast;
// 如果模块已存在,需要去重 // 如果模块已存在,需要去重
@@ -199,12 +259,12 @@ export class TangoViewModule extends TangoModule implements IViewFile {
(name) => !specifiers.includes(name), (name) => !specifiers.includes(name),
); );
ast = updateImportDeclaration(this.ast, { ast = updateImportDeclarationLegacy(this.ast, {
...importDeclaration, ...importDeclaration,
specifiers: newSpecifiers.concat(targetMod.specifiers), specifiers: newSpecifiers.concat(targetMod.specifiers),
}); });
} else { } else {
ast = addImportDeclaration(this.ast, importDeclaration); ast = addImportDeclarationLegacy(this.ast, importDeclaration);
} }
this.ast = ast; this.ast = ast;
return this; return this;
@@ -249,7 +309,8 @@ export class TangoViewModule extends TangoModule implements IViewFile {
// 导入依赖的组件 // 导入依赖的组件
relatedImports.forEach((name: string) => { relatedImports.forEach((name: string) => {
const proto = this.workspace.getPrototype(name); const proto = this.workspace.getPrototype(name);
this.updateImportSpecifiersByPrototype(proto); const { source, specifiers } = prototype2importDeclarationData(proto, this.filename);
this.addImportSpecifiers(source, specifiers);
}); });
} }
this.ast = updateJSXAttributes(this.ast, nodeId, config); this.ast = updateJSXAttributes(this.ast, nodeId, config);
@@ -260,43 +321,25 @@ export class TangoViewModule extends TangoModule implements IViewFile {
* 插入子节点的最后面 * 插入子节点的最后面
* @param targetNodeId * @param targetNodeId
* @param newNode * @param newNode
* @param sourceName * @param position
* @returns * @returns
*/ */
insertChild( insertChild(
targetNodeId: string, targetNodeId: string,
newNode: t.JSXElement, newNode: t.JSXElement,
position: InsertChildPositionType = 'last', position: InsertChildPositionType = 'last',
sourceName: string | ComponentPrototypeType,
) { ) {
this.ast = appendChildToJSXElement(this.ast, targetNodeId, newNode, position); this.ast = appendChildToJSXElement(this.ast, targetNodeId, newNode, position);
if (sourceName) {
this.updateImportSpecifiersByPrototype(sourceName);
}
return this; return this;
} }
insertAfter( insertAfter(targetNodeId: string, newNode: t.JSXElement) {
targetNodeId: string,
newNode: t.JSXElement,
sourceName?: string | ComponentPrototypeType,
) {
this.ast = insertSiblingAfterJSXElement(this.ast, targetNodeId, newNode); this.ast = insertSiblingAfterJSXElement(this.ast, targetNodeId, newNode);
if (sourceName) {
this.updateImportSpecifiersByPrototype(sourceName);
}
return this; return this;
} }
insertBefore( insertBefore(targetNodeId: string, newNode: t.JSXElement) {
targetNodeId: string,
newNode: t.JSXElement,
sourceName?: string | ComponentPrototypeType,
) {
this.ast = insertSiblingBeforeJSXElement(this.ast, targetNodeId, newNode); this.ast = insertSiblingBeforeJSXElement(this.ast, targetNodeId, newNode);
if (sourceName) {
this.updateImportSpecifiersByPrototype(sourceName);
}
return this; return this;
} }
@@ -304,22 +347,15 @@ export class TangoViewModule extends TangoModule implements IViewFile {
* 替换目标节点为新节点 * 替换目标节点为新节点
* @param targetNodeId * @param targetNodeId
* @param newNode * @param newNode
* @param sourcePrototype
*/ */
replaceNode( replaceNode(targetNodeId: string, newNode: t.JSXElement) {
targetNodeId: string,
newNode: t.JSXElement,
sourceName?: string | ComponentPrototypeType,
) {
this.ast = replaceJSXElement(this.ast, targetNodeId, newNode); this.ast = replaceJSXElement(this.ast, targetNodeId, newNode);
if (sourceName) {
this.updateImportSpecifiersByPrototype(sourceName);
}
return this; return this;
} }
/** /**
* 替换 jsx 跟结点的子元素 * 替换 jsx 跟结点的子元素
* @deprecated 不推荐使用
*/ */
replaceViewChildren( replaceViewChildren(
childrenNodes: t.JSXElement[], childrenNodes: t.JSXElement[],
@@ -331,60 +367,10 @@ export class TangoViewModule extends TangoModule implements IViewFile {
if (importDeclarations?.length) { if (importDeclarations?.length) {
importDeclarations.forEach((item) => { importDeclarations.forEach((item) => {
this.updateImportSpecifiers(item); this.updateImportSpecifiersLegacy(item);
}); });
} }
return this; return this;
} }
private buildImportMap(
importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>,
) {
const map: Dict<IImportSpecifierData> = {};
Object.keys(importedModules).forEach((modName) => {
const mod = importedModules[modName];
(Array.isArray(mod) ? mod : [mod]).forEach((item) => {
if (item.defaultSpecifier) {
map[item.defaultSpecifier] = {
source: modName,
isDefault: true,
};
}
if (item.specifiers.length) {
item.specifiers.forEach((spe) => {
map[spe] = { source: modName };
});
}
});
});
return map;
}
}
/**
* 将节点列表转换为 tree data 嵌套数组
* @param list
*/
function nodeListToTreeData(list: ITangoViewNodeData[]) {
const map: Record<string, ITangoViewNodeData> = {};
list.forEach((item) => {
// 如果不存在,则初始化
if (!map[item.id]) {
map[item.id] = {
...item,
children: [],
};
}
// 是否找到父节点,找到则塞进去
if (item.parentId && map[item.parentId]) {
map[item.parentId].children.push(map[item.id]);
}
});
// 保留根节点
const ret = Object.values(map).filter((item) => !item.parentId);
return ret;
} }
+71 -20
View File
@@ -17,6 +17,7 @@ import {
namesToImportDeclarations, namesToImportDeclarations,
getBlockNameByFilename, getBlockNameByFilename,
getPrivilegeCode, getPrivilegeCode,
prototype2importDeclarationData,
} from '../helpers'; } from '../helpers';
import { DropMethod } from './drop-target'; import { DropMethod } from './drop-target';
import { HistoryMessage, TangoHistory } from './history'; import { HistoryMessage, TangoHistory } from './history';
@@ -312,7 +313,7 @@ export class Workspace extends EventTarget implements IWorkspace {
} }
/** /**
* 添加一组文件到工作区 * 添加一组文件到工作区,如果文件同名,后面的文件会覆盖前面的文件
* @param files * @param files
*/ */
addFiles(files: IFileConfig[] = []) { addFiles(files: IFileConfig[] = []) {
@@ -328,7 +329,6 @@ export class Workspace extends EventTarget implements IWorkspace {
* @param fileType 模块类型 * @param fileType 模块类型
*/ */
addFile(filename: string, code: string, fileType?: FileType) { addFile(filename: string, code: string, fileType?: FileType) {
if (!this.files.has(filename)) {
const moduleType = fileType || inferFileType(filename); const moduleType = fileType || inferFileType(filename);
const props = { const props = {
filename, filename,
@@ -403,6 +403,25 @@ export class Workspace extends EventTarget implements IWorkspace {
this.files.set(filename, module); this.files.set(filename, module);
} }
addServiceFile(serviceName: string, code: string) {
const filename = `/src/services/${serviceName}.js`;
this.addFile(filename, code, FileType.ServiceModule);
const indexServiceModule = this.serviceModules.index;
indexServiceModule?.addImportDeclaration(`./${serviceName}`, []).update();
}
addStoreFile(storeName: string, code: string) {
const filename = `/src/stores/${storeName}.js`;
this.addFile(filename, code);
if (!this.storeEntryModule) {
this.addFile('/src/stores/index.js', '');
}
this.storeEntryModule.addStore(storeName).update();
}
addViewFile(viewName: string, code: string) {
// TODO: implement it
} }
updateFile(filename: string, code: string, shouldFormatCode = false) { updateFile(filename: string, code: string, shouldFormatCode = false) {
@@ -509,7 +528,8 @@ export class Workspace extends EventTarget implements IWorkspace {
/** /**
* 添加新的视图文件 * 添加新的视图文件
* @deprecated * @deprecated 使用 addViewFile 代替
* FIXME: 重构这个逻辑
* @param route 视图名 * @param route 视图名
* @param code 视图代码 * @param code 视图代码
*/ */
@@ -604,16 +624,10 @@ export class Workspace extends EventTarget implements IWorkspace {
/** /**
* 添加新的模型文件 * 添加新的模型文件
* @param name 模型名 * @deprecated 使用 addStoreFile 代替
* @param code 模型代码
*/ */
addStoreModule(name: string, code: string) { addStoreModule(name: string, code: string) {
const filename = `/src/stores/${name}.js`; this.addStoreFile(name, code);
this.addFile(filename, code);
if (!this.storeEntryModule) {
this.addFile('/src/stores/index.js', '');
}
this.storeEntryModule.addStore(name).update();
} }
/** /**
@@ -730,14 +744,29 @@ export class Workspace extends EventTarget implements IWorkspace {
this.serviceModules[moduleName]?.updateBaseConfig(config).update(); this.serviceModules[moduleName]?.updateBaseConfig(config).update();
} }
addDependency(data: any) {
// TODO: implement it to replace addBizComp & addServiceComp
}
/** /**
* 获取 package.json 中的依赖列表 * 获取 package.json 中的依赖列表
* @returns * @returns
* TODO: fix this logic to merge dependencies from package.json and tango.config.json
*/ */
listDependencies() { listDependencies() {
return this.packageJson?.getValue('dependencies'); return this.packageJson?.getValue('dependencies');
} }
getDependency(pkgName: string) {
const packages = this.tangoConfigJson?.getValue('packages');
const dependencies = this.packageJson?.getValue('dependencies'); // 兼容老版本
const detail = {
version: dependencies?.[pkgName],
...(packages?.[pkgName] || {}),
};
return detail;
}
/** /**
* 更新依赖,没有就添加 * 更新依赖,没有就添加
* @param name * @param name
@@ -922,7 +951,7 @@ export class Workspace extends EventTarget implements IWorkspace {
); );
importDeclarations.forEach((importDeclaration) => { importDeclarations.forEach((importDeclaration) => {
this.activeViewModule.updateImportSpecifiers(importDeclaration); this.activeViewModule.updateImportSpecifiersLegacy(importDeclaration);
}); });
this.copyTempNodes.forEach((node) => { this.copyTempNodes.forEach((node) => {
@@ -973,7 +1002,11 @@ export class Workspace extends EventTarget implements IWorkspace {
const sourcePrototype = this.getPrototype(sourceName); const sourcePrototype = this.getPrototype(sourceName);
const newNode = prototype2jsxElement(sourcePrototype); const newNode = prototype2jsxElement(sourcePrototype);
const file = this.getNode(targetNodeId).file; const file = this.getNode(targetNodeId).file;
file.insertChild(targetNodeId, newNode, 'last', sourcePrototype).update(); const { source, specifiers } = prototype2importDeclarationData(sourcePrototype, file.filename);
file
.insertChild(targetNodeId, newNode, 'last')
.addImportSpecifiers(source, specifiers)
.update();
this.history.push({ this.history.push({
message: HistoryMessage.InsertNode, message: HistoryMessage.InsertNode,
data: { data: {
@@ -993,7 +1026,8 @@ export class Workspace extends EventTarget implements IWorkspace {
const sourcePrototype = this.getPrototype(sourceName); const sourcePrototype = this.getPrototype(sourceName);
const newNode = prototype2jsxElement(sourcePrototype); const newNode = prototype2jsxElement(sourcePrototype);
const file = this.getNode(targetNodeId).file; const file = this.getNode(targetNodeId).file;
file.replaceNode(targetNodeId, newNode, sourcePrototype).update(); const { source, specifiers } = prototype2importDeclarationData(sourcePrototype, file.filename);
file.replaceNode(targetNodeId, newNode).addImportSpecifiers(source, specifiers).update();
this.history.push({ this.history.push({
message: HistoryMessage.ReplaceNode, message: HistoryMessage.ReplaceNode,
data: { data: {
@@ -1011,7 +1045,14 @@ export class Workspace extends EventTarget implements IWorkspace {
if (insertedPrototype) { if (insertedPrototype) {
const newNode = prototype2jsxElement(insertedPrototype); const newNode = prototype2jsxElement(insertedPrototype);
const file = this.selectSource.file; const file = this.selectSource.file;
file.insertChild(this.selectSource.first.id, newNode, 'last', insertedPrototype).update(); const { source, specifiers } = prototype2importDeclarationData(
insertedPrototype,
file.filename,
);
file
.insertChild(this.selectSource.first.id, newNode, 'last')
.addImportSpecifiers(source, specifiers)
.update();
this.history.push({ this.history.push({
message: HistoryMessage.InsertNode, message: HistoryMessage.InsertNode,
data: { data: {
@@ -1067,30 +1108,40 @@ export class Workspace extends EventTarget implements IWorkspace {
const targetFile = dropTarget.node?.file; const targetFile = dropTarget.node?.file;
const sourceFile = dragSource.node?.file; const sourceFile = dragSource.node?.file;
// dragSourcePrototype to importDeclarations
const { source, specifiers } = prototype2importDeclarationData(
dragSourcePrototype,
targetFile.filename,
);
let isValidOperation = true; let isValidOperation = true;
switch (dropTarget.method) { switch (dropTarget.method) {
// 直接往目标节点的 children 里添加一个节点 // 直接往目标节点的 children 里添加一个节点
case DropMethod.InsertChild: { case DropMethod.InsertChild: {
targetFile.insertChild(dropTarget.id, newNode, 'last', dragSourcePrototype); targetFile
.insertChild(dropTarget.id, newNode, 'last')
.addImportSpecifiers(source, specifiers);
break; break;
} }
case DropMethod.InsertFirstChild: { case DropMethod.InsertFirstChild: {
targetFile.insertChild(dropTarget.id, newNode, 'first', dragSourcePrototype); targetFile
.insertChild(dropTarget.id, newNode, 'first')
.addImportSpecifiers(source, specifiers);
break; break;
} }
// 往目标节点的后边插入一个节点 // 往目标节点的后边插入一个节点
case DropMethod.InsertAfter: { case DropMethod.InsertAfter: {
targetFile.insertAfter(dropTarget.id, newNode, dragSourcePrototype); targetFile.insertAfter(dropTarget.id, newNode).addImportSpecifiers(source, specifiers);
break; break;
} }
// 往目标节点的前方插入一个节点 // 往目标节点的前方插入一个节点
case DropMethod.InsertBefore: { case DropMethod.InsertBefore: {
targetFile.insertBefore(dropTarget.id, newNode, dragSourcePrototype); targetFile.insertBefore(dropTarget.id, newNode).addImportSpecifiers(source, specifiers);
break; break;
} }
// 替换目标节点 // 替换目标节点
case DropMethod.ReplaceNode: { case DropMethod.ReplaceNode: {
targetFile.replaceNode(dropTarget.id, newNode, dragSourcePrototype); targetFile.replaceNode(dropTarget.id, newNode).addImportSpecifiers(source, specifiers);
break; break;
} }
default: default:
+27 -8
View File
@@ -73,15 +73,40 @@ export interface ITangoViewNodeData<T = JSXElement> {
} }
/** /**
* 导入变量的解析数据 * 导入变量的来源
*/ */
export interface IImportSpecifierData { export interface IImportSpecifierSourceData {
source: string; source: string;
isDefault?: boolean; isDefault?: boolean;
} }
/**
* 解析的导入变量数据
*/
export interface IImportSpecifierData {
/**
* 导入名
*/
importedName?: string;
/**
* 本地名
*/
localName: string;
/**
* 类型
*/
type: 'ImportDefaultSpecifier' | 'ImportSpecifier' | 'ImportNamespaceSpecifier';
}
/**
* 解析的导入语句数据
* FIXME: 需要考虑到有两行重复的语句的情况
*/
export type ImportDeclarationDataType = Record<string, IImportSpecifierData[]>;
/** /**
* 模块导入的参数类型 * 模块导入的参数类型
* @deprecated 使用 IImportSpecifierData 代替
*/ */
export interface IImportDeclarationPayload { export interface IImportDeclarationPayload {
defaultSpecifier?: string; defaultSpecifier?: string;
@@ -89,12 +114,6 @@ export interface IImportDeclarationPayload {
sourcePath: string; sourcePath: string;
} }
export interface IClassPropertyNodeData {
reference: string;
propertyName: string;
propertyBody: string;
}
/** /**
* 服务函数参数类型 * 服务函数参数类型
*/ */
+25
View File
@@ -0,0 +1,25 @@
import { isTangoVariable, isWrappedByExpressionContainer } from '../src/helpers';
describe('assert', () => {
it('isTangoVariable', () => {
expect(isTangoVariable('tango.stores.app.name')).toBeTruthy();
expect(isTangoVariable('tango.stores?.app?.name')).toBeTruthy();
expect(isTangoVariable('tango.stores.app?.name')).toBeTruthy();
// expect(isTangoVariable('tango.copyToClipboard')).toBeTruthy();
});
it('isWrappedByExpressionContainer', () => {
expect(isWrappedByExpressionContainer('{this.foo}')).toBeTruthy();
expect(isWrappedByExpressionContainer('{!false}')).toBeTruthy();
expect(isWrappedByExpressionContainer('{[]}')).toBeTruthy();
expect(isWrappedByExpressionContainer('{{ foo: "bar" }}')).toBeTruthy();
expect(isWrappedByExpressionContainer('{[{ foo: "bar" }]}')).toBeTruthy();
expect(isWrappedByExpressionContainer('{123}')).toBeTruthy();
expect(isWrappedByExpressionContainer('{"hello"}')).toBeTruthy();
expect(isWrappedByExpressionContainer('{ foo: "bar" }')).toBeFalsy();
expect(isWrappedByExpressionContainer('{ type: tango.stores?.homePage?.tabKey }')).toBeFalsy();
expect(isWrappedByExpressionContainer('{ type: tango.stores.homePage.tabKey }')).toBeFalsy();
expect(isWrappedByExpressionContainer('{ foo: "bar" }')).toBeFalsy();
expect(isWrappedByExpressionContainer('{ color: tango.stores.app.color }')).toBeFalsy();
});
});
+31 -4
View File
@@ -3,15 +3,16 @@ import {
serviceConfig2Node, serviceConfig2Node,
isValidCode, isValidCode,
isValidExpressionCode, isValidExpressionCode,
code2expression,
} from '../src/helpers'; } from '../src/helpers';
describe('helpers', () => { describe('ast helpers', () => {
test('isValidCode', () => { it('isValidCode', () => {
expect(isValidCode('() => { hello world }')).toBeFalsy(); expect(isValidCode('() => { hello world }')).toBeFalsy();
expect(isValidCode('function() {}')).toBeFalsy(); expect(isValidCode('function() {}')).toBeFalsy();
}); });
test('isValidExpression', () => { it('isValidExpression', () => {
expect(isValidExpressionCode('() => { }')).toBeTruthy(); expect(isValidExpressionCode('() => { }')).toBeTruthy();
expect(isValidExpressionCode('1')).toBeTruthy(); expect(isValidExpressionCode('1')).toBeTruthy();
expect(isValidExpressionCode('"hello"')).toBeTruthy(); expect(isValidExpressionCode('"hello"')).toBeTruthy();
@@ -20,9 +21,17 @@ describe('helpers', () => {
expect(isValidExpressionCode('[1,2,3]')).toBeTruthy(); expect(isValidExpressionCode('[1,2,3]')).toBeTruthy();
expect(isValidExpressionCode('<div>hello</div>')).toBeTruthy(); expect(isValidExpressionCode('<div>hello</div>')).toBeTruthy();
expect(isValidExpressionCode('<div>hello</div>')).toBeTruthy(); expect(isValidExpressionCode('<div>hello</div>')).toBeTruthy();
expect(isValidExpressionCode('{1}')).toBeFalsy();
expect(isValidExpressionCode('{"1"}')).toBeFalsy();
expect(isValidExpressionCode('{ 1+1 }')).toBeFalsy();
expect(isValidExpressionCode('{<div>aaa</div>}')).toBeFalsy();
expect(isValidExpressionCode('{() => {}}')).toBeFalsy();
expect(isValidExpressionCode('{[1,2,3]}')).toBeFalsy();
expect(isValidExpressionCode('{tango.stores.app.title}')).toBeFalsy();
}); });
test('object2node', () => { it('object2node', () => {
const node = object2node({ const node = object2node({
url: '/api/backend/clientversion/appmarket/list', url: '/api/backend/clientversion/appmarket/list',
method: 'POST', method: 'POST',
@@ -40,4 +49,22 @@ describe('helpers', () => {
} as any); } as any);
expect(node.type).toEqual('ObjectExpression'); expect(node.type).toEqual('ObjectExpression');
}); });
it('code2expression', () => {
expect(code2expression('')).toBeUndefined();
expect(code2expression('{tango.stores.app}')).toBeUndefined();
expect(code2expression('{ type: window.bar }').type).toEqual('ObjectExpression');
expect(code2expression('() => {};').type).toEqual('ArrowFunctionExpression');
expect(code2expression('<Button>hello</Button>').type).toBe('JSXElement');
expect(code2expression('<BreadcrumbItem children="节点名称" />').type).toBe('JSXElement');
const arrayCode = `
[
{ label: 'foo', value: 'foo' },
{ label: 'bar', value: 'bar' },
]
`;
expect(code2expression(arrayCode).type).toEqual('ArrayExpression');
});
}); });
+3 -43
View File
@@ -25,40 +25,6 @@ describe('helpers', () => {
expect(code2ast('function App() {}').type).toEqual('File'); expect(code2ast('function App() {}').type).toEqual('File');
}); });
it('code2expression: null', () => {
expect(code2expression('')).toBeUndefined();
});
it('code2expression: object', () => {
const code = `
{
foo: 'bar',
}
`;
const node = code2expression(code);
expect(node.type).toEqual('ObjectExpression');
});
it('code2expression: arrow function', () => {
expect(code2expression('() => {};').type).toEqual('ArrowFunctionExpression');
});
it('code2expression: list', () => {
const code = `
[
{ label: 'foo', value: 'foo' },
{ label: 'bar', value: 'bar' },
]
`;
const node = code2expression(code);
expect(node.type).toEqual('ArrayExpression');
});
it('code2expression: jsxElement', () => {
const node = code2expression('<Button>hello</Button>');
expect(node.type).toEqual('JSXElement');
});
it('parse jsxElement attributes', () => { it('parse jsxElement attributes', () => {
const node = code2expression( const node = code2expression(
"<XColumn dataIndex='col' enumMap={{ 1: '已解决', 2: '未解决' }} />", "<XColumn dataIndex='col' enumMap={{ 1: '已解决', 2: '未解决' }} />",
@@ -73,11 +39,6 @@ describe('helpers', () => {
}); });
}); });
it('code2expression: closed jsxElement', () => {
const node = code2expression('<BreadcrumbItem children="节点名称" />');
expect(node.type).toEqual('JSXElement');
});
it('value2node: number', () => { it('value2node: number', () => {
expect(value2node(1).type).toEqual('NumericLiteral'); expect(value2node(1).type).toEqual('NumericLiteral');
}); });
@@ -126,7 +87,6 @@ describe('helpers', () => {
}); });
describe('string helpers', () => { describe('string helpers', () => {
it('value2code: empty array', () => { it('value2code: empty array', () => {
expect(value2code([])).toEqual('[]'); expect(value2code([])).toEqual('[]');
}); });
@@ -151,9 +111,9 @@ describe('string helpers', () => {
it('namesToImportDeclarations', () => { it('namesToImportDeclarations', () => {
expect( expect(
namesToImportDeclarations(['Button', 'Box', 'React'], { namesToImportDeclarations(['Button', 'Box', 'React'], {
Button: { package: '@music163/antd' }, Button: { source: '@music163/antd' },
Box: { package: '@music163/antd' }, Box: { source: '@music163/antd' },
React: { package: 'react', isDefault: true }, React: { source: 'react', isDefault: true },
}), }),
).toEqual([ ).toEqual([
{ sourcePath: '@music163/antd', specifiers: ['Button', 'Box'] }, { sourcePath: '@music163/antd', specifiers: ['Button', 'Box'] },
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.5.6](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.5...@music163/tango-designer@0.5.6) (2023-11-23)
**Note:** Version bump only for package @music163/tango-designer
## [0.5.5](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.4...@music163/tango-designer@0.5.5) (2023-11-20)
### Bug Fixes
- pass down sandbox props ([d6ffb50](https://github.com/netease/tango/commit/d6ffb50e85573cb17ee52a797b84de5e6cedb044))
## [0.5.4](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.3...@music163/tango-designer@0.5.4) (2023-11-20)
### Bug Fixes
- refactor parse expression ([#61](https://github.com/netease/tango/issues/61)) ([dbbd1dd](https://github.com/netease/tango/commit/dbbd1dddc75c532b7c9710ab0941c8680100f093))
## [0.5.3](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.2...@music163/tango-designer@0.5.3) (2023-11-16)
### Bug Fixes
- allow override existing file via addFile ([6cf673e](https://github.com/netease/tango/commit/6cf673e7f6f330613e1d6331256a59900e8cbc68))
## [0.5.2](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.1...@music163/tango-designer@0.5.2) (2023-11-02) ## [0.5.2](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.1...@music163/tango-designer@0.5.2) (2023-11-02)
### Bug Fixes ### Bug Fixes
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@music163/tango-designer", "name": "@music163/tango-designer",
"version": "0.5.2", "version": "0.5.6",
"description": "lowcode designer", "description": "lowcode designer",
"keywords": [ "keywords": [
"react" "react"
@@ -33,12 +33,12 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.8.0", "@ant-design/icons": "^4.8.0",
"@music163/request": "^0.1.2", "@music163/request": "^0.1.2",
"@music163/tango-context": "^0.2.4", "@music163/tango-context": "^0.2.7",
"@music163/tango-core": "^0.2.3", "@music163/tango-core": "^0.2.6",
"@music163/tango-helpers": "^0.1.5", "@music163/tango-helpers": "^0.1.8",
"@music163/tango-sandbox": "^0.1.8", "@music163/tango-sandbox": "^0.1.11",
"@music163/tango-setting-form": "^0.2.7", "@music163/tango-setting-form": "^0.2.10",
"@music163/tango-ui": "^0.1.8", "@music163/tango-ui": "^0.1.11",
"antd": "^4.24.2", "antd": "^4.24.2",
"cash-dom": "^8.1.2", "cash-dom": "^8.1.2",
"classnames": "^2.3.2", "classnames": "^2.3.2",
@@ -372,11 +372,11 @@ export function VariableTree({
const isDeletable = node.showDeleteIcon ?? showDeleteIcon; const isDeletable = node.showDeleteIcon ?? showDeleteIcon;
return ( return (
<Box display="flex" justifyContent="space-between" alignItems="center"> <Box display="flex" justifyContent="space-between" alignItems="center">
<Text> <Text flex="1" truncated>
{node.title} {node.title}
{node.type === 'function' && <FunctionOutlined />} {node.type === 'function' && <FunctionOutlined />}
</Text> </Text>
<Box> <Box flex="0 0 72px" textAlign="right">
{isDeletable && ( {isDeletable && (
<Popconfirm <Popconfirm
title="确认删除吗?该操作会导致引用此模型的代码报错,请谨慎操作!" title="确认删除吗?该操作会导致引用此模型的代码报错,请谨慎操作!"
@@ -119,6 +119,7 @@ const PreviewSandbox = observer(
startRoute, startRoute,
injectScript, injectScript,
sandboxType: 'preview', sandboxType: 'preview',
...rest,
}); });
return ( return (
@@ -166,6 +167,7 @@ const DesignSandbox = observer(
startRoute, startRoute,
injectScript, injectScript,
sandboxType: 'design', sandboxType: 'design',
...rest,
}); });
return ( return (
@@ -4,7 +4,6 @@ import { AutoComplete } from 'antd';
import { ActionSelect } from '@music163/tango-ui'; import { ActionSelect } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form'; import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspace, useWorkspaceData } from '@music163/tango-context'; import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { wrapCode } from '@music163/tango-helpers';
import { ExpressionModal } from './expression-setter'; import { ExpressionModal } from './expression-setter';
enum EventAction { enum EventAction {
@@ -84,7 +83,7 @@ export function EventSetter(props: EventSetterProps) {
onCancel={() => setExpModalVisible(false)} onCancel={() => setExpModalVisible(false)}
onOk={(nextValue) => { onOk={(nextValue) => {
setExpModalVisible(false); setExpModalVisible(false);
handleChange(wrapCode(nextValue)); handleChange(`{${nextValue}}`);
}} }}
dataSource={actionVariables} dataSource={actionVariables}
/> />
@@ -1,15 +1,12 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { Box, Text, css } from 'coral-system'; import { Box, Text, css } from 'coral-system';
import { Modal } from 'antd'; import { Modal } from 'antd';
import { isValidExpressionCode, value2expressionCode } from '@music163/tango-core';
import { import {
isVariableString, isValidExpressionCode,
getVariableContent, isWrappedByExpressionContainer,
noop, value2expressionCode,
useBoolean, } from '@music163/tango-core';
getValue, import { getVariableContent, noop, useBoolean, getValue } from '@music163/tango-helpers';
wrapCode,
} from '@music163/tango-helpers';
import { CloseCircleFilled, ExpandAltOutlined } from '@ant-design/icons'; import { CloseCircleFilled, ExpandAltOutlined } from '@ant-design/icons';
import { IconButton, Panel, InputCode } from '@music163/tango-ui'; import { IconButton, Panel, InputCode } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form'; import { FormItemComponentProps } from '@music163/tango-setting-form';
@@ -18,7 +15,7 @@ import { EditableVariableTree, IVariableTreeNode } from '../components';
import { useSandboxQuery } from '../context'; import { useSandboxQuery } from '../context';
export const expressionValueValidate = (value: string) => { export const expressionValueValidate = (value: string) => {
if (isVariableString(value)) { if (isWrappedByExpressionContainer(value)) {
const exp = getVariableContent(value); const exp = getVariableContent(value);
if (!isValidExpressionCode(exp)) { if (!isValidExpressionCode(exp)) {
return '表达式存在语法错误!'; return '表达式存在语法错误!';
@@ -27,7 +24,7 @@ export const expressionValueValidate = (value: string) => {
}; };
export const jsonValueValidate = (value: string) => { export const jsonValueValidate = (value: string) => {
if (isVariableString(value)) { if (isWrappedByExpressionContainer(value)) {
const jsonStr = getVariableContent(value); const jsonStr = getVariableContent(value);
try { try {
JSON.parse(jsonStr); JSON.parse(jsonStr);
@@ -78,15 +75,25 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
const change = useCallback( const change = useCallback(
(code: string) => { (code: string) => {
if (!code) { if (code === valueProp) {
onChange(undefined);
return;
}
if (getVariableContent(code) === value2expressionCode(valueProp)) {
return; return;
} }
onChange(wrapCode(code)); let ret;
if (!code) {
// do nothing
} else if (isWrappedByExpressionContainer(code)) {
ret = code;
} else {
ret = `{${code}}`;
}
if (ret === valueProp) {
return;
}
onChange(ret);
}, },
[valueProp, onChange], [valueProp, onChange],
); );
@@ -189,15 +196,12 @@ export function ExpressionModal({
<Modal <Modal
closable={false} closable={false}
destroyOnClose destroyOnClose
width="70%" width="60%"
open={visible} open={visible}
onCancel={onCancel} onCancel={onCancel}
onOk={() => { onOk={() => {
onOk(exp); onOk(exp);
}} }}
okButtonProps={{
disabled: !!error,
}}
bodyStyle={{ bodyStyle={{
padding: 0, padding: 0,
}} }}
@@ -6,13 +6,13 @@ import { PlayCircleOutlined } from '@ant-design/icons';
import { Form, InputCode, Panel, JsonView, Search } from '@music163/tango-ui'; import { Form, InputCode, Panel, JsonView, Search } from '@music163/tango-ui';
import { import {
isNil, isNil,
isVariableString,
getVariableContent, getVariableContent,
isValidFunctionCode, isValidFunctionCode,
logger, logger,
code2object, code2object,
filterTreeData, filterTreeData,
} from '@music163/tango-helpers'; } from '@music163/tango-helpers';
import { isWrappedByExpressionContainer } from '@music163/tango-core';
import { useSandboxQuery } from '../../context'; import { useSandboxQuery } from '../../context';
import { VariableTree } from '../../components'; import { VariableTree } from '../../components';
@@ -187,7 +187,10 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
const shapeValues = { ...val }; const shapeValues = { ...val };
delete shapeValues.type; delete shapeValues.type;
// 兼容旧版,如果 formatter 包裹了 {} 则删掉首尾 // 兼容旧版,如果 formatter 包裹了 {} 则删掉首尾
if (shapeValues.formatter && isVariableString(shapeValues.formatter)) { if (
shapeValues.formatter &&
isWrappedByExpressionContainer(shapeValues.formatter)
) {
shapeValues.formatter = getVariableContent(shapeValues.formatter); shapeValues.formatter = getVariableContent(shapeValues.formatter);
} }
return shapeValues; return shapeValues;
@@ -275,7 +278,7 @@ function ServiceDetailForm({
{...formProps} {...formProps}
> >
<Form.Item <Form.Item
label="所属模块" label="命名空间"
name="moduleName" name="moduleName"
rules={[ rules={[
{ {
@@ -3,11 +3,14 @@ import cx from 'classnames';
import { Box, HTMLCoralProps, css } from 'coral-system'; import { Box, HTMLCoralProps, css } from 'coral-system';
import { Resizable, ResizeHandle } from 'react-resizable'; import { Resizable, ResizeHandle } from 'react-resizable';
export interface ResizableBoxProps extends HTMLCoralProps<'div'> { export interface ResizableBoxProps {
width?: number; width?: number;
height?: number; height?: number;
resizeHandlePosition?: 'left' | 'right' | 'top' | 'bottom'; resizeHandlePosition?: 'left' | 'right' | 'top' | 'bottom';
axis?: 'x' | 'y'; axis?: 'x' | 'y';
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
} }
const resizeHandleStyle = (axis: ResizableBoxProps['axis'], barStyle: HTMLCoralProps<'div'>) => css` const resizeHandleStyle = (axis: ResizableBoxProps['axis'], barStyle: HTMLCoralProps<'div'>) => css`
+27 -17
View File
@@ -75,6 +75,8 @@ export interface SidebarPanelItemProps
} }
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) { function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) {
const designer = useDesigner();
const items = useMemo(() => { const items = useMemo(() => {
const ret: Record<string, SidebarPanelItemProps> = {}; const ret: Record<string, SidebarPanelItemProps> = {};
React.Children.forEach(children, (child) => { React.Children.forEach(children, (child) => {
@@ -90,20 +92,6 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, childre
return ret; return ret;
}, [children]); }, [children]);
const designer = useDesigner();
const panel = items[designer.activeSidebarPanel];
const floatPanelStyle: any = panel?.isFloat
? {
position: 'absolute',
left: '50px',
top: 0,
zIndex: 1000,
height: '100%',
boxShadow: 'var(--tango-shadows-lowRight)',
}
: {};
const panelWidth = typeof panel?.width === 'number' ? panel?.width : defaultPanelWidth;
return ( return (
<Box display="flex" flexShrink={0} css={sidebarStyle} className="SidebarPanel"> <Box display="flex" flexShrink={0} css={sidebarStyle} className="SidebarPanel">
<Box className="SidebarPanelBar" display="flex" flexDirection="column" position="relative"> <Box className="SidebarPanelBar" display="flex" flexDirection="column" position="relative">
@@ -142,8 +130,29 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, childre
{footer} {footer}
</Box> </Box>
</Box> </Box>
{panel ? ( {Object.keys(items).map((key) => {
<ResizableBox key={panel.label} width={panelWidth} style={floatPanelStyle}> const panel = items[key];
const floatPanelStyle: any = panel?.isFloat
? {
position: 'absolute',
left: '50px',
top: 0,
zIndex: 1000,
height: '100%',
boxShadow: 'var(--tango-shadows-lowRight)',
}
: {};
const panelWidth = typeof panel?.width === 'number' ? panel?.width : defaultPanelWidth;
return (
<ResizableBox
key={panel.label}
width={panelWidth}
style={{
display: designer.activeSidebarPanel === key ? 'block' : 'none',
...floatPanelStyle,
}}
>
<SidebarPanelExpandedContent <SidebarPanelExpandedContent
isFloat={panel.isFloat} isFloat={panel.isFloat}
closeable={panel.isFloat} closeable={panel.isFloat}
@@ -154,7 +163,8 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, childre
{panel.children} {panel.children}
</SidebarPanelExpandedContent> </SidebarPanelExpandedContent>
</ResizableBox> </ResizableBox>
) : null} );
})}
</Box> </Box>
); );
} }
@@ -53,7 +53,7 @@ export const VariablePanel = observer(
existNames={storeNames} existNames={storeNames}
onCancel={off} onCancel={off}
onSubmit={(values) => { onSubmit={(values) => {
workspace.addStoreModule(values.name, newStoreTemplate); workspace.addStoreFile(values.name, newStoreTemplate);
off(); off();
}} }}
/> />
+13 -3
View File
@@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import { Group } from 'coral-system'; import { Group } from 'coral-system';
import { Modal } from 'antd';
import { ToggleButton } from '@music163/tango-ui'; import { ToggleButton } from '@music163/tango-ui';
import { observer, useDesigner } from '@music163/tango-context'; import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
export const ModeSwitchTool = observer(() => { export const ModeSwitchTool = observer(() => {
const workspace = useWorkspace();
const designer = useDesigner(); const designer = useDesigner();
return ( return (
<Group attached> <Group attached>
@@ -13,7 +15,7 @@ export const ModeSwitchTool = observer(() => {
onClick={() => { onClick={() => {
designer.setActiveView('design'); designer.setActiveView('design');
}} }}
tip="设计视图" tooltip="设计视图"
> >
</ToggleButton> </ToggleButton>
@@ -23,8 +25,16 @@ export const ModeSwitchTool = observer(() => {
onClick={() => { onClick={() => {
designer.setActiveView('code'); // 切换到源码视图 designer.setActiveView('code'); // 切换到源码视图
designer.setActiveSidebarPanel(''); // 关闭左侧面板 designer.setActiveSidebarPanel(''); // 关闭左侧面板
if (workspace.activeFile !== workspace.activeViewFile) {
Modal.confirm({
title: '当前打开的文件与视图不匹配,是否切换到当前视图对应的文件?',
onOk: () => {
workspace.setActiveFile(workspace.activeViewFile);
},
});
}
}} }}
tip="源码视图" tooltip="源码视图"
> >
</ToggleButton> </ToggleButton>
+19
View File
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.7...@music163/tango-helpers@0.1.8) (2023-11-23)
### Bug Fixes
- refactor update import specifiers ([#68](https://github.com/netease/tango/issues/68)) ([558f2cd](https://github.com/netease/tango/commit/558f2cd0a692c6bbc866d08250d25e2619af183f))
## [0.1.7](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.6...@music163/tango-helpers@0.1.7) (2023-11-20)
### Bug Fixes
- refactor parse expression ([#61](https://github.com/netease/tango/issues/61)) ([dbbd1dd](https://github.com/netease/tango/commit/dbbd1dddc75c532b7c9710ab0941c8680100f093))
## [0.1.6](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.5...@music163/tango-helpers@0.1.6) (2023-11-16)
### Bug Fixes
- isVariableString ([b9403cf](https://github.com/netease/tango/commit/b9403cfec668b2717a68e92a6f837d5a88096c7c))
- parse variables from view ([089a482](https://github.com/netease/tango/commit/089a482f750e9d9a7743a6641d6e39989347d318))
## [0.1.5](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.4...@music163/tango-helpers@0.1.5) (2023-11-02) ## [0.1.5](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.4...@music163/tango-helpers@0.1.5) (2023-11-02)
**Note:** Version bump only for package @music163/tango-helpers **Note:** Version bump only for package @music163/tango-helpers
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@music163/tango-helpers", "name": "@music163/tango-helpers",
"version": "0.1.5", "version": "0.1.8",
"description": "Shared types, helpers, and hooks of tango-apps", "description": "Shared types, helpers, and hooks of tango-apps",
"keywords": [ "keywords": [
"shared", "shared",
+19 -5
View File
@@ -182,6 +182,8 @@ const templatePattern = /^{(.+)}$/s;
/** /**
* 判断给定字符串是否为变量字符串 * 判断给定字符串是否为变量字符串
* @deprecated 使用 isWrappedByExpressionContainer 代替
*
* @example {[]} * @example {[]}
* @example {{}} * @example {{}}
* @example {this.foo} * @example {this.foo}
@@ -191,6 +193,7 @@ const templatePattern = /^{(.+)}$/s;
*/ */
export function isVariableString(str: string) { export function isVariableString(str: string) {
// 先检查是否是简单的对象 // 先检查是否是简单的对象
// FIXME: 这里有问题,如果代码中有引用,会被误判
if (code2object(str)) { if (code2object(str)) {
return false; return false;
} }
@@ -205,6 +208,8 @@ export function isVariableString(str: string) {
* @example "hello" => {"hello"} * @example "hello" => {"hello"}
* @example () => {} => {() => {}} * @example () => {} => {() => {}}
* *
* @deprecated 有问题,不要使用
*
* @param code 输入代码 * @param code 输入代码
* @returns 加上花括号后的代码 * @returns 加上花括号后的代码
*/ */
@@ -238,17 +243,26 @@ export function getVariableContent(str: string) {
return str; return str;
} }
// 提供给代码执行环境的全局变量
const patchCode = `
var tango = {
stores: {},
services: {},
config: {},
refs: {},
};
`;
/** /**
* 将代码放到函数体中进行执行 * 将代码放到函数体中进行执行
* @param code * @param code
* @returns 函数执行的结果 * @returns 函数执行的结果
*/ */
export function executeCodeAsFunctionBody(code: string) { export function runCode(code: string) {
let ret; let ret;
try { try {
// eslint-disable-next-line no-new-func // eslint-disable-next-line no-new-func
ret = new Function(code); ret = new Function(`${patchCode}\n return ${code}`)();
ret = ret();
} catch (err) { } catch (err) {
// ignore error // ignore error
} }
@@ -261,14 +275,14 @@ const objectWrapperPattern = /^[{\[].*[}\]]$/s;
/** /**
* 将代码片段转成 js 对象 * 将代码片段转成 js 对象
* @param code 代码文本 * @param code 代码文本
* @param isStrict 是否为严格模式 * @param isStrict 是否为严格模式(是否废弃)
* @returns * @returns
*/ */
export function code2object(code: string, isStrict = true) { export function code2object(code: string, isStrict = true) {
// 非严格模式直接执行 // 非严格模式直接执行
// 严格模式下需检测 code 是一个对象 // 严格模式下需检测 code 是一个对象
if (!isStrict || (isStrict && objectWrapperPattern.test(code))) { if (!isStrict || (isStrict && objectWrapperPattern.test(code))) {
const ret = executeCodeAsFunctionBody(`return ${code}`); const ret = runCode(code);
return typeof ret === 'object' ? ret : undefined; return typeof ret === 'object' ? ret : undefined;
} }
return code; return code;
-143
View File
@@ -33,94 +33,11 @@ export type OptionType = {
relatedImports?: string[]; relatedImports?: string[];
}; };
type TangoSchemaTreeNodePropValueBaseType = {
value: string | number | boolean;
rawCode: string;
};
type TangoSchemaTreeNodePropValueSlotType = {
type: 'Slot';
value: TangoSchemaTreeNodeType;
};
type TangoSchemaTreeNodePropValueType =
| string
| number
| boolean
| object
| TangoSchemaTreeNodePropValueBaseType
| TangoSchemaTreeNodePropValueSlotType;
export type TangoSchemaTreeNodeType = {
/**
* 节点唯一标识
*/
id?: string;
/**
* 节点组件名
*/
component?: string;
/**
* 节点属性集合
*/
props?: Record<string, TangoSchemaTreeNodePropValueType>;
/**
* 节点是否渲染
* TODO: 支持变量表达式
*/
isRender?: boolean;
/**
* 子节点
*/
children?: TangoSchemaTreeNodeType[];
};
/** /**
* 服务函数类型 * 服务函数类型
*/ */
export type ServiceFunctionType = (...args: any[]) => Promise<any>; export type ServiceFunctionType = (...args: any[]) => Promise<any>;
export type TangoRemoteServicesType = {
/**
* 图片服务
*/
ImageService?: {
listMy: ServiceFunctionType;
listFav: ServiceFunctionType;
listPub: ServiceFunctionType;
};
/**
* 数据源服务
*/
OxService?: {
listApp: ServiceFunctionType;
listBranch: ServiceFunctionType;
listApi: ServiceFunctionType;
listApiByPage: ServiceFunctionType;
};
/**
* 依赖面板服务
*/
DependencyService?: {
listBizDependencies: ServiceFunctionType;
listBaseDependencies: ServiceFunctionType;
listPackageVersions: ServiceFunctionType;
};
RiddleService?: {
listBlock: ServiceFunctionType;
postBlock: ServiceFunctionType;
updateBlock: ServiceFunctionType;
};
/**
* AIGC 服务
*/
GptService?: {
codeCompletion?: ServiceFunctionType;
textCompletion?: ServiceFunctionType;
textEmbedding?: ServiceFunctionType;
};
};
export type RelatedDependencyType = { name: string; version: string }; export type RelatedDependencyType = { name: string; version: string };
/** /**
@@ -163,63 +80,3 @@ export type SetterOnChangeDetailType = {
relatedImports?: string[]; relatedImports?: string[];
isRawCode?: boolean; isRawCode?: boolean;
}; };
/**
* 智能向导生成类型
*/
export enum WizardType {
NEW = 'new',
REPLACE = 'replace',
PREVIEW = 'preview',
}
/**
* 智能向导OX模型定义
*/
export interface IOXModel {
key: string; // 唯一标识 (字段名加哈希值)
name: string; // 字段名
description: string; // 字段描述
basicDataModelName: 'List' | 'Integer' | 'int' | 'String' | 'long' | 'boolean' | 'ENUM'; // 字段类型
dataModelFieldDTOList: IOXModel[];
type: 'ARRAY' | 'BASIC' | 'CLASS' | 'GENERIC' | 'ENUM' | 'MAP';
extra: IOXModel;
extras: IOXModel[];
linkedDataModelName: String;
linkedModelDTO: {
type: 'ARRAY' | 'BASIC' | 'CLASS' | 'GENERIC' | 'ENUM' | 'MAP';
fieldDTOList: IOXModel[];
};
paramType: 'RequestParam' | 'JsonRequestParam'; // 请求传参类型
}
/**
* 通用智能向导
*/
type ChildType = Array<Record<string, any>>;
export type IDataProviderFormProps = {
loadData: string;
payload: Record<string, any>;
model: string;
formatter: (res: any) => any;
};
export interface CommonGuideFormValueType {
fatherComponentName: string;
fatherProps: Record<string, any>;
childComponentName: string;
children: ChildType;
dataProviderProps: IDataProviderFormProps;
childPropsConfig: {
// 字段名对应属性名
fieldName: string;
// 字段注释对应属性名
fieldDescriptionName: string;
// 字段值类型对应属性类型 (暂时用不到)
fieldType?: string;
};
}
/**
* end
*/
+2 -2
View File
@@ -15,8 +15,8 @@ export type PartialRecord<K extends keyof any, T> = {
[P in K]?: T; [P in K]?: T;
}; };
export type ReactComponentProps = { export interface ReactComponentProps {
children?: React.ReactNode; children?: React.ReactNode;
style?: React.CSSProperties; style?: React.CSSProperties;
className?: string; className?: string;
}; }
+3 -3
View File
@@ -42,9 +42,9 @@ describe('string', () => {
expect(isVariableString('{value}')).toBeTruthy(); expect(isVariableString('{value}')).toBeTruthy();
expect(isVariableString('{"hello"}')).toBeTruthy(); expect(isVariableString('{"hello"}')).toBeTruthy();
expect(isVariableString('{ foo: "bar" }')).toBeFalsy(); expect(isVariableString('{ foo: "bar" }')).toBeFalsy();
expect(isVariableString('{{ foo: "bar" }}')).toBeTruthy(); expect(isVariableString('{ type: tango.stores?.homePage?.tabKey }')).toBeFalsy();
expect(isVariableString('{() => {}}')).toBeTruthy(); // expect(isVariableString('{ type: tango.stores.homePage.tabKey }')).toBeFalsy(); // FIXME: fix this case
expect(isVariableString('{() => { console.log(); }}')).toBeTruthy(); expect(isVariableString('{ foo: "bar" }')).toBeFalsy();
}); });
it('getVariableContent', () => { it('getVariableContent', () => {
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.11](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.10...@music163/tango-sandbox@0.1.11) (2023-11-23)
**Note:** Version bump only for package @music163/tango-sandbox
## [0.1.10](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.9...@music163/tango-sandbox@0.1.10) (2023-11-20)
**Note:** Version bump only for package @music163/tango-sandbox
## [0.1.9](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.8...@music163/tango-sandbox@0.1.9) (2023-11-16)
### Bug Fixes
- fix sandbox props ([afb94d6](https://github.com/netease/tango/commit/afb94d6a304f217df24a5d3d986dec1670cd29a9))
## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.7...@music163/tango-sandbox@0.1.8) (2023-11-02) ## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.7...@music163/tango-sandbox@0.1.8) (2023-11-02)
**Note:** Version bump only for package @music163/tango-sandbox **Note:** Version bump only for package @music163/tango-sandbox
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@music163/tango-sandbox", "name": "@music163/tango-sandbox",
"version": "0.1.8", "version": "0.1.11",
"description": "sandbox of tango apps", "description": "sandbox of tango apps",
"author": "wwsun <ww.sun@outlook.com>", "author": "wwsun <ww.sun@outlook.com>",
"homepage": "", "homepage": "",
@@ -29,8 +29,8 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.8.0", "@ant-design/icons": "^4.8.0",
"@music163/tango-core": "^0.2.3", "@music163/tango-core": "^0.2.6",
"@music163/tango-helpers": "^0.1.5", "@music163/tango-helpers": "^0.1.8",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"lodash.isequal": "4.5.0", "lodash.isequal": "4.5.0",
"react-frame-component": "^5.2.4" "react-frame-component": "^5.2.4"
+3 -1
View File
@@ -21,6 +21,7 @@ import { IFiles, CodeSandboxState, CodeSandboxProps, UnsubscribeFunction } from
// }; // };
export class CodeSandbox extends React.Component<CodeSandboxProps, CodeSandboxState> { export class CodeSandbox extends React.Component<CodeSandboxProps, CodeSandboxState> {
static defaultProps = { static defaultProps = {
template: 'create-react-app',
skipEval: false, skipEval: false,
eventHandlers: {}, eventHandlers: {},
externalResources: [] as string[], externalResources: [] as string[],
@@ -181,9 +182,10 @@ export class CodeSandbox extends React.Component<CodeSandboxProps, CodeSandboxSt
}; };
render() { render() {
const { style } = this.props;
const { loading, status, iframeId } = this.state; const { loading, status, iframeId } = this.state;
return ( return (
<div className="CodeSandbox" style={{ position: 'relative', height: '100%' }}> <div className="CodeSandbox" style={{ position: 'relative', height: '100%', ...style }}>
{loading ? <Loading status={status} /> : null} {loading ? <Loading status={status} /> : null}
<iframe <iframe
ref={this.setupFrame} ref={this.setupFrame}
+1
View File
@@ -504,6 +504,7 @@ export interface CodeSandboxProps {
isFile: (path: string) => Promise<boolean>; isFile: (path: string) => Promise<boolean>;
readFile: (path: string) => Promise<string>; readFile: (path: string) => Promise<string>;
}; };
style?: React.CSSProperties;
} }
export interface ViteSandboxProps { export interface ViteSandboxProps {
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.10](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.9...@music163/tango-setting-form@0.2.10) (2023-11-23)
**Note:** Version bump only for package @music163/tango-setting-form
## [0.2.9](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.8...@music163/tango-setting-form@0.2.9) (2023-11-20)
### Bug Fixes
- refactor parse expression ([#61](https://github.com/netease/tango/issues/61)) ([dbbd1dd](https://github.com/netease/tango/commit/dbbd1dddc75c532b7c9710ab0941c8680100f093))
## [0.2.8](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.7...@music163/tango-setting-form@0.2.8) (2023-11-16)
**Note:** Version bump only for package @music163/tango-setting-form
## [0.2.7](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.6...@music163/tango-setting-form@0.2.7) (2023-11-02) ## [0.2.7](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.6...@music163/tango-setting-form@0.2.7) (2023-11-02)
**Note:** Version bump only for package @music163/tango-setting-form **Note:** Version bump only for package @music163/tango-setting-form
+4 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@music163/tango-setting-form", "name": "@music163/tango-setting-form",
"version": "0.2.7", "version": "0.2.10",
"description": "setting form of tango-apps", "description": "setting form of tango-apps",
"author": "wwsun <ww.sun@outlook.com>", "author": "wwsun <ww.sun@outlook.com>",
"homepage": "", "homepage": "",
@@ -28,8 +28,9 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.8.0", "@ant-design/icons": "^4.8.0",
"@music163/tango-helpers": "^0.1.5", "@music163/tango-core": "^0.2.6",
"@music163/tango-ui": "^0.1.8", "@music163/tango-helpers": "^0.1.8",
"@music163/tango-ui": "^0.1.11",
"antd": "^4.24.2", "antd": "^4.24.2",
"coral-system": "^1.0.5", "coral-system": "^1.0.5",
"mobx": "6.9.0", "mobx": "6.9.0",
+2 -2
View File
@@ -4,10 +4,10 @@ import { observer } from 'mobx-react-lite';
import { import {
clone, clone,
ComponentPropType, ComponentPropType,
isVariableString,
SetterOnChangeDetailType, SetterOnChangeDetailType,
useBoolean, useBoolean,
} from '@music163/tango-helpers'; } from '@music163/tango-helpers';
import { isWrappedByExpressionContainer } from '@music163/tango-core';
import { IconFont, ToggleButton } from '@music163/tango-ui'; import { IconFont, ToggleButton } from '@music163/tango-ui';
import { QuestionCircleOutlined } from '@ant-design/icons'; import { QuestionCircleOutlined } from '@ant-design/icons';
import { InputProps, Tooltip } from 'antd'; import { InputProps, Tooltip } from 'antd';
@@ -83,7 +83,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
const value = toJS(field.value ?? defaultValue); const value = toJS(field.value ?? defaultValue);
const disableVariableSetter = disableSwitchExpressionSetter ?? disableVariableSetterProp; const disableVariableSetter = disableSwitchExpressionSetter ?? disableVariableSetterProp;
const [isVariable, { toggle: toggleIsVariable }] = useBoolean( const [isVariable, { toggle: toggleIsVariable }] = useBoolean(
() => !disableVariableSetter && isVariableString(value), () => !disableVariableSetter && isWrappedByExpressionContainer(value),
); );
const setterName = isVariable ? 'expressionSetter' : setter; const setterName = isVariable ? 'expressionSetter' : setter;
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.11](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.10...@music163/tango-ui@0.1.11) (2023-11-23)
**Note:** Version bump only for package @music163/tango-ui
## [0.1.10](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.9...@music163/tango-ui@0.1.10) (2023-11-20)
**Note:** Version bump only for package @music163/tango-ui
## [0.1.9](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.8...@music163/tango-ui@0.1.9) (2023-11-16)
**Note:** Version bump only for package @music163/tango-ui
## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.7...@music163/tango-ui@0.1.8) (2023-11-02) ## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.7...@music163/tango-ui@0.1.8) (2023-11-02)
### Bug Fixes ### Bug Fixes
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@music163/tango-ui", "name": "@music163/tango-ui",
"version": "0.1.8", "version": "0.1.11",
"description": "ui widgets of tango", "description": "ui widgets of tango",
"keywords": [ "keywords": [
"react", "react",
@@ -38,7 +38,7 @@
"@codemirror/lang-javascript": "^6.2.1", "@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lint": "^6.4.2", "@codemirror/lint": "^6.4.2",
"@codemirror/search": "^6.5.4", "@codemirror/search": "^6.5.4",
"@music163/tango-helpers": "^0.1.5", "@music163/tango-helpers": "^0.1.8",
"@uiw/react-codemirror": "^4.21.20", "@uiw/react-codemirror": "^4.21.20",
"antd": "^4.24.2", "antd": "^4.24.2",
"antd-easy-form": "^1.1.3", "antd-easy-form": "^1.1.3",