mirror of
https://github.com/NetEase/tango.git
synced 2026-09-01 04:19:05 +08:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e21d5ee28b | |||
| caf0a8d7d9 | |||
| 5c84bc8571 | |||
| f14106c760 | |||
| 54fee79f1b | |||
| a8357f45b8 | |||
| 9db6358588 | |||
| 5bc2fb2d5a | |||
| 05a0f8f490 | |||
| c884712331 | |||
| e35ff792a0 | |||
| 138cbe9b20 | |||
| 979aa34259 | |||
| 76a5a2c659 | |||
| 53ebd89c9a | |||
| 3980810502 | |||
| accd826376 | |||
| 5ca1118f88 | |||
| 14cd4c54a1 | |||
| 7f46137c56 | |||
| 1db52948db | |||
| 2550411c1c | |||
| 640859d36e | |||
| d91763379c | |||
| 588861f649 | |||
| 91b53da38e | |||
| 9b0ada94a0 | |||
| f604996e56 | |||
| fb26540f27 | |||
| 558f2cd0a6 | |||
| 8a1234e565 | |||
| 630c486eb8 | |||
| 324f450851 | |||
| d6ffb50e85 | |||
| 2906efc8fc | |||
| 2259fe77fc | |||
| dbbd1dddc7 |
@@ -2,11 +2,12 @@
|
||||
"extends": ["eslint-config-ali/typescript/react", "prettier"],
|
||||
"ignorePatterns": ["**/dist/**/*", "**/lib/**/*", "**/node_modules/**/*", "scripts/**/*"],
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-definitions": "off",
|
||||
"@typescript-eslint/dot-notation": "off",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"import/no-cycle": "off",
|
||||
"no-nested-ternary": "off",
|
||||
"no-useless-return":"off",
|
||||
"no-useless-return": "off",
|
||||
"no-param-reassign": "off",
|
||||
"prefer-destructuring": "off"
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ gantt
|
||||
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
|
||||
|
||||
section Builder Release
|
||||
Alpha version :active, des1, 2023-08-10, 2023-08-30
|
||||
Beta version : des2, 2023-09-01, 2023-09-30
|
||||
1.0 RC : des3, after des2, 40d
|
||||
1.0 version : des4, after des3, 21d
|
||||
Alpha version :active, m1, 2023-08-10, 2023-08-30
|
||||
Beta version : m2, 2023-09-01, 2023-09-30
|
||||
1.0 RC : m3, 2023-10-01, 2023-12-15
|
||||
1.0 version : m4, after m3, 10d
|
||||
```
|
||||
|
||||
## 💻 Development
|
||||
@@ -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.
|
||||
|
||||
## 📣 Product Promotion
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
This project is licensed under the terms of the [MIT license](./LICENSE)
|
||||
|
||||
+15
-4
@@ -45,10 +45,10 @@ gantt
|
||||
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
|
||||
|
||||
section Builder Release
|
||||
Alpha version :active, des1, 2023-08-10, 2023-08-30
|
||||
Beta version : des2, 2023-09-01, 2023-09-30
|
||||
1.0 RC : des3, after des2, 40d
|
||||
1.0 version : des4, after des3, 21d
|
||||
Alpha version :active, m1, 2023-08-10, 2023-08-30
|
||||
Beta version : m2, 2023-09-01, 2023-09-30
|
||||
1.0 RC : m3, 2023-10-01, 2023-12-15
|
||||
1.0 version : m4, after m3, 10d
|
||||
```
|
||||
|
||||
## 💻 开发
|
||||
@@ -97,6 +97,17 @@ yarn start
|
||||
|
||||
感谢 CodeSandbox 提供的 [Sandpack](https://sandpack.codesandbox.io/) 项目,为 Tango 提供了强大的基于浏览器的代码构建与执行能力。
|
||||
|
||||
## 📣 产品推广
|
||||
|
||||

|
||||
|
||||
不要再浪费时间还原 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)
|
||||
|
||||
@@ -75,15 +75,6 @@ const tangoConfigJson = {
|
||||
},
|
||||
};
|
||||
|
||||
const appJson: any = {
|
||||
pages: [
|
||||
{
|
||||
path: '/',
|
||||
name: '首页',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const routesCode = `
|
||||
import Index from "./pages/list";
|
||||
|
||||
@@ -151,6 +142,9 @@ import {
|
||||
Input,
|
||||
FormilyForm,
|
||||
} from "@music163/antd";
|
||||
import { Space } from '@music163/antd';
|
||||
import { MyButton } from '../components/button';
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
@@ -158,8 +152,11 @@ class App extends React.Component {
|
||||
<Section title="Section Title">
|
||||
</Section>
|
||||
<Section>
|
||||
<Space>
|
||||
<MyButton />
|
||||
<Button>button</Button>
|
||||
<Input />
|
||||
</Space>
|
||||
</Section>
|
||||
</Page>
|
||||
);
|
||||
@@ -168,6 +165,16 @@ class App extends React.Component {
|
||||
export default definePage(App);
|
||||
`;
|
||||
|
||||
const componentsButtonCode = `
|
||||
import React from 'react';
|
||||
|
||||
export function MyButton() {
|
||||
return <button>my button</button>
|
||||
}
|
||||
`;
|
||||
|
||||
const componentsPrototypeCode = ``;
|
||||
|
||||
const storeApp = `
|
||||
import { defineStore } from '@music163/tango-boot';
|
||||
|
||||
@@ -184,6 +191,13 @@ import { defineServices } from '@music163/tango-boot';
|
||||
import './sub';
|
||||
|
||||
export default defineServices({
|
||||
longLongLongLongLongLongLongLongGet: {
|
||||
url: 'https://nei.hz.netease.com/api/apimock-v2/cc974ffbaa7a85c77f30e4ce67deb67f/api/getUserProfile',
|
||||
formatter: res => res.data,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
get: {
|
||||
url: 'https://nei.hz.netease.com/api/apimock-v2/cc974ffbaa7a85c77f30e4ce67deb67f/api/getUserProfile',
|
||||
formatter: res => res.data,
|
||||
@@ -239,13 +253,14 @@ p {
|
||||
|
||||
export const sampleFiles = [
|
||||
{ filename: '/package.json', code: JSON.stringify(packageJson) },
|
||||
{ filename: '/appJson.json', code: JSON.stringify(appJson) },
|
||||
{ filename: '/tango.config.json', code: JSON.stringify(tangoConfigJson) },
|
||||
{ filename: '/README.md', code: '# readme' },
|
||||
{ filename: '/src/index.less', code: lessCode },
|
||||
{ filename: '/src/style.css', code: cssCode },
|
||||
{ filename: '/src/index.js', code: entryCode },
|
||||
{ filename: '/src/pages/list.js', code: viewHomeCode },
|
||||
{ filename: '/src/components/button.js', code: componentsButtonCode },
|
||||
{ filename: '/src/components/prototype.js', code: componentsPrototypeCode },
|
||||
{ filename: '/src/routes.js', code: routesCode },
|
||||
{ filename: '/src/stores/index.js', code: storeIndexCode },
|
||||
{ filename: '/src/stores/app.js', code: storeApp },
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
CodeEditor,
|
||||
Sandbox,
|
||||
DndQuery,
|
||||
themeLight,
|
||||
} from '@music163/tango-designer';
|
||||
import { createEngine, Workspace } from '@music163/tango-core';
|
||||
import { Logo, ProjectDetail, bootHelperVariables } from './share';
|
||||
@@ -52,6 +53,7 @@ export default function App() {
|
||||
const [menuData, setMenuData] = useState(false);
|
||||
return (
|
||||
<Designer
|
||||
theme={themeLight}
|
||||
engine={engine}
|
||||
config={{
|
||||
customActionVariables: bootHelperVariables,
|
||||
@@ -66,6 +68,7 @@ export default function App() {
|
||||
<Box px="l">
|
||||
<Toolbar>
|
||||
<Toolbar.Item key="routeSwitch" placement="left" />
|
||||
<Toolbar.Item key="history" placement="left" />
|
||||
<Toolbar.Item key="preview" placement="left" />
|
||||
<Toolbar.Item key="modeSwitch" placement="right" />
|
||||
<Toolbar.Item key="togglePanel" placement="right" />
|
||||
@@ -80,7 +83,6 @@ export default function App() {
|
||||
}
|
||||
>
|
||||
<Sidebar>
|
||||
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
|
||||
<Sidebar.Item
|
||||
key="components"
|
||||
label="组件"
|
||||
@@ -90,6 +92,7 @@ export default function App() {
|
||||
loading: menuLoading,
|
||||
}}
|
||||
/>
|
||||
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
|
||||
<Sidebar.Item
|
||||
key="variables"
|
||||
label="变量"
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"dependencies": {
|
||||
"@music163/tango-setting-form": "*",
|
||||
"@music163/tango-ui": "*",
|
||||
"mobx": "6.9.0",
|
||||
"mobx-react-lite": "4.0.2"
|
||||
"mobx": "6.12.0",
|
||||
"mobx-react-lite": "4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^6.5.14",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { EditableVariableTree, FormModel, SettingForm } from '@music163/tango-setting-form';
|
||||
import { ComponentPrototypeType, getValue } from '@music163/tango-helpers';
|
||||
import { createServices } from '@music163/request';
|
||||
import React from 'react';
|
||||
import { FormModel, SettingForm } from '@music163/tango-setting-form';
|
||||
import { ComponentPrototypeType } from '@music163/tango-helpers';
|
||||
import { Box } from 'coral-system';
|
||||
import { JsonView } from '@music163/tango-ui';
|
||||
import { toJS } from 'mobx';
|
||||
@@ -9,51 +8,9 @@ import { observer } from 'mobx-react-lite';
|
||||
import { Card } from 'antd';
|
||||
|
||||
export default {
|
||||
title: 'SettingForm/ Setters',
|
||||
title: 'SettingForm',
|
||||
};
|
||||
|
||||
const modelVariables = [
|
||||
{
|
||||
title: 'stores',
|
||||
key: 'stores',
|
||||
selectable: false,
|
||||
children: [
|
||||
{
|
||||
title: 'app',
|
||||
key: 'stores.app',
|
||||
selectable: false,
|
||||
children: [
|
||||
{ title: 'app.title', key: 'stores.app.title', raw: '"hello"' },
|
||||
{ title: 'app.age', key: 'stores.app.age', raw: '20' },
|
||||
{ title: 'app.detail', key: 'stores.app.detail', raw: '{ foo: "foo" }' },
|
||||
{ title: 'app.list', key: 'stores.app.list', type: 'function', raw: '() => {}' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'user',
|
||||
key: 'stores.user',
|
||||
selectable: false,
|
||||
children: [
|
||||
{ title: 'name', key: 'stores.user.name' },
|
||||
{ title: 'age', key: 'stores.user.age' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'services',
|
||||
key: 'services',
|
||||
selectable: false,
|
||||
children: [
|
||||
{
|
||||
title: 'listUsers',
|
||||
key: 'services.listUsers',
|
||||
type: 'function',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const prototype: ComponentPrototypeType = {
|
||||
name: 'Test',
|
||||
exportType: 'namedExport',
|
||||
@@ -257,47 +214,6 @@ const prototype: ComponentPrototypeType = {
|
||||
],
|
||||
};
|
||||
|
||||
const deerService = createServices(
|
||||
{
|
||||
listMy: {
|
||||
url: '/my/upload/list',
|
||||
},
|
||||
listFav: {
|
||||
url: '/my/star/list',
|
||||
},
|
||||
listPub: {
|
||||
url: '/list',
|
||||
},
|
||||
},
|
||||
{
|
||||
baseURL: 'https://febase-openapi.fn.netease.com/deer/api/deer/pic',
|
||||
withCredentials: false, // 解决跨域时必须非*问题
|
||||
headers: {
|
||||
'Febase-Auth': 'dskPVkIRnQ2dEn1DbyxURmUj4rl4BsCh53xFAsJnvVs=',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const context = {
|
||||
stores: {
|
||||
app: {
|
||||
title: 'Sample App',
|
||||
age: 20,
|
||||
detail: {
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
biz: {
|
||||
x: 'xxx',
|
||||
},
|
||||
},
|
||||
newKey: 'xxx',
|
||||
},
|
||||
foo: {
|
||||
test: 'test string',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 表单值预览
|
||||
*/
|
||||
@@ -310,9 +226,7 @@ export function Basic() {
|
||||
const model = new FormModel(
|
||||
{
|
||||
router: 'www.163.com',
|
||||
expression: {
|
||||
foo: 'foo',
|
||||
},
|
||||
expression: `{ foo: 'foo' }`,
|
||||
object: {
|
||||
name: 'Alice',
|
||||
},
|
||||
@@ -324,19 +238,7 @@ export function Basic() {
|
||||
|
||||
return (
|
||||
<Box display="flex">
|
||||
<SettingForm
|
||||
model={model}
|
||||
remoteServices={{
|
||||
ImageService: deerService as any,
|
||||
}}
|
||||
prototype={prototype}
|
||||
evaluateContext={{
|
||||
tango: context,
|
||||
__UNSAFE_TANGO_CURRENT_PAGE_RENDER_RUNTIME__: { routeData: { params: {}, query: {} } },
|
||||
}}
|
||||
modelVariables={modelVariables}
|
||||
expressionVariables={modelVariables}
|
||||
/>
|
||||
<SettingForm model={model} prototype={prototype} />
|
||||
<Box position="relative">
|
||||
<Card title="表单状态预览" style={{ position: 'sticky', top: 0 }}>
|
||||
<FormValuePreview model={model} />
|
||||
@@ -346,6 +248,14 @@ export function Basic() {
|
||||
);
|
||||
}
|
||||
|
||||
export function Lite() {
|
||||
return (
|
||||
<Box width={320} border="solid">
|
||||
<SettingForm showSearch={false} showGroups={false} prototype={prototype} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function NoExpressionSwitch() {
|
||||
const model = new FormModel({});
|
||||
return (
|
||||
@@ -354,30 +264,3 @@ export function NoExpressionSwitch() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function Binding() {
|
||||
const [data, setData] = useState<any>({});
|
||||
return (
|
||||
<Box>
|
||||
<Box as="code" bg="highlight" color="white" fontSize="24px">
|
||||
selected: {data?.key}
|
||||
</Box>
|
||||
<EditableVariableTree
|
||||
dataSource={modelVariables as any}
|
||||
getPreviewValue={(node) => {
|
||||
if (!node || !node.key) {
|
||||
return;
|
||||
}
|
||||
if (node.type === 'function') {
|
||||
return node.raw;
|
||||
}
|
||||
const keyPath = node.key.replaceAll('?', '');
|
||||
return getValue(context, keyPath);
|
||||
}}
|
||||
onSelect={setData}
|
||||
onAddVariable={console.log}
|
||||
onSave={console.log}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ToggleButton, IconFont } from '@music163/tango-ui';
|
||||
import { ToggleButton, UndoOutlined, RedoOutlined } from '@music163/tango-ui';
|
||||
import { Box, Flex } from 'coral-system';
|
||||
|
||||
export default {
|
||||
@@ -11,24 +11,24 @@ export function Basic() {
|
||||
<Box>
|
||||
<Flex gap="l" bg="#efefef" p="l">
|
||||
<ToggleButton>
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton selected>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton disabled>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
<Flex gap="l" bg="#222" p="l">
|
||||
<ToggleButton shape="ghost">
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton shape="ghost" selected>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton shape="ghost" disabled>
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
@@ -137,7 +137,7 @@ const config = {
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '更多',
|
||||
title: '相关资源',
|
||||
items: [
|
||||
{
|
||||
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: {
|
||||
alt: 'NetEase Cloud Music',
|
||||
|
||||
@@ -24,7 +24,7 @@ const timelines = [
|
||||
{
|
||||
icon: null,
|
||||
title: '1.0 RC',
|
||||
date: '2023.10.30',
|
||||
date: '2023.11.30',
|
||||
description: translate({
|
||||
id: 'homepage.timeline.rc',
|
||||
message: '核心 API 基本稳定,不再发生 BR,发布 1.0 RC 版本。',
|
||||
@@ -33,7 +33,7 @@ const timelines = [
|
||||
{
|
||||
icon: null,
|
||||
title: '1.0',
|
||||
date: 'Before 2023.12.25',
|
||||
date: 'Before 2023.12.29',
|
||||
description: translate({
|
||||
id: 'homepage.timeline.stable',
|
||||
message: 'API 完全稳定,提供良好的社区支持,可用于生产环境。',
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ const config = {
|
||||
// testLocationInResults: false,
|
||||
|
||||
// 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
|
||||
// testPathIgnorePatterns: [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"useWorkspaces": true,
|
||||
"version": "independent",
|
||||
"packages": ["packages/*"],
|
||||
"npmClient": "yarn",
|
||||
|
||||
+18
-14
@@ -18,8 +18,9 @@
|
||||
"test:watch": "jest --watch",
|
||||
"eslint": "eslint packages/**/src/*.{ts,tsx} --cache",
|
||||
"publish": "lerna publish",
|
||||
"ver": "lerna version --no-private --conventional-commits",
|
||||
"release": "yarn eslint && yarn build && yarn run ver && lerna publish from-git",
|
||||
"ver": "lerna version --no-private",
|
||||
"release": "yarn eslint && yarn build && lerna version --no-private --conventional-commits && lerna publish from-git",
|
||||
"release:npm": "yarn eslint && yarn build && yarn run ver && lerna publish from-git",
|
||||
"release:beta": "yarn eslint && yarn build && yarn run ver && lerna publish from-package --dist-tag beta",
|
||||
"up": "yarn upgrade-interactive --latest",
|
||||
"prepare": "husky install"
|
||||
@@ -28,8 +29,8 @@
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@babel/preset-react": "^7.22.15",
|
||||
"@babel/preset-typescript": "^7.23.2",
|
||||
"@commitlint/cli": "^17.5.0",
|
||||
"@commitlint/config-conventional": "^17.3.0",
|
||||
"@commitlint/cli": "^18.4.3",
|
||||
"@commitlint/config-conventional": "^18.4.3",
|
||||
"@types/jest": "^29.5.7",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/lodash.get": "^4.4.8",
|
||||
@@ -38,13 +39,13 @@
|
||||
"@types/react": "^18.2.34",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@types/styled-components": "^5.1.29",
|
||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||
"@typescript-eslint/parser": "^6.9.1",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
||||
"@typescript-eslint/parser": "^6.13.2",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-ali": "^14.0.2",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
@@ -53,14 +54,17 @@
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"lerna": "^6.6.1",
|
||||
"lint-staged": "^14.0.1",
|
||||
"prettier": "^2.8.7",
|
||||
"lerna": "^8.0.0",
|
||||
"lint-staged": "^15.2.0",
|
||||
"prettier": "^3.1.0",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"styled-components": "^5.3.6",
|
||||
"typedoc": "^0.25.3",
|
||||
"typescript": "^5.2.2"
|
||||
"typedoc": "^0.25.4",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.12](https://github.com/netease/tango/compare/@music163/tango-context@0.2.11...@music163/tango-context@0.2.12) (2023-12-04)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-context
|
||||
|
||||
## [0.2.11](https://github.com/netease/tango/compare/@music163/tango-context@0.2.10...@music163/tango-context@0.2.11) (2023-12-04)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-context
|
||||
|
||||
## [0.2.10](https://github.com/netease/tango/compare/@music163/tango-context@0.2.9...@music163/tango-context@0.2.10) (2023-11-30)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-context
|
||||
|
||||
## [0.2.9](https://github.com/netease/tango/compare/@music163/tango-context@0.2.8...@music163/tango-context@0.2.9) (2023-11-29)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-context
|
||||
|
||||
## [0.2.8](https://github.com/netease/tango/compare/@music163/tango-context@0.2.7...@music163/tango-context@0.2.8) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-context
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-context",
|
||||
"version": "0.2.5",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "react context for tango-apps",
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -31,9 +31,9 @@
|
||||
"react": ">= 16.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@music163/tango-core": "^0.2.4",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"mobx-react-lite": "4.0.2"
|
||||
"@music163/tango-core": "^1.0.0-alpha.0",
|
||||
"@music163/tango-helpers": "^0.1.8",
|
||||
"mobx-react-lite": "4.0.5"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.11](https://github.com/netease/tango/compare/@music163/tango-core@0.2.10...@music163/tango-core@0.2.11) (2023-12-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update FileType ([c884712](https://github.com/netease/tango/commit/c8847123312bc47ea06575f4e714dfd596893e39))
|
||||
|
||||
## [0.2.10](https://github.com/netease/tango/compare/@music163/tango-core@0.2.9...@music163/tango-core@0.2.10) (2023-12-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add filesFormatter to sandbox & fix update props with imports ([#71](https://github.com/netease/tango/issues/71)) ([138cbe9](https://github.com/netease/tango/commit/138cbe9b203b370aff42c1ae8086d69edacf35e9))
|
||||
|
||||
## [0.2.9](https://github.com/netease/tango/compare/@music163/tango-core@0.2.8...@music163/tango-core@0.2.9) (2023-11-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fix render components tree ([76a5a2c](https://github.com/netease/tango/commit/76a5a2c65920bc42b019cd1f32a3cacd0d888638))
|
||||
|
||||
## [0.2.8](https://github.com/netease/tango/compare/@music163/tango-core@0.2.7...@music163/tango-core@0.2.8) (2023-11-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- workspace onFilesChange ([#70](https://github.com/netease/tango/issues/70)) ([accd826](https://github.com/netease/tango/commit/accd8263764c811ea8175a9cd341fc6fa6c75967))
|
||||
|
||||
## [0.2.7](https://github.com/netease/tango/compare/@music163/tango-core@0.2.6...@music163/tango-core@0.2.7) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- list service functions ([91b53da](https://github.com/netease/tango/commit/91b53da38e362ae320457429037dc347adf90bd3))
|
||||
- parse tango variables in view file ([d917633](https://github.com/netease/tango/commit/d91763379c4ccf9d826b717aa80a07abdaf2e3a2))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-core",
|
||||
"version": "0.2.4",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "tango core",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -24,14 +24,14 @@
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@types/babel__generator": "^7.6.6",
|
||||
"@types/babel__traverse": "^7.20.3",
|
||||
"mobx": "6.9.0",
|
||||
"@babel/generator": "^7.23.5",
|
||||
"@babel/parser": "^7.23.5",
|
||||
"@babel/traverse": "^7.23.5",
|
||||
"@babel/types": "^7.23.5",
|
||||
"@music163/tango-helpers": "^0.1.8",
|
||||
"@types/babel__generator": "^7.6.7",
|
||||
"@types/babel__traverse": "^7.20.4",
|
||||
"mobx": "6.12.0",
|
||||
"path-browserify": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isValidExpressionCode } from './ast';
|
||||
|
||||
const defineServiceHandlerNames = ['defineServices', 'createServices'];
|
||||
const sfHandlerPattern = new RegExp(`^(${defineServiceHandlerNames.join('|')})$`);
|
||||
|
||||
@@ -32,3 +34,16 @@ export function isDefineStore(name: string) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import * as t from '@babel/types';
|
||||
import {
|
||||
logger,
|
||||
isValidObjectString,
|
||||
isVariableString,
|
||||
getVariableContent,
|
||||
isPlainObject,
|
||||
} from '@music163/tango-helpers';
|
||||
import { isWrappedByExpressionContainer } from '../assert';
|
||||
|
||||
// @see https://babeljs.io/docs/en/babel-parser#pluginss
|
||||
const babelParserConfig: ParserOptions = {
|
||||
@@ -22,7 +22,6 @@ const babelParserConfig: ParserOptions = {
|
||||
'classProperties',
|
||||
'asyncGenerators',
|
||||
'functionBind',
|
||||
'functionSent',
|
||||
'dynamicImport',
|
||||
'optionalChaining',
|
||||
],
|
||||
@@ -44,6 +43,7 @@ export function isValidCode(code: string) {
|
||||
|
||||
/**
|
||||
* 检测代码是否是合法的表达式代码
|
||||
* 表达式是一组代码的集合,它返回一个值;每一个合法的表达式都能计算成某个值
|
||||
* @param code
|
||||
* @returns
|
||||
*/
|
||||
@@ -91,8 +91,8 @@ export function code2expression(code: string) {
|
||||
try {
|
||||
expNode = t.cloneNode(parseExpression(code, babelParserConfig), false, true);
|
||||
} catch (err) {
|
||||
console.error('invalid code', err);
|
||||
expNode = t.identifier('undefined');
|
||||
logger.error('invalid code', err);
|
||||
// expNode = t.identifier('undefined');
|
||||
}
|
||||
return expNode;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ export function code2expression(code: string) {
|
||||
* @returns File
|
||||
*/
|
||||
export function expressionCode2ast(code: string) {
|
||||
if (isVariableString(code)) {
|
||||
if (isWrappedByExpressionContainer(code)) {
|
||||
code = getVariableContent(code);
|
||||
}
|
||||
const node = code2expression(code);
|
||||
@@ -130,7 +130,7 @@ export function value2node(
|
||||
ret = t.numericLiteral(value);
|
||||
break;
|
||||
case 'string':
|
||||
if (isVariableString(value)) {
|
||||
if (isWrappedByExpressionContainer(value)) {
|
||||
// 再检查是否是表达式容器,例如 {this.foo}, {1}
|
||||
const innerString = getVariableContent(value);
|
||||
ret = code2expression(innerString);
|
||||
@@ -201,7 +201,7 @@ export function value2jsxAttributeValueNode(value: any) {
|
||||
if (isValidObjectString(value)) {
|
||||
// 先检查是否是对象字符串
|
||||
ret = t.jsxExpressionContainer(code2expression(value));
|
||||
} else if (isVariableString(value)) {
|
||||
} else if (isWrappedByExpressionContainer(value)) {
|
||||
// 再检查是否是表达式容器,例如 {this.foo}, {1}
|
||||
const innerString = getVariableContent(value);
|
||||
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;
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
if (isVariableString(value)) {
|
||||
if (isWrappedByExpressionContainer(value)) {
|
||||
const innerString = getVariableContent(value);
|
||||
ret = t.jsxExpressionContainer(code2expression(innerString));
|
||||
} else {
|
||||
|
||||
@@ -453,7 +453,7 @@ function getLastExportDeclarationIndex(node: t.Program) {
|
||||
* @param importedSourcePath
|
||||
* @returns
|
||||
*/
|
||||
export function makeImportDeclaration(importedModule: IImportDeclarationPayload) {
|
||||
export function makeImportDeclarationLegacy(importedModule: IImportDeclarationPayload) {
|
||||
const specifierNodes =
|
||||
importedModule.specifiers?.map((localName) =>
|
||||
t.importSpecifier(t.identifier(localName), t.identifier(localName)),
|
||||
@@ -468,19 +468,28 @@ export function makeImportDeclaration(importedModule: IImportDeclarationPayload)
|
||||
return t.importDeclaration(newSpecifierNodes, t.stringLiteral(importedModule.sourcePath));
|
||||
}
|
||||
|
||||
export function makeImportDeclaration2(source: string, specifiers: IImportSpecifierData[]) {
|
||||
const specifierNodes = specifiers.map((item) => {
|
||||
switch (item.type) {
|
||||
case 'ImportDefaultSpecifier':
|
||||
return t.importDefaultSpecifier(t.identifier(item.localName));
|
||||
case 'ImportSpecifier':
|
||||
return t.importSpecifier(t.identifier(item.importedName), t.identifier(item.localName));
|
||||
case 'ImportNamespaceSpecifier':
|
||||
return t.importNamespaceSpecifier(t.identifier(item.localName));
|
||||
default:
|
||||
return;
|
||||
}
|
||||
});
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -515,11 +524,11 @@ function getImportDeclarationData(node: t.ImportDeclaration): IImportDeclaration
|
||||
* @param importedSourcePath
|
||||
* @returns
|
||||
*/
|
||||
export function addImportDeclaration(ast: t.File, importedModule: IImportDeclarationPayload) {
|
||||
export function addImportDeclarationLegacy(ast: t.File, importedModule: IImportDeclarationPayload) {
|
||||
traverse(ast, {
|
||||
Program(path) {
|
||||
const lastIndex = getLastImportDeclarationIndex(path.node);
|
||||
const newImportDeclaration = makeImportDeclaration(importedModule);
|
||||
const newImportDeclaration = makeImportDeclarationLegacy(importedModule);
|
||||
path.node.body.splice(lastIndex, 0, newImportDeclaration);
|
||||
path.stop();
|
||||
},
|
||||
@@ -527,7 +536,7 @@ export function addImportDeclaration(ast: t.File, importedModule: IImportDeclara
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function addImportDeclaration2(
|
||||
export function addImportDeclaration(
|
||||
ast: t.File,
|
||||
source: string,
|
||||
specifiers: IImportSpecifierData[],
|
||||
@@ -535,11 +544,12 @@ export function addImportDeclaration2(
|
||||
traverse(ast, {
|
||||
Program(path) {
|
||||
const lastIndex = getLastImportDeclarationIndex(path.node);
|
||||
const newImportDeclaration = makeImportDeclaration2(source, specifiers);
|
||||
const newImportDeclaration = makeImportDeclaration(source, specifiers);
|
||||
path.node.body.splice(lastIndex, 0, newImportDeclaration);
|
||||
path.stop();
|
||||
},
|
||||
});
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,12 +560,15 @@ export function addImportDeclaration2(
|
||||
* @param importedSourcePath
|
||||
* @returns
|
||||
*/
|
||||
export function updateImportDeclaration(ast: t.File, importedModule: IImportDeclarationPayload) {
|
||||
export function updateImportDeclarationLegacy(
|
||||
ast: t.File,
|
||||
importedModule: IImportDeclarationPayload,
|
||||
) {
|
||||
traverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
const currentSourcePath = node2value(path.node.source);
|
||||
if (currentSourcePath === importedModule.sourcePath) {
|
||||
const newImportDeclaration = makeImportDeclaration(importedModule);
|
||||
const newImportDeclaration = makeImportDeclarationLegacy(importedModule);
|
||||
path.replaceWith(newImportDeclaration);
|
||||
path.stop(); // 只修改匹配到的第一条
|
||||
}
|
||||
@@ -564,7 +577,7 @@ export function updateImportDeclaration(ast: t.File, importedModule: IImportDecl
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function updateImportDeclaration2(
|
||||
export function updateImportDeclaration(
|
||||
ast: t.File,
|
||||
source: string,
|
||||
specifiers: IImportSpecifierData[],
|
||||
@@ -573,7 +586,7 @@ export function updateImportDeclaration2(
|
||||
ImportDeclaration(path) {
|
||||
const currentSourcePath = node2value(path.node.source);
|
||||
if (currentSourcePath === source) {
|
||||
const newImportDeclaration = makeImportDeclaration2(source, specifiers);
|
||||
const newImportDeclaration = makeImportDeclaration(source, specifiers);
|
||||
path.replaceWith(newImportDeclaration);
|
||||
path.stop(); // 只修改匹配到的第一条
|
||||
}
|
||||
@@ -582,6 +595,32 @@ export function updateImportDeclaration2(
|
||||
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
|
||||
@@ -649,7 +688,6 @@ export function addRouteToRouteFile(ast: t.File, routePath: string, importFilePa
|
||||
const newNode = t.objectExpression([
|
||||
t.objectProperty(t.identifier('path'), t.stringLiteral(routePath)),
|
||||
t.objectProperty(t.identifier('component'), t.identifier(component)),
|
||||
// FIXME: tango/issues#144 https://g.hz.netease.com/tango/issues/-/issues/144
|
||||
t.objectProperty(t.identifier('exact'), t.booleanLiteral(true)),
|
||||
]);
|
||||
path.node.elements.push(newNode);
|
||||
@@ -1180,7 +1218,12 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
||||
traverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
const { source, specifiers } = parseImportDeclaration(path.node);
|
||||
imports[source] = specifiers;
|
||||
if (imports[source]) {
|
||||
// 存在重复的导入语句,合并导入符号列表
|
||||
imports[source] = imports[source].concat(specifiers);
|
||||
} else {
|
||||
imports[source] = specifiers;
|
||||
}
|
||||
|
||||
// FIXME: 下面的逻辑兼容旧的逻辑,后续需要移除掉
|
||||
const declarationData = specifiers.reduce(
|
||||
@@ -1218,7 +1261,12 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
||||
|
||||
MemberExpression(path) {
|
||||
const variable = node2value(path.node, false);
|
||||
if (isTangoVariable(variable) && !variables.includes(variable)) {
|
||||
const parentNode = path.parentPath.node;
|
||||
if (
|
||||
!t.isMemberExpression(parentNode) &&
|
||||
isTangoVariable(variable) &&
|
||||
!variables.includes(variable)
|
||||
) {
|
||||
variables.push(variable);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getVariableContent, isVariableString } from '@music163/tango-helpers';
|
||||
import { value2node, expression2code } from './ast';
|
||||
import { getVariableContent } from '@music163/tango-helpers';
|
||||
import { value2node, expression2code, isValidExpressionCode } from './ast';
|
||||
import { isWrappedByExpressionContainer } from './assert';
|
||||
|
||||
/**
|
||||
* 将 js value 转换为代码字符串
|
||||
@@ -36,7 +37,9 @@ export function value2expressionCode(val: any) {
|
||||
|
||||
switch (typeof val) {
|
||||
case 'string': {
|
||||
if (isVariableString(val)) {
|
||||
if (isValidExpressionCode(val)) {
|
||||
ret = val;
|
||||
} else if (isWrappedByExpressionContainer(val, false)) {
|
||||
ret = getVariableContent(val);
|
||||
} else if (isStringCode(val)) {
|
||||
ret = val;
|
||||
|
||||
@@ -3,16 +3,51 @@ import {
|
||||
ComponentPropType,
|
||||
ComponentPrototypeType,
|
||||
isNil,
|
||||
isVariableString,
|
||||
logger,
|
||||
uuid,
|
||||
} from '@music163/tango-helpers';
|
||||
import { getRelativePath, isFilepath } from './string';
|
||||
import type { IImportDeclarationPayload } from '../types';
|
||||
import type { IImportDeclarationPayload, IImportSpecifierData } from '../types';
|
||||
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
|
||||
* @deprecated
|
||||
*/
|
||||
export function getImportDeclarationPayloadByPrototype(
|
||||
prototype: ComponentPrototypeType,
|
||||
@@ -35,6 +70,9 @@ export function getImportDeclarationPayloadByPrototype(
|
||||
if (relativeFilepath && isFilepath(sourcePath)) {
|
||||
sourcePath = getRelativePath(relativeFilepath, sourcePath);
|
||||
}
|
||||
if (sourcePath.endsWith('.js')) {
|
||||
sourcePath = sourcePath.slice(0, -3);
|
||||
}
|
||||
|
||||
return {
|
||||
defaultSpecifier,
|
||||
@@ -82,7 +120,7 @@ function getPropKeyValuePair(item: ComponentPropType, generateValue: (...args: a
|
||||
break;
|
||||
}
|
||||
case 'string': {
|
||||
if (!isVariableString(value)) {
|
||||
if (!isWrappedByExpressionContainer(value)) {
|
||||
// 不是变量字符串
|
||||
value = `"${value}"`;
|
||||
} else {
|
||||
|
||||
@@ -4,5 +4,5 @@ export * from './helpers';
|
||||
export * from './types';
|
||||
|
||||
export { default as generator } from '@babel/generator';
|
||||
export { default as traverse } from '@babel/types';
|
||||
export { default as traverse } from '@babel/traverse';
|
||||
export * from '@babel/parser';
|
||||
|
||||
@@ -56,6 +56,7 @@ export class TangoFile {
|
||||
this._code = code;
|
||||
this._cleanCode = code;
|
||||
}
|
||||
this.workspace.onFilesChange([this.filename]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +135,7 @@ export class TangoJsonFile extends TangoFile {
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
this.workspace.onFilesChange([this.filename]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,12 +5,12 @@ import { DragSource } from './drag-source';
|
||||
import {
|
||||
IFileConfig,
|
||||
FileType,
|
||||
IImportDeclarationPayload,
|
||||
InsertChildPositionType,
|
||||
ITangoConfigPackages,
|
||||
IPageConfigData,
|
||||
IServiceFunctionPayload,
|
||||
IImportSpecifierSourceData,
|
||||
IImportSpecifierData,
|
||||
} from '../types';
|
||||
import { TangoFile, TangoJsonFile } from './file';
|
||||
import { TangoRouteModule } from './route-module';
|
||||
@@ -33,6 +33,14 @@ export interface IViewFile {
|
||||
|
||||
update: (code?: string, isFormatCode?: boolean, refreshWorkspace?: boolean) => void;
|
||||
|
||||
/**
|
||||
* 添加新的导入符号
|
||||
* @param source 导入来源
|
||||
* @param newSpecifiers 新导入符号列表
|
||||
* @returns this
|
||||
*/
|
||||
addImportSpecifiers: (source: string, newSpecifiers: IImportSpecifierData[]) => IViewFile;
|
||||
|
||||
getNode: (targetNodeId: string) => IViewNode;
|
||||
|
||||
removeNode: (targetNodeId: string) => IViewFile;
|
||||
@@ -41,31 +49,15 @@ export interface IViewFile {
|
||||
targetNodeId: string,
|
||||
newNode: any,
|
||||
position?: InsertChildPositionType,
|
||||
sourcePrototype?: string | ComponentPrototypeType,
|
||||
) => IViewFile;
|
||||
|
||||
insertAfter: (
|
||||
targetNodeId: string,
|
||||
newNode: any,
|
||||
sourcePrototype?: string | ComponentPrototypeType,
|
||||
) => IViewFile;
|
||||
insertAfter: (targetNodeId: string, newNode: any) => IViewFile;
|
||||
|
||||
insertBefore: (
|
||||
targetNodeId: string,
|
||||
newNode: any,
|
||||
sourcePrototype?: string | ComponentPrototypeType,
|
||||
) => IViewFile;
|
||||
insertBefore: (targetNodeId: string, newNode: any) => IViewFile;
|
||||
|
||||
replaceNode: (
|
||||
targetNodeId: string,
|
||||
newNode: any,
|
||||
sourcePrototype?: string | ComponentPrototypeType,
|
||||
) => IViewFile;
|
||||
replaceNode: (targetNodeId: string, newNode: any) => IViewFile;
|
||||
|
||||
replaceViewChildren: (
|
||||
rawNodes: any[],
|
||||
importDeclarations?: IImportDeclarationPayload[],
|
||||
) => IViewFile;
|
||||
replaceViewChildren: (rawNodes: any[]) => IViewFile;
|
||||
|
||||
updateNodeAttribute: (
|
||||
nodeId: string,
|
||||
@@ -157,8 +149,6 @@ export interface IWorkspace {
|
||||
setComponentPrototypes: (prototypes: Record<string, ComponentPrototypeType>) => void;
|
||||
getPrototype: (name: string | ComponentPrototypeType) => ComponentPrototypeType;
|
||||
|
||||
onFilesChange?: (filenames: string[]) => void;
|
||||
|
||||
// ----------------- 文件操作 -----------------
|
||||
addFiles: (files: IFileConfig[]) => void;
|
||||
addFile: (filename: string, code: string, fileType?: FileType) => void;
|
||||
@@ -177,6 +167,12 @@ export interface IWorkspace {
|
||||
listFiles: () => Record<string, string>;
|
||||
getFile: (filename: string) => TangoFile;
|
||||
|
||||
/**
|
||||
* 文件变化回调
|
||||
* @param filenames 文件名列表
|
||||
*/
|
||||
onFilesChange: (filenames: string[]) => void;
|
||||
|
||||
// ----------------- 节点操作 -----------------
|
||||
|
||||
removeSelectedNode: () => void;
|
||||
@@ -208,15 +204,15 @@ export interface IWorkspace {
|
||||
|
||||
// ----------------- 服务函数文件操作 -----------------
|
||||
|
||||
getServiceFunction: (serviceKey: string) => object;
|
||||
listServiceFunctions: () => Record<string, object>;
|
||||
removeServiceFunction: (serviceName: string, modName?: string) => void;
|
||||
addServiceFunction: (
|
||||
getServiceFunction?: (serviceKey: string) => object;
|
||||
listServiceFunctions?: () => Record<string, object>;
|
||||
removeServiceFunction?: (serviceName: string, modName?: string) => void;
|
||||
addServiceFunction?: (
|
||||
payload: IServiceFunctionPayload | IServiceFunctionPayload[],
|
||||
modName?: string,
|
||||
) => void;
|
||||
updateServiceFunction: (payload: IServiceFunctionPayload, modName?: string) => void;
|
||||
updateServiceBaseConfig: (IServiceFunctionPayload: object, modName?: string) => void;
|
||||
updateServiceFunction?: (payload: IServiceFunctionPayload, modName?: string) => void;
|
||||
updateServiceBaseConfig?: (IServiceFunctionPayload: object, modName?: string) => void;
|
||||
|
||||
// ----------------- 状态管理文件操作 -----------------
|
||||
addStoreState?: (storeName: string, stateName: string, initValue: string) => void;
|
||||
@@ -234,11 +230,11 @@ export interface IWorkspace {
|
||||
|
||||
// ----------------- 依赖包操作 -----------------
|
||||
|
||||
addDependency: (data: any) => void;
|
||||
listDependencies: () => any;
|
||||
getDependency: (pkgName: string) => object;
|
||||
addDependency?: (data: any) => void;
|
||||
listDependencies?: () => any;
|
||||
getDependency?: (pkgName: string) => object;
|
||||
|
||||
updateDependency: (
|
||||
updateDependency?: (
|
||||
name: string,
|
||||
version: string,
|
||||
options?: {
|
||||
@@ -247,7 +243,7 @@ export interface IWorkspace {
|
||||
},
|
||||
) => void;
|
||||
|
||||
removeDependency: (name: string) => void;
|
||||
removeDependency?: (name: string) => void;
|
||||
|
||||
addBizComp?: (
|
||||
name: string,
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
ast2code,
|
||||
formatCode,
|
||||
traverseFile,
|
||||
addImportDeclaration2,
|
||||
updateImportDeclaration2,
|
||||
addImportDeclaration,
|
||||
updateImportDeclaration,
|
||||
} from '../helpers';
|
||||
import { TangoFile } from './file';
|
||||
import { IFileConfig, IImportSpecifierData, ImportDeclarationDataType } from '../types';
|
||||
@@ -46,18 +46,20 @@ export class TangoModule extends TangoFile {
|
||||
|
||||
this._analysisAst();
|
||||
|
||||
this.workspace.onFilesChange([this.filename]);
|
||||
|
||||
if (refreshWorkspace) {
|
||||
this.workspace.refresh([this.filename]);
|
||||
}
|
||||
}
|
||||
|
||||
addImportDeclaration(source: string, specifiers: IImportSpecifierData[]) {
|
||||
addImportDeclaration2(this.ast, source, specifiers);
|
||||
this.ast = addImportDeclaration(this.ast, source, specifiers);
|
||||
return this;
|
||||
}
|
||||
|
||||
updateImportDeclaration(source: string, specifiers: IImportSpecifierData[]) {
|
||||
updateImportDeclaration2(this.ast, source, specifiers);
|
||||
this.ast = updateImportDeclaration(this.ast, source, specifiers);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as t from '@babel/types';
|
||||
import { action, computed, makeObservable, observable, toJS } from 'mobx';
|
||||
import { ComponentPrototypeType, Dict } from '@music163/tango-helpers';
|
||||
import { Dict } from '@music163/tango-helpers';
|
||||
import {
|
||||
ast2code,
|
||||
traverseViewFile,
|
||||
@@ -8,15 +8,17 @@ import {
|
||||
insertSiblingAfterJSXElement,
|
||||
appendChildToJSXElement,
|
||||
addImportDeclaration,
|
||||
updateImportDeclaration,
|
||||
replaceJSXElement,
|
||||
getImportDeclarationPayloadByPrototype,
|
||||
removeUnusedImportSpecifiers,
|
||||
insertSiblingBeforeJSXElement,
|
||||
replaceRootJSXElementChildren,
|
||||
IdGenerator,
|
||||
updateJSXAttributes,
|
||||
queryXFormItemFields,
|
||||
prototype2importDeclarationData,
|
||||
insertImportSpecifiers,
|
||||
addImportDeclarationLegacy,
|
||||
updateImportDeclarationLegacy,
|
||||
} from '../helpers';
|
||||
import { TangoNode } from './node';
|
||||
import {
|
||||
@@ -26,10 +28,57 @@ import {
|
||||
InsertChildPositionType,
|
||||
IImportSpecifierSourceData,
|
||||
ImportDeclarationDataType,
|
||||
IImportSpecifierData,
|
||||
} from '../types';
|
||||
import { IViewFile, IWorkspace } from './interfaces';
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图模块
|
||||
*/
|
||||
@@ -111,7 +160,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
|
||||
this._importedModules = importedModules;
|
||||
this.imports = imports;
|
||||
this.importMap = this.buildImportMap(imports);
|
||||
this.importMap = buildImportMap(imports);
|
||||
this.variables = variables;
|
||||
|
||||
this._nodes.clear();
|
||||
@@ -172,27 +221,30 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于组件的 prototype 信息更新导入信息
|
||||
* TODO: 和 Module.addImportDeclaration 中的保持一致
|
||||
* @deprecated
|
||||
* @param prototype
|
||||
* @param shouldUpdateCode
|
||||
* 添加导入符号
|
||||
* @param source
|
||||
* @param newSpecifiers
|
||||
* @returns
|
||||
*/
|
||||
updateImportSpecifiersByPrototype(sourcePrototype: string | ComponentPrototypeType) {
|
||||
const prototype = this.workspace.getPrototype(sourcePrototype);
|
||||
if (prototype) {
|
||||
const importDeclaration = getImportDeclarationPayloadByPrototype(prototype, this.filename);
|
||||
this.updateImportSpecifiers(importDeclaration);
|
||||
addImportSpecifiers(source: string, newSpecifiers: IImportSpecifierData[]) {
|
||||
const existSpecifiers = this.imports[source];
|
||||
if (existSpecifiers) {
|
||||
const insertedSpecifiers = newSpecifiers.filter((item) => {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新导入的变量(新版)
|
||||
* TODO: 和 Module.updateImportDeclaration 中的保持一致
|
||||
* @deprecated
|
||||
* 更新导入的变量
|
||||
* @deprecated 使用 updateImportDeclaration 代替
|
||||
*/
|
||||
updateImportSpecifiers(importDeclaration: IImportDeclarationPayload) {
|
||||
updateImportSpecifiersLegacy(importDeclaration: IImportDeclarationPayload) {
|
||||
const mods = this._importedModules[importDeclaration.sourcePath];
|
||||
let ast;
|
||||
// 如果模块已存在,需要去重
|
||||
@@ -207,12 +259,12 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
(name) => !specifiers.includes(name),
|
||||
);
|
||||
|
||||
ast = updateImportDeclaration(this.ast, {
|
||||
ast = updateImportDeclarationLegacy(this.ast, {
|
||||
...importDeclaration,
|
||||
specifiers: newSpecifiers.concat(targetMod.specifiers),
|
||||
});
|
||||
} else {
|
||||
ast = addImportDeclaration(this.ast, importDeclaration);
|
||||
ast = addImportDeclarationLegacy(this.ast, importDeclaration);
|
||||
}
|
||||
this.ast = ast;
|
||||
return this;
|
||||
@@ -255,9 +307,24 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
updateNodeAttributes(nodeId: string, config: Record<string, any>, relatedImports?: string[]) {
|
||||
if (relatedImports && relatedImports.length) {
|
||||
// 导入依赖的组件
|
||||
relatedImports.forEach((name: string) => {
|
||||
const newImportData = relatedImports.reduce((prev, name) => {
|
||||
const proto = this.workspace.getPrototype(name);
|
||||
this.updateImportSpecifiersByPrototype(proto);
|
||||
const { source, specifiers } = prototype2importDeclarationData(proto, this.filename);
|
||||
const existSpecifiers: IImportSpecifierData[] = prev[source];
|
||||
if (existSpecifiers) {
|
||||
// merge specifiers
|
||||
specifiers.forEach((item) => {
|
||||
if (!existSpecifiers.find((existItem) => existItem.localName === item.localName)) {
|
||||
existSpecifiers.push(item);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
prev[source] = specifiers;
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
Object.keys(newImportData).forEach((source) => {
|
||||
this.addImportSpecifiers(source, newImportData[source]);
|
||||
});
|
||||
}
|
||||
this.ast = updateJSXAttributes(this.ast, nodeId, config);
|
||||
@@ -268,43 +335,25 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
* 插入子节点的最后面
|
||||
* @param targetNodeId
|
||||
* @param newNode
|
||||
* @param sourceName
|
||||
* @param position
|
||||
* @returns
|
||||
*/
|
||||
insertChild(
|
||||
targetNodeId: string,
|
||||
newNode: t.JSXElement,
|
||||
position: InsertChildPositionType = 'last',
|
||||
sourceName: string | ComponentPrototypeType,
|
||||
) {
|
||||
this.ast = appendChildToJSXElement(this.ast, targetNodeId, newNode, position);
|
||||
if (sourceName) {
|
||||
this.updateImportSpecifiersByPrototype(sourceName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
insertAfter(
|
||||
targetNodeId: string,
|
||||
newNode: t.JSXElement,
|
||||
sourceName?: string | ComponentPrototypeType,
|
||||
) {
|
||||
insertAfter(targetNodeId: string, newNode: t.JSXElement) {
|
||||
this.ast = insertSiblingAfterJSXElement(this.ast, targetNodeId, newNode);
|
||||
if (sourceName) {
|
||||
this.updateImportSpecifiersByPrototype(sourceName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
insertBefore(
|
||||
targetNodeId: string,
|
||||
newNode: t.JSXElement,
|
||||
sourceName?: string | ComponentPrototypeType,
|
||||
) {
|
||||
insertBefore(targetNodeId: string, newNode: t.JSXElement) {
|
||||
this.ast = insertSiblingBeforeJSXElement(this.ast, targetNodeId, newNode);
|
||||
if (sourceName) {
|
||||
this.updateImportSpecifiersByPrototype(sourceName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -312,22 +361,15 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
* 替换目标节点为新节点
|
||||
* @param targetNodeId
|
||||
* @param newNode
|
||||
* @param sourcePrototype
|
||||
*/
|
||||
replaceNode(
|
||||
targetNodeId: string,
|
||||
newNode: t.JSXElement,
|
||||
sourceName?: string | ComponentPrototypeType,
|
||||
) {
|
||||
replaceNode(targetNodeId: string, newNode: t.JSXElement) {
|
||||
this.ast = replaceJSXElement(this.ast, targetNodeId, newNode);
|
||||
if (sourceName) {
|
||||
this.updateImportSpecifiersByPrototype(sourceName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换 jsx 跟结点的子元素
|
||||
* @deprecated 不推荐使用
|
||||
*/
|
||||
replaceViewChildren(
|
||||
childrenNodes: t.JSXElement[],
|
||||
@@ -339,51 +381,10 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
|
||||
if (importDeclarations?.length) {
|
||||
importDeclarations.forEach((item) => {
|
||||
this.updateImportSpecifiers(item);
|
||||
this.updateImportSpecifiersLegacy(item);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
import { JSXElement } from '@babel/types';
|
||||
import {
|
||||
array2object,
|
||||
ComponentPrototypeType,
|
||||
hasFileExtension,
|
||||
isString,
|
||||
@@ -16,7 +15,7 @@ import {
|
||||
getJSXElementChildrenNames,
|
||||
namesToImportDeclarations,
|
||||
getBlockNameByFilename,
|
||||
getPrivilegeCode,
|
||||
prototype2importDeclarationData,
|
||||
} from '../helpers';
|
||||
import { DropMethod } from './drop-target';
|
||||
import { HistoryMessage, TangoHistory } from './history';
|
||||
@@ -58,7 +57,7 @@ export interface IWorkspaceOptions {
|
||||
/**
|
||||
* 工作区文件变更事件
|
||||
*/
|
||||
onFilesChange?: (filenames: string[]) => void;
|
||||
onFilesChange?: IWorkspace['onFilesChange'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +94,7 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
|
||||
/**
|
||||
* 本地区块 { [blockName]: filePath }
|
||||
* FIXME: 修正类型 Record<string, TangoBlockModule>
|
||||
* TODO: 废弃这个,不再支持
|
||||
*/
|
||||
localBlocks: Record<string, string>;
|
||||
|
||||
@@ -123,17 +122,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
*/
|
||||
tangoConfigJson: TangoJsonFile;
|
||||
|
||||
/**
|
||||
* appJson.json 文件
|
||||
* FIXME: 是否保留 ???
|
||||
*/
|
||||
appJson: TangoJsonFile;
|
||||
|
||||
/**
|
||||
* 代码变更回调
|
||||
*/
|
||||
onFilesChange?: (filenames: string[]) => void;
|
||||
|
||||
/**
|
||||
* 绑定事件
|
||||
* TODO: 是否需要自己来管理 listeners,并及时进行 gc
|
||||
@@ -169,15 +157,12 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
* 获取页面列表
|
||||
*/
|
||||
get pages() {
|
||||
const appJsonPages = this.appJson?.getValue('pages');
|
||||
const pagesMap = array2object(appJsonPages || [], (item) => item.path);
|
||||
const ret: IPageConfigData[] = [];
|
||||
this.routeModule?.routes.forEach((item) => {
|
||||
const data = pagesMap[item.path];
|
||||
if (item.path !== '*') {
|
||||
ret.push({
|
||||
...data,
|
||||
path: item.path,
|
||||
name: item.component,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -229,7 +214,11 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
this.activeViewFile = '';
|
||||
this.files = new Map();
|
||||
this.isReady = false;
|
||||
this.onFilesChange = options?.onFilesChange;
|
||||
|
||||
if (options?.onFilesChange) {
|
||||
// 使用用户提供的 onFilesChange
|
||||
this.onFilesChange = options.onFilesChange;
|
||||
}
|
||||
|
||||
if (options?.files) {
|
||||
this.addFiles(options.files);
|
||||
@@ -389,10 +378,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.tangoConfigJson = module;
|
||||
break;
|
||||
case FileType.AppJson:
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.appJson = module;
|
||||
break;
|
||||
case FileType.Json:
|
||||
module = new TangoJsonFile(this, props);
|
||||
break;
|
||||
@@ -516,18 +501,13 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
this.routeModule.removeRoute(routePath).update();
|
||||
this.setActiveRoute(this.routeModule.routes[0]?.path || '/');
|
||||
}
|
||||
// remove appJson page
|
||||
this.appJson
|
||||
?.setValue('pages', (pages) => {
|
||||
return (pages as IPageConfigData[]).filter((page) => page.path !== routePath);
|
||||
})
|
||||
.update();
|
||||
this.removeFile(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的视图文件
|
||||
* @deprecated
|
||||
* @deprecated 使用 addViewFile 代替
|
||||
* FIXME: 重构这个逻辑
|
||||
* @param route 视图名
|
||||
* @param code 视图代码
|
||||
*/
|
||||
@@ -550,16 +530,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
*/
|
||||
addRoute(routeData: IPageConfigData, importFilePath: string) {
|
||||
this.routeModule?.addRoute(routeData.path, importFilePath).update();
|
||||
this.appJson
|
||||
?.setValue('pages', (pages) => {
|
||||
(pages as IPageConfigData[]).push({
|
||||
name: routeData.name || routeData.path,
|
||||
path: routeData.path,
|
||||
privilegeCode: getPrivilegeCode(this.packageJson?.getValue('name'), routeData.path),
|
||||
});
|
||||
return pages;
|
||||
})
|
||||
.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -571,18 +541,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
if (sourceRoutePath !== targetPageData.path) {
|
||||
this.routeModule?.updateRoute(sourceRoutePath, targetPageData.path).update();
|
||||
}
|
||||
this.appJson
|
||||
?.setValue('pages', (pages) => {
|
||||
for (const page of pages as IPageConfigData[]) {
|
||||
if (page.path === sourceRoutePath) {
|
||||
page.path = targetPageData.path;
|
||||
page.name = targetPageData.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pages;
|
||||
})
|
||||
.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -694,12 +652,10 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
*/
|
||||
listServiceFunctions() {
|
||||
const ret: Record<string, object> = {};
|
||||
Object.values(this.serviceModules).forEach((module) => {
|
||||
Object.keys(this.serviceModules).forEach((moduleName) => {
|
||||
const module = this.serviceModules[moduleName];
|
||||
Object.keys(module.serviceFunctions).forEach((name) => {
|
||||
const serviceKey =
|
||||
module.baseConfig.namespace !== 'index'
|
||||
? name
|
||||
: [module.baseConfig.namespace, name].join('.');
|
||||
const serviceKey = moduleName === 'index' ? name : [moduleName, name].join('.');
|
||||
ret[serviceKey] = module.serviceFunctions[name];
|
||||
});
|
||||
});
|
||||
@@ -949,7 +905,7 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
);
|
||||
|
||||
importDeclarations.forEach((importDeclaration) => {
|
||||
this.activeViewModule.updateImportSpecifiers(importDeclaration);
|
||||
this.activeViewModule.updateImportSpecifiersLegacy(importDeclaration);
|
||||
});
|
||||
|
||||
this.copyTempNodes.forEach((node) => {
|
||||
@@ -1000,7 +956,11 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
const sourcePrototype = this.getPrototype(sourceName);
|
||||
const newNode = prototype2jsxElement(sourcePrototype);
|
||||
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({
|
||||
message: HistoryMessage.InsertNode,
|
||||
data: {
|
||||
@@ -1020,7 +980,8 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
const sourcePrototype = this.getPrototype(sourceName);
|
||||
const newNode = prototype2jsxElement(sourcePrototype);
|
||||
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({
|
||||
message: HistoryMessage.ReplaceNode,
|
||||
data: {
|
||||
@@ -1038,7 +999,14 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
if (insertedPrototype) {
|
||||
const newNode = prototype2jsxElement(insertedPrototype);
|
||||
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({
|
||||
message: HistoryMessage.InsertNode,
|
||||
data: {
|
||||
@@ -1094,30 +1062,40 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
const targetFile = dropTarget.node?.file;
|
||||
const sourceFile = dragSource.node?.file;
|
||||
|
||||
// dragSourcePrototype to importDeclarations
|
||||
const { source, specifiers } = prototype2importDeclarationData(
|
||||
dragSourcePrototype,
|
||||
targetFile.filename,
|
||||
);
|
||||
|
||||
let isValidOperation = true;
|
||||
switch (dropTarget.method) {
|
||||
// 直接往目标节点的 children 里添加一个节点
|
||||
case DropMethod.InsertChild: {
|
||||
targetFile.insertChild(dropTarget.id, newNode, 'last', dragSourcePrototype);
|
||||
targetFile
|
||||
.insertChild(dropTarget.id, newNode, 'last')
|
||||
.addImportSpecifiers(source, specifiers);
|
||||
break;
|
||||
}
|
||||
case DropMethod.InsertFirstChild: {
|
||||
targetFile.insertChild(dropTarget.id, newNode, 'first', dragSourcePrototype);
|
||||
targetFile
|
||||
.insertChild(dropTarget.id, newNode, 'first')
|
||||
.addImportSpecifiers(source, specifiers);
|
||||
break;
|
||||
}
|
||||
// 往目标节点的后边插入一个节点
|
||||
case DropMethod.InsertAfter: {
|
||||
targetFile.insertAfter(dropTarget.id, newNode, dragSourcePrototype);
|
||||
targetFile.insertAfter(dropTarget.id, newNode).addImportSpecifiers(source, specifiers);
|
||||
break;
|
||||
}
|
||||
// 往目标节点的前方插入一个节点
|
||||
case DropMethod.InsertBefore: {
|
||||
targetFile.insertBefore(dropTarget.id, newNode, dragSourcePrototype);
|
||||
targetFile.insertBefore(dropTarget.id, newNode).addImportSpecifiers(source, specifiers);
|
||||
break;
|
||||
}
|
||||
// 替换目标节点
|
||||
case DropMethod.ReplaceNode: {
|
||||
targetFile.replaceNode(dropTarget.id, newNode, dragSourcePrototype);
|
||||
targetFile.replaceNode(dropTarget.id, newNode).addImportSpecifiers(source, specifiers);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1153,6 +1131,10 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
}
|
||||
}
|
||||
|
||||
onFilesChange(filenams: string[]) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新目标文件
|
||||
* @param filenames
|
||||
@@ -1166,7 +1148,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
},
|
||||
}),
|
||||
);
|
||||
this.onFilesChange?.(filenames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,11 +10,20 @@ export enum FileType {
|
||||
Module = 'module',
|
||||
StoreEntryModule = 'storeEntryModule',
|
||||
RouteModule = 'routeModule',
|
||||
BlockEntryModule = 'blockEntryModule',
|
||||
ServiceModule = 'serviceModule',
|
||||
StoreModule = 'storeModule',
|
||||
|
||||
// 组件配置文件
|
||||
ComponentPrototypeModule = 'componentPrototypeModule',
|
||||
// 组件运行调试入口文件,一般为 `/app.js`
|
||||
ComponentDemoEntryModule = 'componentDemoEntryModule',
|
||||
|
||||
// 区块入口文件 FIXME: 是否移除
|
||||
BlockEntryModule = 'blockEntryModule',
|
||||
|
||||
// jsx 类型视图文件
|
||||
JsxViewModule = 'jsxViewModule',
|
||||
// json 类型视图文件
|
||||
JsonViewModule = 'jsonViewModule',
|
||||
|
||||
// 非 js 文件
|
||||
@@ -100,7 +109,6 @@ export interface IImportSpecifierData {
|
||||
|
||||
/**
|
||||
* 解析的导入语句数据
|
||||
* FIXME: 需要考虑到有两行重复的语句的情况
|
||||
*/
|
||||
export type ImportDeclarationDataType = Record<string, IImportSpecifierData[]>;
|
||||
|
||||
@@ -193,7 +201,7 @@ export interface IPageConfigData {
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* PMS 权限码
|
||||
* 权限码
|
||||
*/
|
||||
privilegeCode?: string;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -3,15 +3,16 @@ import {
|
||||
serviceConfig2Node,
|
||||
isValidCode,
|
||||
isValidExpressionCode,
|
||||
code2expression,
|
||||
} from '../src/helpers';
|
||||
|
||||
describe('helpers', () => {
|
||||
test('isValidCode', () => {
|
||||
describe('ast helpers', () => {
|
||||
it('isValidCode', () => {
|
||||
expect(isValidCode('() => { hello world }')).toBeFalsy();
|
||||
expect(isValidCode('function() {}')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('isValidExpression', () => {
|
||||
it('isValidExpression', () => {
|
||||
expect(isValidExpressionCode('() => { }')).toBeTruthy();
|
||||
expect(isValidExpressionCode('1')).toBeTruthy();
|
||||
expect(isValidExpressionCode('"hello"')).toBeTruthy();
|
||||
@@ -20,9 +21,17 @@ describe('helpers', () => {
|
||||
expect(isValidExpressionCode('[1,2,3]')).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({
|
||||
url: '/api/backend/clientversion/appmarket/list',
|
||||
method: 'POST',
|
||||
@@ -40,4 +49,22 @@ describe('helpers', () => {
|
||||
} as any);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,40 +25,6 @@ describe('helpers', () => {
|
||||
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', () => {
|
||||
const node = code2expression(
|
||||
"<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', () => {
|
||||
expect(value2node(1).type).toEqual('NumericLiteral');
|
||||
});
|
||||
|
||||
@@ -3,6 +3,66 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.5.14](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.13...@music163/tango-designer@0.5.14) (2023-12-04)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-designer
|
||||
|
||||
## [0.5.13](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.12...@music163/tango-designer@0.5.13) (2023-12-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add filesFormatter to sandbox & fix update props with imports ([#71](https://github.com/netease/tango/issues/71)) ([138cbe9](https://github.com/netease/tango/commit/138cbe9b203b370aff42c1ae8086d69edacf35e9))
|
||||
|
||||
## [0.5.12](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.11...@music163/tango-designer@0.5.12) (2023-11-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fix render components tree ([76a5a2c](https://github.com/netease/tango/commit/76a5a2c65920bc42b019cd1f32a3cacd0d888638))
|
||||
|
||||
## [0.5.11](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.10...@music163/tango-designer@0.5.11) (2023-11-29)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-designer
|
||||
|
||||
## [0.5.10](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.9...@music163/tango-designer@0.5.10) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update default theme ([14cd4c5](https://github.com/netease/tango/commit/14cd4c54a1e5eed834d01921e186bdba9bbe7922))
|
||||
|
||||
## [0.5.9](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.8...@music163/tango-designer@0.5.9) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update theme ([2550411](https://github.com/netease/tango/commit/2550411c1c93037931d44aa9f7822ffe8caa900b))
|
||||
|
||||
## [0.5.8](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.7...@music163/tango-designer@0.5.8) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- event setter ([588861f](https://github.com/netease/tango/commit/588861f6492b45954e29ceed950567781b822492))
|
||||
|
||||
## [0.5.7](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.6...@music163/tango-designer@0.5.7) (2023-11-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update varTreeNode style ([#69](https://github.com/netease/tango/issues/69)) ([f604996](https://github.com/netease/tango/commit/f604996e56c9e4d58ff7f80cd98c3753986f081f))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-designer",
|
||||
"version": "0.5.3",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "lowcode designer",
|
||||
"keywords": [
|
||||
"react"
|
||||
@@ -33,12 +33,12 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/request": "^0.1.2",
|
||||
"@music163/tango-context": "^0.2.5",
|
||||
"@music163/tango-core": "^0.2.4",
|
||||
"@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",
|
||||
"@music163/tango-context": "^1.0.0-alpha.0",
|
||||
"@music163/tango-core": "^1.0.0-alpha.0",
|
||||
"@music163/tango-helpers": "^0.1.8",
|
||||
"@music163/tango-sandbox": "^1.0.0-alpha.0",
|
||||
"@music163/tango-setting-form": "^1.0.0-alpha.0",
|
||||
"@music163/tango-ui": "^1.0.0-alpha.0",
|
||||
"antd": "^4.24.2",
|
||||
"cash-dom": "^8.1.2",
|
||||
"classnames": "^2.3.2",
|
||||
|
||||
@@ -317,6 +317,10 @@ const varTreeStyle = css`
|
||||
font-family: Consolas, Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
.ant-tree-node-content-wrapper.ant-tree-node-content-wrapper-normal {
|
||||
width: calc(100% - 50px);
|
||||
}
|
||||
|
||||
.ant-tree .ant-tree-treenode {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -372,11 +376,11 @@ export function VariableTree({
|
||||
const isDeletable = node.showDeleteIcon ?? showDeleteIcon;
|
||||
return (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Text>
|
||||
<Text flex="1" truncated>
|
||||
{node.title}
|
||||
{node.type === 'function' && <FunctionOutlined />}
|
||||
</Text>
|
||||
<Box>
|
||||
<Box flex="0 0 72px" textAlign="right">
|
||||
{isDeletable && (
|
||||
<Popconfirm
|
||||
title="确认删除吗?该操作会导致引用此模型的代码报错,请谨慎操作!"
|
||||
|
||||
@@ -36,6 +36,8 @@ export function DesignerPanel(props: DesignerPanelProps) {
|
||||
height="48px"
|
||||
bg="colors.custom.topNavBg"
|
||||
color="colors.custom.topNavColor"
|
||||
borderBottom="solid"
|
||||
borderColor="colors.custom.topNavBorderColor"
|
||||
className="DesignerPanelHeader"
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
|
||||
@@ -3,9 +3,9 @@ import { SystemProvider, extendTheme } from 'coral-system';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { TangoEngineProvider, ITangoEngineContext } from '@music163/tango-context';
|
||||
import { createFromIconfontCN } from '@ant-design/icons';
|
||||
import { DesignerProvider, IDesignerContext } from './context';
|
||||
import { theme as baseTheme } from './theme';
|
||||
import zhCN from 'antd/lib/locale/zh_CN';
|
||||
import { DesignerProvider, IDesignerContext } from './context';
|
||||
import defaultTheme from './themes/default';
|
||||
|
||||
export interface DesignerProps extends IDesignerContext, ITangoEngineContext {
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ export function Designer(props: DesignerProps) {
|
||||
iconfontScriptUrl = '//at.alicdn.com/t/c/font_2891794_lzc7rtwuzf.js',
|
||||
children,
|
||||
} = props;
|
||||
const theme = useMemo(() => extendTheme(themeProp, baseTheme), [themeProp]);
|
||||
const theme = useMemo(() => extendTheme(themeProp, defaultTheme), [themeProp]);
|
||||
useEffect(() => {
|
||||
createFromIconfontCN({
|
||||
scriptUrl: iconfontScriptUrl,
|
||||
|
||||
@@ -12,6 +12,7 @@ export * from './sidebar';
|
||||
export * from './toolbar';
|
||||
export * from './selection-menu';
|
||||
export * from './widgets';
|
||||
export * from './themes';
|
||||
|
||||
export { register as registerSetter } from '@music163/tango-setting-form';
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ const navigatorStyle = css`
|
||||
|
||||
.navigatorInput {
|
||||
flex: 1;
|
||||
border-color: transparent;
|
||||
background-color: var(--tango-colors-fill2);
|
||||
|
||||
&:focus {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -68,7 +74,7 @@ export class Navigator extends React.Component<NavigatorProps, NavigatorState> {
|
||||
className="navigator"
|
||||
bg="white"
|
||||
px="l"
|
||||
py="4px"
|
||||
py="m"
|
||||
borderBottom="solid"
|
||||
borderBottomColor="line.normal"
|
||||
css={navigatorStyle}
|
||||
|
||||
@@ -23,6 +23,12 @@ export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'o
|
||||
* tangoConfigJson 处理器
|
||||
*/
|
||||
configFormatter?: IMergeTangoConfigJsonConfig['formatter'];
|
||||
/**
|
||||
* 文件格式化器
|
||||
* @param files 沙箱文件列表
|
||||
* @returns 修改后的文件列表
|
||||
*/
|
||||
filesFormatter?: (files: CodeSandboxProps['files']) => CodeSandboxProps['files'];
|
||||
sandboxType?: 'design' | 'preview';
|
||||
mode?: 'single' | 'combined';
|
||||
injectScript?: string;
|
||||
@@ -41,6 +47,7 @@ const LANDING_PAGE_PATH = '/__background_landing_page__';
|
||||
function useSandbox({
|
||||
isPreview: isPreviewProp,
|
||||
configFormatter,
|
||||
filesFormatter,
|
||||
onViewChange,
|
||||
onMessage: onMessageProp,
|
||||
onLoad: onLoadProp,
|
||||
@@ -79,7 +86,12 @@ function useSandbox({
|
||||
prev[filename] = { code };
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
files = fixSandboxFiles(files, workspace.entry);
|
||||
if (filesFormatter) {
|
||||
// 支持用户对传入的文件进行修改
|
||||
files = filesFormatter(files);
|
||||
}
|
||||
|
||||
const onMessage = (data: any) => onMessageProp && onMessageProp(data, getSandboxConfig());
|
||||
const onLoad = () => onLoadProp && onLoadProp(getSandboxConfig());
|
||||
@@ -119,6 +131,7 @@ const PreviewSandbox = observer(
|
||||
startRoute,
|
||||
injectScript,
|
||||
sandboxType: 'preview',
|
||||
...rest,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -166,6 +179,7 @@ const DesignSandbox = observer(
|
||||
startRoute,
|
||||
injectScript,
|
||||
sandboxType: 'design',
|
||||
...rest,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -411,6 +425,7 @@ function fixSandboxFiles(files: Record<string, { code: string }>, entry = '/src/
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ import { AutoComplete } from 'antd';
|
||||
import { ActionSelect } from '@music163/tango-ui';
|
||||
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||
import { wrapCode } from '@music163/tango-helpers';
|
||||
import { ExpressionModal } from './expression-setter';
|
||||
import { ExpressionModal, getWrappedExpressionCode } from './expression-setter';
|
||||
|
||||
enum EventAction {
|
||||
NoAction = 'noAction',
|
||||
@@ -48,8 +47,9 @@ export function EventSetter(props: EventSetterProps) {
|
||||
|
||||
const handleChange = useCallback<FormItemComponentProps['onChange']>(
|
||||
(nextValue: any, ...args) => {
|
||||
if (nextValue !== value) {
|
||||
onChange(nextValue, ...args);
|
||||
const ret = getWrappedExpressionCode(nextValue);
|
||||
if (ret !== value) {
|
||||
onChange(ret, ...args);
|
||||
}
|
||||
},
|
||||
[onChange, value],
|
||||
@@ -84,7 +84,7 @@ export function EventSetter(props: EventSetterProps) {
|
||||
onCancel={() => setExpModalVisible(false)}
|
||||
onOk={(nextValue) => {
|
||||
setExpModalVisible(false);
|
||||
handleChange(wrapCode(nextValue));
|
||||
handleChange(nextValue);
|
||||
}}
|
||||
dataSource={actionVariables}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, Text, css } from 'coral-system';
|
||||
import { Modal } from 'antd';
|
||||
import { isValidExpressionCode, value2expressionCode } from '@music163/tango-core';
|
||||
import {
|
||||
isVariableString,
|
||||
getVariableContent,
|
||||
noop,
|
||||
useBoolean,
|
||||
getValue,
|
||||
wrapCode,
|
||||
} from '@music163/tango-helpers';
|
||||
isValidExpressionCode,
|
||||
isWrappedByExpressionContainer,
|
||||
value2expressionCode,
|
||||
} from '@music163/tango-core';
|
||||
import { getVariableContent, noop, useBoolean, getValue } from '@music163/tango-helpers';
|
||||
import { CloseCircleFilled, ExpandAltOutlined } from '@ant-design/icons';
|
||||
import { IconButton, Panel, InputCode } from '@music163/tango-ui';
|
||||
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||
@@ -18,7 +15,7 @@ import { EditableVariableTree, IVariableTreeNode } from '../components';
|
||||
import { useSandboxQuery } from '../context';
|
||||
|
||||
export const expressionValueValidate = (value: string) => {
|
||||
if (isVariableString(value)) {
|
||||
if (isWrappedByExpressionContainer(value)) {
|
||||
const exp = getVariableContent(value);
|
||||
if (!isValidExpressionCode(exp)) {
|
||||
return '表达式存在语法错误!';
|
||||
@@ -27,7 +24,7 @@ export const expressionValueValidate = (value: string) => {
|
||||
};
|
||||
|
||||
export const jsonValueValidate = (value: string) => {
|
||||
if (isVariableString(value)) {
|
||||
if (isWrappedByExpressionContainer(value)) {
|
||||
const jsonStr = getVariableContent(value);
|
||||
try {
|
||||
JSON.parse(jsonStr);
|
||||
@@ -37,6 +34,23 @@ export const jsonValueValidate = (value: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 返回 `{}` 包裹后的表达式代码
|
||||
* @param code 原始代码
|
||||
* @returns
|
||||
*/
|
||||
export function getWrappedExpressionCode(code: string) {
|
||||
let ret;
|
||||
if (!code) {
|
||||
// do nothing
|
||||
} else if (isWrappedByExpressionContainer(code)) {
|
||||
ret = code;
|
||||
} else {
|
||||
ret = `{${code}}`;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const suffixStyle = css`
|
||||
.anticon-close-circle {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
@@ -78,16 +92,17 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
|
||||
|
||||
const change = useCallback(
|
||||
(code: string) => {
|
||||
if (!code) {
|
||||
onChange(undefined);
|
||||
if (code === valueProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getVariableContent(code) === value2expressionCode(valueProp)) {
|
||||
const ret = getWrappedExpressionCode(code);
|
||||
|
||||
if (ret === valueProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(wrapCode(code));
|
||||
onChange(ret);
|
||||
},
|
||||
[valueProp, onChange],
|
||||
);
|
||||
@@ -132,7 +147,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
|
||||
value={inputValue}
|
||||
onCancel={() => off()}
|
||||
onOk={(value) => {
|
||||
onChange(value);
|
||||
change(value);
|
||||
off();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -168,4 +168,6 @@ const setters: IFormItemCreateOptions[] = [
|
||||
{ name: 'borderSetter', component: BorderSetter },
|
||||
];
|
||||
|
||||
setters.forEach(register);
|
||||
export function registerBuiltinSetters() {
|
||||
setters.forEach(register);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import { SettingForm, FormModel, SettingFormProps } from '@music163/tango-settin
|
||||
import { Panel } from '@music163/tango-ui';
|
||||
import { clone } from '@music163/tango-helpers';
|
||||
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
|
||||
import './setters';
|
||||
import { registerBuiltinSetters } from './setters';
|
||||
|
||||
registerBuiltinSetters();
|
||||
|
||||
export interface SettingPanelProps extends SettingFormProps {
|
||||
title?: React.ReactNode;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||
import { Box, css } from 'coral-system';
|
||||
import { Button, FormProps, Empty, Space, Dropdown } from 'antd';
|
||||
import { Button, FormProps, Empty, Space, Dropdown, Form, Select, Input } from 'antd';
|
||||
import { PlayCircleOutlined } from '@ant-design/icons';
|
||||
import { Form, InputCode, Panel, JsonView, Search } from '@music163/tango-ui';
|
||||
import { InputCode, Panel, JsonView, Search } from '@music163/tango-ui';
|
||||
import {
|
||||
isNil,
|
||||
isVariableString,
|
||||
getVariableContent,
|
||||
isValidFunctionCode,
|
||||
logger,
|
||||
code2object,
|
||||
filterTreeData,
|
||||
} from '@music163/tango-helpers';
|
||||
import { isWrappedByExpressionContainer } from '@music163/tango-core';
|
||||
import { useSandboxQuery } from '../../context';
|
||||
import { VariableTree } from '../../components';
|
||||
|
||||
@@ -37,15 +37,6 @@ enum ServiceFunctionMethodType {
|
||||
// DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
/**
|
||||
* requestType
|
||||
* headers: Content-Type
|
||||
*/
|
||||
enum DataServiceRequestType {
|
||||
'application/json' = 'json',
|
||||
'application/x-www-form-urlencoded' = 'x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 api 路径转换为默认的驼峰方法名
|
||||
*/
|
||||
@@ -79,12 +70,6 @@ const httpMethods = Object.keys(ServiceFunctionMethodType).map((key) => ({
|
||||
value: key,
|
||||
}));
|
||||
|
||||
// requestType 类型
|
||||
const requestTypes = Object.keys(DataServiceRequestType).map((key) => ({
|
||||
label: key,
|
||||
value: DataServiceRequestType[key],
|
||||
}));
|
||||
|
||||
const detailFormStyle = css`
|
||||
.ant-form-item {
|
||||
margin-bottom: 12px;
|
||||
@@ -187,7 +172,10 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
|
||||
const shapeValues = { ...val };
|
||||
delete shapeValues.type;
|
||||
// 兼容旧版,如果 formatter 包裹了 {} 则删掉首尾
|
||||
if (shapeValues.formatter && isVariableString(shapeValues.formatter)) {
|
||||
if (
|
||||
shapeValues.formatter &&
|
||||
isWrappedByExpressionContainer(shapeValues.formatter)
|
||||
) {
|
||||
shapeValues.formatter = getVariableContent(shapeValues.formatter);
|
||||
}
|
||||
return shapeValues;
|
||||
@@ -258,8 +246,8 @@ function ServiceDetailForm({
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={6}
|
||||
wrapperCol={17}
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 17 }}
|
||||
colon={false}
|
||||
layout="horizontal"
|
||||
initialValues={initialValues}
|
||||
@@ -282,12 +270,9 @@ function ServiceDetailForm({
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
component="select"
|
||||
componentProps={{
|
||||
options: serviceModules,
|
||||
disabled: disabled || isModifyMode,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Select options={serviceModules} disabled={disabled || isModifyMode} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="方法名"
|
||||
name="name"
|
||||
@@ -305,11 +290,6 @@ function ServiceDetailForm({
|
||||
},
|
||||
},
|
||||
]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '请输入数据服务调用名称',
|
||||
disabled: disabled || isModifyMode,
|
||||
}}
|
||||
extra={
|
||||
!disabled &&
|
||||
!isModifyMode && (
|
||||
@@ -328,38 +308,15 @@ function ServiceDetailForm({
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Item
|
||||
label="路径"
|
||||
name="url"
|
||||
rules={[{ required: true, type: 'url' }]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '请输入数据服务调用名称',
|
||||
disabled: disabled || isModifyMode,
|
||||
}}
|
||||
/>
|
||||
<Form.Item
|
||||
label="方法"
|
||||
name="method"
|
||||
component="select"
|
||||
componentProps={{
|
||||
options: httpMethods,
|
||||
placeholder: '请选择 HTTP 请求方法',
|
||||
disabled,
|
||||
}}
|
||||
/>
|
||||
<Form.Item
|
||||
label="编码类型"
|
||||
name="requestType"
|
||||
tooltip="设置请求头的 Content-Type,若不设置,则默认变为为 JSON"
|
||||
component="select"
|
||||
componentProps={{
|
||||
options: requestTypes,
|
||||
placeholder: '请求头Content-Type,默认为JSON',
|
||||
disabled,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Input placeholder="请输入数据服务调用名称" disabled={disabled || isModifyMode} />
|
||||
</Form.Item>
|
||||
<Form.Item label="路径" name="url" rules={[{ required: true, type: 'url' }]}>
|
||||
<Input placeholder="请输入数据服务调用名称" disabled={disabled || isModifyMode} />
|
||||
</Form.Item>
|
||||
<Form.Item label="方法" name="method">
|
||||
<Select options={httpMethods} placeholder="请选择 HTTP 请求方法" disabled={disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="格式化响应"
|
||||
name="formatter"
|
||||
|
||||
@@ -148,6 +148,12 @@ export const ComponentsTree: React.FC<ComponentsTreeProps> = observer(
|
||||
setSelectedKeys(workspace.selectSource.selected.map((item) => item.id));
|
||||
}, [workspace.selectSource.selected]);
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedKeys(getNodeKeys(nodesTree));
|
||||
// nodeTree 更新后重新计算expandKeys
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodesTree?.[0]?.id]);
|
||||
|
||||
if (!nodesTree.length) {
|
||||
return (
|
||||
<Box>
|
||||
@@ -156,12 +162,6 @@ export const ComponentsTree: React.FC<ComponentsTreeProps> = observer(
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedKeys(getNodeKeys(nodesTree));
|
||||
// nodeTree 更新后重新计算expandKeys
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodesTree?.[0]?.id]);
|
||||
|
||||
return (
|
||||
<Box css={outlineStyle}>
|
||||
<Tree
|
||||
@@ -230,4 +230,4 @@ export const ComponentsTree: React.FC<ComponentsTreeProps> = observer(
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Button, Space } from 'antd';
|
||||
import { useBoolean } from '@music163/tango-helpers';
|
||||
import { Panel, Form, IconButton } from '@music163/tango-ui';
|
||||
import { Button, Space, Form, Input } from 'antd';
|
||||
import { FileAddOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { useBoolean } from '@music163/tango-helpers';
|
||||
import { Panel, IconButton } from '@music163/tango-ui';
|
||||
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||
import { EditableVariableTree, EditableVariableTreeProps } from '../components';
|
||||
|
||||
@@ -30,7 +30,6 @@ export const VariablePanel = observer(
|
||||
|
||||
return (
|
||||
<Panel
|
||||
shape="solid"
|
||||
className="ModelView"
|
||||
height={wrapperHeight}
|
||||
title="视图模型与变量管理"
|
||||
@@ -91,9 +90,10 @@ interface AddStoreFormProps {
|
||||
|
||||
function AddStoreForm({ existNames = [], onCancel, onSubmit }: AddStoreFormProps) {
|
||||
return (
|
||||
<Panel title="添加模型" subTitle="模型可以用来组织一组变量">
|
||||
<Panel title="添加模型" subTitle="模型可以用来组织一组变量" bodyProps={{ p: 'l' }}>
|
||||
<Form
|
||||
autoComplete="off"
|
||||
colon={false}
|
||||
onFinish={(values) => {
|
||||
onSubmit && onSubmit(values);
|
||||
}}
|
||||
@@ -113,11 +113,9 @@ function AddStoreForm({ existNames = [], onCancel, onSubmit }: AddStoreFormProps
|
||||
},
|
||||
},
|
||||
]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '请输入模型名称',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Input placeholder="请输入模型名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label=" ">
|
||||
<Space>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { extendTheme } from 'coral-system';
|
||||
|
||||
export const theme = extendTheme({
|
||||
export default extendTheme({
|
||||
colors: {
|
||||
primary: {
|
||||
10: '#e8f3ff',
|
||||
@@ -27,11 +27,16 @@ export const theme = extendTheme({
|
||||
custom: {
|
||||
topNavBg: '#222',
|
||||
topNavColor: '#fff',
|
||||
topNavBorderColor: '#222',
|
||||
toolbarDividerColor: 'gray.60',
|
||||
toolbarButtonBg: 'rgba(223, 223, 223, 0.08)',
|
||||
toolbarButtonBgHover: '#4080ff',
|
||||
toolbarButtonTextColor: '#f5f5f5',
|
||||
toolbarButtonBgDisabled: 'rgba(223,223,223, 0.08)',
|
||||
toolbarButtonBgActive: '#4080ff',
|
||||
toolbarButtonTextColor: '#FFF',
|
||||
toolbarButtonTextColorHover: '#FFF',
|
||||
toolbarButtonTextColorDisabled: '#bfbfbf',
|
||||
toolbarButtonTextColorDisabled: 'hsla(0,0%,100%,0.3)',
|
||||
toolbarButtonTextColorActive: '#fff',
|
||||
sidebarBg: '#fff',
|
||||
sidebarExpandBg: '#fff',
|
||||
sidebarItemActiveBg: '#f2f3f5',
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as themeDefault } from './default';
|
||||
export { default as themeLight } from './light';
|
||||
@@ -0,0 +1,25 @@
|
||||
import { extendTheme } from 'coral-system';
|
||||
|
||||
export default extendTheme({
|
||||
colors: {
|
||||
custom: {
|
||||
topNavBg: '#FFF',
|
||||
topNavColor: '#333',
|
||||
topNavBorderColor: 'rgb(229,230,235)',
|
||||
toolbarDividerColor: 'rgb(229,230,235)',
|
||||
toolbarButtonBg: 'rgb(242,243,245)',
|
||||
toolbarButtonBgHover: 'rgb(229,230,235)',
|
||||
toolbarButtonBgDisabled: 'rgb(247,248,250)',
|
||||
toolbarButtonBgActive: 'colors.brand',
|
||||
toolbarButtonTextColor: 'colors.text2',
|
||||
toolbarButtonTextColorHover: 'colors.text2',
|
||||
toolbarButtonTextColorDisabled: 'colors.text4',
|
||||
toolbarButtonTextColorActive: '#FFF',
|
||||
sidebarBg: '#fff',
|
||||
sidebarExpandBg: '#fff',
|
||||
sidebarItemActiveBg: '#f2f3f5',
|
||||
sidebarItemHoverBg: '#f2f3f5',
|
||||
viewportBg: '#f0f2f5',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box } from 'coral-system';
|
||||
import { ToggleButton, IconFont } from '@music163/tango-ui';
|
||||
import { ToggleButton, RedoOutlined, UndoOutlined } from '@music163/tango-ui';
|
||||
import { observer, useWorkspace } from '@music163/tango-context';
|
||||
|
||||
export const HistoryTool = observer(() => {
|
||||
@@ -10,24 +10,24 @@ export const HistoryTool = observer(() => {
|
||||
<ToggleButton
|
||||
shape="ghost"
|
||||
disabled={!workspace.history.couldBack || workspace.history.index === 0}
|
||||
tip="撤销"
|
||||
tooltip="撤销"
|
||||
onClick={() => {
|
||||
workspace.history.back();
|
||||
workspace.selectSource.clear();
|
||||
}}
|
||||
>
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
shape="ghost"
|
||||
disabled={!workspace.history.couldForward}
|
||||
tip="重做"
|
||||
tooltip="重做"
|
||||
onClick={() => {
|
||||
workspace.history.forward();
|
||||
workspace.selectSource.clear();
|
||||
}}
|
||||
>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ export const PreviewTool = observer(() => {
|
||||
designer.setActiveView('design');
|
||||
}
|
||||
}}
|
||||
tip="预览"
|
||||
tooltip="预览"
|
||||
>
|
||||
<EyeOutlined />
|
||||
</ToggleButton>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Button, Popconfirm, Modal, ModalProps } from 'antd';
|
||||
import { Button, Popconfirm, Modal, ModalProps, Form, Input } from 'antd';
|
||||
import { Box, css, Group, Text } from 'coral-system';
|
||||
import { FileOutlined, DeleteOutlined, CopyOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { array2object, callAll } from '@music163/tango-helpers';
|
||||
import { IPageConfigData } from '@music163/tango-core';
|
||||
import { Form, ToggleButton } from '@music163/tango-ui';
|
||||
import { ToggleButton } from '@music163/tango-ui';
|
||||
import { observer, useWorkspace } from '@music163/tango-context';
|
||||
|
||||
export const RouteSwitchTool = observer(() => {
|
||||
@@ -71,6 +71,7 @@ function PageSelect({ value, onSelect, onRemove, onUpdate, onCopy, options }: Pa
|
||||
}}
|
||||
/>
|
||||
}
|
||||
icon={<FileOutlined />}
|
||||
>
|
||||
{current?.name || current?.label || value}
|
||||
</ToggleButton>
|
||||
@@ -249,17 +250,13 @@ function CopyViewModalForm({
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
wrapperCol={20}
|
||||
wrapperCol={{ span: 20 }}
|
||||
form={form}
|
||||
initialValues={{ name: current?.name, path: current?.path }}
|
||||
>
|
||||
<Form.Item
|
||||
label="页面标题"
|
||||
name="name"
|
||||
required
|
||||
component="input"
|
||||
componentProps={{ placeholder: '请输入标题' }}
|
||||
/>
|
||||
<Form.Item label="页面标题" name="name" required>
|
||||
<Input placeholder="请输入标题" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="页面路由"
|
||||
name="path"
|
||||
@@ -280,11 +277,9 @@ function CopyViewModalForm({
|
||||
message: '非法的路由地址',
|
||||
},
|
||||
]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '输入页面路由',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Input placeholder="请输入页面路由" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import { observer, useDesigner } from '@music163/tango-context';
|
||||
import { Box } from 'coral-system';
|
||||
import { IconFont, ToggleButton } from '@music163/tango-ui';
|
||||
import {
|
||||
OpenPanelFilledLeftOutlined,
|
||||
OpenPanelFilledRightOutlined,
|
||||
OpenPanelLeftOutlined,
|
||||
OpenPanelRightOutlined,
|
||||
ToggleButton,
|
||||
} from '@music163/tango-ui';
|
||||
|
||||
export const TogglePanelTool = observer(() => {
|
||||
const designer = useDesigner();
|
||||
@@ -14,20 +20,14 @@ export const TogglePanelTool = observer(() => {
|
||||
designer.setActiveSidebarPanel(designer.activeSidebarPanel ? '' : 'outline');
|
||||
}}
|
||||
>
|
||||
<IconFont
|
||||
type={
|
||||
designer.activeSidebarPanel ? 'icon-open-panel-filled-left' : 'icon-open-panel-left'
|
||||
}
|
||||
/>
|
||||
{designer.activeSidebarPanel ? <OpenPanelFilledLeftOutlined /> : <OpenPanelLeftOutlined />}
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
tooltip="隐藏/显示右侧面板"
|
||||
shape="ghost"
|
||||
onClick={() => designer.toggleRightPanel()}
|
||||
>
|
||||
<IconFont
|
||||
type={designer.showRightPanel ? 'icon-open-panel-filled-right' : 'icon-open-panel-right'}
|
||||
/>
|
||||
{designer.showRightPanel ? <OpenPanelFilledRightOutlined /> : <OpenPanelRightOutlined />}
|
||||
</ToggleButton>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -81,7 +81,7 @@ function ToolbarItem({ placement, widgetProps, children }: ToolbarItemProps) {
|
||||
}
|
||||
|
||||
function Separator() {
|
||||
return <Box className="Separator" width={1} height={16} bg="gray.60" />;
|
||||
return <Box className="Separator" width={1} height={16} bg="colors.custom.toolbarDividerColor" />;
|
||||
}
|
||||
|
||||
Toolbar.Item = ToolbarItem;
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ViewportSwitchTool = observer(() => {
|
||||
onClick={() => {
|
||||
designer.setSimulator('desktop');
|
||||
}}
|
||||
tip="桌面视图"
|
||||
tooltip="桌面视图"
|
||||
>
|
||||
<DesktopOutlined />
|
||||
</ToggleButton>
|
||||
@@ -27,7 +27,7 @@ export const ViewportSwitchTool = observer(() => {
|
||||
onClick={() => {
|
||||
designer.setSimulator('phone');
|
||||
}}
|
||||
tip="手机视图"
|
||||
tooltip="手机视图"
|
||||
>
|
||||
<MobileOutlined />
|
||||
</ToggleButton>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-helpers",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.8",
|
||||
"description": "Shared types, helpers, and hooks of tango-apps",
|
||||
"keywords": [
|
||||
"shared",
|
||||
|
||||
@@ -182,6 +182,8 @@ const templatePattern = /^{(.+)}$/s;
|
||||
|
||||
/**
|
||||
* 判断给定字符串是否为变量字符串
|
||||
* @deprecated 使用 isWrappedByExpressionContainer 代替
|
||||
*
|
||||
* @example {[]}
|
||||
* @example {{}}
|
||||
* @example {this.foo}
|
||||
@@ -191,6 +193,7 @@ const templatePattern = /^{(.+)}$/s;
|
||||
*/
|
||||
export function isVariableString(str: string) {
|
||||
// 先检查是否是简单的对象
|
||||
// FIXME: 这里有问题,如果代码中有引用,会被误判
|
||||
if (code2object(str)) {
|
||||
return false;
|
||||
}
|
||||
@@ -205,6 +208,8 @@ export function isVariableString(str: string) {
|
||||
* @example "hello" => {"hello"}
|
||||
* @example () => {} => {() => {}}
|
||||
*
|
||||
* @deprecated 有问题,不要使用
|
||||
*
|
||||
* @param code 输入代码
|
||||
* @returns 加上花括号后的代码
|
||||
*/
|
||||
|
||||
@@ -33,94 +33,11 @@ export type OptionType = {
|
||||
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 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 };
|
||||
|
||||
/**
|
||||
@@ -163,63 +80,3 @@ export type SetterOnChangeDetailType = {
|
||||
relatedImports?: string[];
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -15,8 +15,8 @@ export type PartialRecord<K extends keyof any, T> = {
|
||||
[P in K]?: T;
|
||||
};
|
||||
|
||||
export type ReactComponentProps = {
|
||||
export interface ReactComponentProps {
|
||||
children?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.16](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.15...@music163/tango-sandbox@0.1.16) (2023-12-04)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
## [0.1.15](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.14...@music163/tango-sandbox@0.1.15) (2023-12-04)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
## [0.1.14](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.13...@music163/tango-sandbox@0.1.14) (2023-11-30)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
## [0.1.13](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.12...@music163/tango-sandbox@0.1.13) (2023-11-29)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
## [0.1.12](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.11...@music163/tango-sandbox@0.1.12) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-sandbox",
|
||||
"version": "0.1.9",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "sandbox of tango apps",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -29,8 +29,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/tango-core": "^0.2.4",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@music163/tango-core": "^1.0.0-alpha.0",
|
||||
"@music163/tango-helpers": "^0.1.8",
|
||||
"crypto-js": "^4.1.1",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"react-frame-component": "^5.2.4"
|
||||
|
||||
@@ -3,6 +3,45 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.16](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.15...@music163/tango-setting-form@0.2.16) (2023-12-04)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [0.2.15](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.14...@music163/tango-setting-form@0.2.15) (2023-12-04)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [0.2.14](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.13...@music163/tango-setting-form@0.2.14) (2023-11-30)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [0.2.13](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.12...@music163/tango-setting-form@0.2.13) (2023-11-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- workspace onFilesChange ([#70](https://github.com/netease/tango/issues/70)) ([accd826](https://github.com/netease/tango/commit/accd8263764c811ea8175a9cd341fc6fa6c75967))
|
||||
|
||||
## [0.2.12](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.11...@music163/tango-setting-form@0.2.12) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update theme ([2550411](https://github.com/netease/tango/commit/2550411c1c93037931d44aa9f7822ffe8caa900b))
|
||||
- update type ([1db5294](https://github.com/netease/tango/commit/1db52948dbdc28f4533eb968f7da1bf6d16173d4))
|
||||
|
||||
## [0.2.11](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.10...@music163/tango-setting-form@0.2.11) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-setting-form",
|
||||
"version": "0.2.8",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "setting form of tango-apps",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -28,12 +28,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@music163/tango-ui": "^0.1.9",
|
||||
"@music163/tango-core": "^1.0.0-alpha.0",
|
||||
"@music163/tango-helpers": "^0.1.8",
|
||||
"@music163/tango-ui": "^1.0.0-alpha.0",
|
||||
"antd": "^4.24.2",
|
||||
"coral-system": "^1.0.5",
|
||||
"mobx": "6.9.0",
|
||||
"mobx-react-lite": "4.0.2"
|
||||
"mobx": "6.12.0",
|
||||
"mobx-react-lite": "4.0.5"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -4,18 +4,19 @@ import { observer } from 'mobx-react-lite';
|
||||
import {
|
||||
clone,
|
||||
ComponentPropType,
|
||||
isVariableString,
|
||||
SetterOnChangeDetailType,
|
||||
useBoolean,
|
||||
} from '@music163/tango-helpers';
|
||||
import { IconFont, ToggleButton } from '@music163/tango-ui';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { InputProps, Tooltip } from 'antd';
|
||||
import { isWrappedByExpressionContainer } from '@music163/tango-core';
|
||||
import { ToggleButton, CodeOutlined } from '@music163/tango-ui';
|
||||
import { InputProps } from 'antd';
|
||||
import { useFormModel, useFormVariable } from './context';
|
||||
import { FormControl } from './form-ui';
|
||||
import { Box, Text } from 'coral-system';
|
||||
|
||||
export interface FormItemProps extends ComponentPropType {
|
||||
extra?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface FormItemComponentProps<T = any> {
|
||||
@@ -76,6 +77,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
getVisible: getVisibleProp,
|
||||
getSetterProps: getSetterPropsProp,
|
||||
extra,
|
||||
footer,
|
||||
}: FormItemProps) {
|
||||
const { disableSwitchExpressionSetter } = useFormVariable();
|
||||
const model = useFormModel();
|
||||
@@ -83,7 +85,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
const value = toJS(field.value ?? defaultValue);
|
||||
const disableVariableSetter = disableSwitchExpressionSetter ?? disableVariableSetterProp;
|
||||
const [isVariable, { toggle: toggleIsVariable }] = useBoolean(
|
||||
() => !disableVariableSetter && isVariableString(value),
|
||||
() => !disableVariableSetter && isWrappedByExpressionContainer(value),
|
||||
);
|
||||
|
||||
const setterName = isVariable ? 'expressionSetter' : setter;
|
||||
@@ -106,7 +108,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
|
||||
let expProps = {};
|
||||
|
||||
// FIXME: 重新考虑这段代码的位置
|
||||
// FIXME: 重新考虑这段代码的位置,外置这个逻辑
|
||||
if (['expressionSetter', 'actionSetter', 'eventSetter'].includes(setter) || isVariable) {
|
||||
expProps = {
|
||||
modalTitle: title,
|
||||
@@ -141,7 +143,8 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
docs={docs}
|
||||
error={field.error}
|
||||
extra={
|
||||
<>
|
||||
<Box>
|
||||
{extra}
|
||||
{!disableVariableSetter ? (
|
||||
<ToggleButton
|
||||
borderRadius="s"
|
||||
@@ -153,12 +156,12 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
selected={isVariable}
|
||||
onClick={() => toggleIsVariable()}
|
||||
>
|
||||
<IconFont type="icon-brackets-curly" />
|
||||
<CodeOutlined />
|
||||
</ToggleButton>
|
||||
) : null}
|
||||
{extra}
|
||||
</>
|
||||
</Box>
|
||||
}
|
||||
footer={footer}
|
||||
data-setter={setterName}
|
||||
data-field={name}
|
||||
>
|
||||
@@ -187,10 +190,6 @@ export function register(config: IFormItemCreateOptions) {
|
||||
});
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
color: 'red',
|
||||
};
|
||||
|
||||
export function SettingFormItem(props: FormItemProps) {
|
||||
const { setter } = props;
|
||||
const Comp = REGISTERED_FORM_ITEM_MAP[setter];
|
||||
@@ -199,10 +198,10 @@ export function SettingFormItem(props: FormItemProps) {
|
||||
return (
|
||||
<Fallback
|
||||
{...props}
|
||||
extra={
|
||||
<Tooltip title={`${props.name}: invalid setter ${props.setter}`}>
|
||||
<QuestionCircleOutlined color="red" style={iconStyle} />
|
||||
</Tooltip>
|
||||
footer={
|
||||
<Text color="red" mt="m">
|
||||
{props.setter} is invalid
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isNil, isString } from '@music163/tango-helpers';
|
||||
export interface FormControlProps extends Omit<FormLabelProps, 'type'> {
|
||||
visible: boolean;
|
||||
extra?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
error?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
@@ -18,6 +19,7 @@ export function FormControl({
|
||||
tip,
|
||||
docs,
|
||||
extra,
|
||||
footer,
|
||||
error,
|
||||
children,
|
||||
...rest
|
||||
@@ -29,6 +31,7 @@ export function FormControl({
|
||||
{extra}
|
||||
</Box>
|
||||
<Box>{children}</Box>
|
||||
{footer}
|
||||
{!!error && (
|
||||
<Box mt="m" color="red">
|
||||
{error}
|
||||
|
||||
@@ -83,7 +83,15 @@ export interface SettingFormProps {
|
||||
*/
|
||||
prototype?: ComponentPrototypeType;
|
||||
/**
|
||||
* 选项分组信息
|
||||
* 是否显示搜索框
|
||||
*/
|
||||
showSearch?: boolean;
|
||||
/**
|
||||
* 是否显示分组导航
|
||||
*/
|
||||
showGroups?: boolean;
|
||||
/**
|
||||
* 选项分组信息,用于对表单项进行分组展示,如未提供,则使用内置的分组信息
|
||||
*/
|
||||
groupOptions?: IFormTabsGroupOption[];
|
||||
/**
|
||||
@@ -98,6 +106,8 @@ export function SettingForm({
|
||||
model: modelProp,
|
||||
defaultValue,
|
||||
onChange = noop,
|
||||
showSearch = true,
|
||||
showGroups = true,
|
||||
groupOptions = internalGroups,
|
||||
}: SettingFormProps) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
@@ -107,10 +117,8 @@ export function SettingForm({
|
||||
const componentPropGroups = useMemo(() => {
|
||||
return normalizeComponentProps(componentProps);
|
||||
}, [componentProps]);
|
||||
|
||||
const filterProps = useMemo(() => {
|
||||
if (!keyword) {
|
||||
return componentPropGroups[tabKey] || [];
|
||||
}
|
||||
const computedProps = mapTreeData(componentProps, (node) => {
|
||||
const { getProp, ...rest } = node;
|
||||
return {
|
||||
@@ -118,8 +126,19 @@ export function SettingForm({
|
||||
...getProp?.(model),
|
||||
};
|
||||
});
|
||||
|
||||
// 没有进行搜索的情况
|
||||
if (!keyword) {
|
||||
if (showGroups) {
|
||||
return componentPropGroups[tabKey] || [];
|
||||
} else {
|
||||
return computedProps;
|
||||
}
|
||||
}
|
||||
|
||||
// 用户进行搜索的情况
|
||||
return filterComponentProps(computedProps, keyword);
|
||||
}, [keyword, componentProps, tabKey, model]);
|
||||
}, [keyword, componentProps, tabKey, model, showGroups, componentPropGroups]);
|
||||
|
||||
const renderProps = useCallback(
|
||||
(props: ComponentPropType[]) =>
|
||||
@@ -155,15 +174,17 @@ export function SettingForm({
|
||||
<FormModelProvider value={model}>
|
||||
<Box className="SettingForm" mb="xl" css={formStyle}>
|
||||
<Box className="SettingFormHeader" position="sticky" top="0" bg="white" zIndex="2">
|
||||
<Box className="SettingFormSearch" px="l" py="m">
|
||||
<Search
|
||||
placeholder="搜索属性"
|
||||
onChange={(val) => {
|
||||
setKeyword(val?.trim());
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!keyword && (
|
||||
{showSearch && (
|
||||
<Box className="SettingFormSearch" px="l" py="m">
|
||||
<Search
|
||||
placeholder="搜索属性"
|
||||
onChange={(val) => {
|
||||
setKeyword(val?.trim());
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{!keyword && showGroups && (
|
||||
<Box className="SettingFormNav">
|
||||
<Tabs activeKey={tabKey} onChange={setTabKey}>
|
||||
{groupOptions.map((group) => (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { register } from './form-item';
|
||||
import { ExpressionSetter } from './setter';
|
||||
import { ExpressionSetter, TextSetter } from './setter';
|
||||
|
||||
// 预注册基础 Setter
|
||||
register({
|
||||
@@ -7,6 +7,11 @@ register({
|
||||
component: ExpressionSetter,
|
||||
});
|
||||
|
||||
register({
|
||||
name: 'textSetter',
|
||||
component: TextSetter,
|
||||
});
|
||||
|
||||
export * from './form';
|
||||
export * from './form-item';
|
||||
export * from './form-object';
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Input } from 'antd';
|
||||
import { InputCode } from '@music163/tango-ui';
|
||||
import { FormItemComponentProps } from './form-item';
|
||||
|
||||
// FIXME: 内置 ExpressionSetter,默认全部 fallback 到此 setter
|
||||
export function ExpressionSetter(props: FormItemComponentProps<string>) {
|
||||
return <div>exp setter</div>;
|
||||
export function ExpressionSetter({ value, onChange, ...rest }: FormItemComponentProps<string>) {
|
||||
return <InputCode onChange={(val) => onChange?.(val)} value={value || ''} {...rest} />;
|
||||
}
|
||||
|
||||
export function TextSetter({ onChange, ...rest }: FormItemComponentProps<string>) {
|
||||
return <Input onChange={(e) => onChange?.(e.target.value)} {...rest} />;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.12](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.11...@music163/tango-ui@0.1.12) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update theme ([2550411](https://github.com/netease/tango/commit/2550411c1c93037931d44aa9f7822ffe8caa900b))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-ui",
|
||||
"version": "0.1.9",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "ui widgets of tango",
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -34,14 +34,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@codemirror/autocomplete": "^6.10.2",
|
||||
"@codemirror/autocomplete": "^6.11.1",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lint": "^6.4.2",
|
||||
"@codemirror/search": "^6.5.4",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@uiw/react-codemirror": "^4.21.20",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@music163/tango-helpers": "^0.1.8",
|
||||
"@uiw/react-codemirror": "^4.21.21",
|
||||
"antd": "^4.24.2",
|
||||
"antd-easy-form": "^1.1.3",
|
||||
"coral-system": "^1.0.5",
|
||||
"eslint-linter-browserify": "^8.51.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const CodeOutlinedSvg = () => (
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="m31 16-7 7-1.41-1.41 5.58-5.59-5.58-5.59 1.41-1.41z" />
|
||||
<path d="m1 16 7-7 1.41 1.41-5.58 5.59 5.58 5.59-1.41 1.41z" />
|
||||
<path
|
||||
d="m5.91 15h20.17v2h-20.17z"
|
||||
transform="matrix(.25881905 -.96592583 .96592583 .25881905 -3.6 27.31)"
|
||||
/>
|
||||
<path d="m0 0h32v32h-32z" fill="none" transform="matrix(0 -1 1 0 0 32)" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CodeOutlined = createIcon(CodeOutlinedSvg, 'CodeOutlined');
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import Icon from '@ant-design/icons';
|
||||
import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
|
||||
|
||||
export function createIcon(component: React.ComponentType<any>, displayName?: string) {
|
||||
const CustomIcon = (props: Partial<CustomIconComponentProps>) => (
|
||||
<Icon component={component} {...props} />
|
||||
);
|
||||
if (displayName) {
|
||||
CustomIcon.displayName = displayName;
|
||||
}
|
||||
return CustomIcon;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from './create-icon';
|
||||
export * from './code-outlined';
|
||||
export * from './open-panel-filled-left-outlined';
|
||||
export * from './open-panel-filled-right-outlined';
|
||||
export * from './open-panel-left-outlined';
|
||||
export * from './open-panel-right-outlined';
|
||||
export * from './undo-outlined';
|
||||
export * from './redo-outlined';
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelFilledLeftOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm0 22h-16v-20h16z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelFilledLeftOutlined = createIcon(
|
||||
OpenPanelFilledLeftOutlinedSvg,
|
||||
'OpenPanelFilledLeftOutlined',
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelFilledRightOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm-24 2h16v20h-16z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelFilledRightOutlined = createIcon(
|
||||
OpenPanelFilledRightOutlinedSvg,
|
||||
'OpenPanelFilledRightOutlined',
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelLeftOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm-24 2h6v20h-6zm24 20h-16v-20h16z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelLeftOutlined = createIcon(OpenPanelLeftOutlinedSvg, 'OpenPanelLeftOutlined');
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelRightOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm-24 2h16v20h-16zm24 20h-6v-20h6z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelRightOutlined = createIcon(
|
||||
OpenPanelRightOutlinedSvg,
|
||||
'OpenPanelRightOutlined',
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const RedoOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m12 10h12.1851l-3.5874-3.5859 1.4023-1.4141 6 6-6 6-1.4023-1.4146 3.5844-3.5854h-12.1821a6 6 0 0 0 0 12h8v2h-8a8 8 0 0 1 0-16z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const RedoOutlined = createIcon(RedoOutlinedSvg, 'RedoOutlined');
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const UndoOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m20 10h-12.1851l3.5874-3.5859-1.4023-1.4141-6 6 6 6 1.4023-1.4146-3.5844-3.5854h12.1821a6 6 0 0 1 0 12h-8v2h8a8 8 0 0 0 0-16z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const UndoOutlined = createIcon(UndoOutlinedSvg, 'UndoOutlined');
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './icons';
|
||||
export * from './action-select';
|
||||
export * from './code-editor';
|
||||
export * from './chat-input';
|
||||
@@ -19,4 +20,3 @@ export * from './tabs';
|
||||
export * from './select-action';
|
||||
export * from './copy-clipboard';
|
||||
export * from './tag-select';
|
||||
export { Form } from 'antd-easy-form';
|
||||
@@ -153,12 +153,12 @@ export function InputCode({
|
||||
autocomplete: buildAutoComplete(autoCompleteContext, options),
|
||||
});
|
||||
}, [autoCompleteContext, autoCompleteOptions]);
|
||||
const { rootStyle, codeSetup } = useInputCode({ shape, status, showLineNumbers, showFoldGutter });
|
||||
const extensions = [javascript({ jsx: true }), globalJavaScriptCompletions];
|
||||
if (enableESLint) {
|
||||
// @ts-ignore
|
||||
extensions.push(lintGutter(), linter(esLint(new eslint.Linter(), eslintConfig)));
|
||||
}
|
||||
const { rootStyle, codeSetup } = useInputCode({ shape, status, showLineNumbers, showFoldGutter });
|
||||
|
||||
return (
|
||||
<Box className="InputCode" display="flex" alignItems="center" overflow="hidden" {...rootStyle}>
|
||||
@@ -194,6 +194,7 @@ function useInputCode({
|
||||
codeSetup: {
|
||||
lineNumbers: showLineNumbers ?? lineNumbers,
|
||||
foldGutter: showFoldGutter ?? foldGutter,
|
||||
searchKeymap: false, // 默认关闭搜索快捷键,原因:https://github.com/uiwjs/react-codemirror/issues/280
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import { css, Button, HTMLCoralProps } from 'coral-system';
|
||||
import { css, Button, HTMLCoralProps, Box } from 'coral-system';
|
||||
import { DropDownProps, Dropdown, MenuProps, Tooltip, TooltipProps } from 'antd';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
|
||||
@@ -26,19 +26,11 @@ const buttonStyle = css`
|
||||
&.size-m {
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
|
||||
.anticon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.size-l {
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
|
||||
.anticon {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&.size-xl {
|
||||
@@ -106,10 +98,12 @@ const buttonStyle = css`
|
||||
|
||||
&.disabled {
|
||||
color: var(--tango-colors-custom-toolbarButtonTextColorDisabled, rgb(201, 205, 212));
|
||||
background-color: var(--tango-colors-custom-toolbarButtonBgDisabled, rgb(247, 248, 250));
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--tango-colors-brand);
|
||||
color: var(--tango-colors-custom-toolbarButtonTextColorActive);
|
||||
background-color: var(--tango-colors-custom-toolbarButtonBgActive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +118,7 @@ export interface ToggleButtonProps extends Omit<HTMLCoralProps<'button'>, 'type'
|
||||
type?: 'normal' | 'primary';
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
/**
|
||||
* 提示文案
|
||||
* @deprecated 使用 tooltip 代替
|
||||
@@ -167,6 +162,7 @@ export function ToggleButton(props: ToggleButtonProps) {
|
||||
disabled,
|
||||
selected,
|
||||
children,
|
||||
icon,
|
||||
tip,
|
||||
tooltip = tip,
|
||||
tooltipPlacement = 'bottom',
|
||||
@@ -189,7 +185,14 @@ export function ToggleButton(props: ToggleButtonProps) {
|
||||
|
||||
let btn = (
|
||||
<Button css={buttonStyle} className={clazz} type="button" {...rest}>
|
||||
{children}
|
||||
<Box as="span">
|
||||
{icon && (
|
||||
<Box as="span" className="buttonIcon" mr="s">
|
||||
{icon}
|
||||
</Box>
|
||||
)}
|
||||
{children}
|
||||
</Box>
|
||||
{showDropdown ? <DownOutlined /> : null}
|
||||
</Button>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user