mirror of
https://github.com/NetEase/tango.git
synced 2026-08-31 00:59:32 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c240c1824b | |||
| d63517ecb9 | |||
| 1a17ee1e94 | |||
| e1bab66819 | |||
| 89bcd45728 | |||
| e86a6d7cd9 | |||
| 46b54459ce | |||
| 85898dee19 | |||
| e429610606 | |||
| b6814d6484 | |||
| 3d7b58f19a | |||
| b6a47acdde | |||
| 226d71c704 | |||
| 1c72ee6ac2 | |||
| 09a1e2135a |
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn commitlint --edit "$1"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged --allow-empty
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
],
|
||||
devServer: {
|
||||
host: 'local.netease.com',
|
||||
port: 7007,
|
||||
port: 8008,
|
||||
https: {
|
||||
key: path.resolve(__dirname, 'local.netease.com-key.pem'),
|
||||
cert: path.resolve(__dirname, 'local.netease.com.pem'),
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "COMPRESS=none umi build",
|
||||
"start": "HTTPS=1 umi dev"
|
||||
"start": "umi dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Row, Switch } from 'antd';
|
||||
import { observer } from '@music163/tango-designer';
|
||||
import { observer } from '@music163/tango-context';
|
||||
|
||||
const OtherPanel = observer(({ autoRemove, setAutoRemove }: any) => {
|
||||
const onChange = (checked: boolean) => {
|
||||
|
||||
@@ -44,7 +44,7 @@ const tangoConfigJson = {
|
||||
resources: ['https://unpkg.com/moment@{{version}}/moment.js'],
|
||||
},
|
||||
'@music163/tango-boot': {
|
||||
version: '0.2.0',
|
||||
version: '0.2.1',
|
||||
library: 'TangoBoot',
|
||||
type: 'baseDependency',
|
||||
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'],
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
@import '~antd/dist/antd.less'; // 引入官方提供的 less 样式入口文件
|
||||
|
||||
@primary-color: #2f54eb;
|
||||
@border-radius-base: 2px;
|
||||
@border-radius-base: 2px;
|
||||
@@ -9,15 +9,21 @@ import {
|
||||
Toolbar,
|
||||
WorkspacePanel,
|
||||
WorkspaceView,
|
||||
ComponentsPanel,
|
||||
CodeEditor,
|
||||
Sandbox,
|
||||
DndQuery,
|
||||
} from '@music163/tango-designer';
|
||||
import { createEngine, Workspace } from '@music163/tango-core';
|
||||
import { Logo, ProjectDetail } from './share';
|
||||
import { Logo, ProjectDetail, bootHelperVariables } from './share';
|
||||
import { sampleFiles } from '../mock/project';
|
||||
import './index.less';
|
||||
import {
|
||||
ApiOutlined,
|
||||
AppstoreAddOutlined,
|
||||
BuildOutlined,
|
||||
ClusterOutlined,
|
||||
FunctionOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
// 1. 实例化工作区
|
||||
const workspace = new Workspace({
|
||||
@@ -44,7 +50,14 @@ export default function App() {
|
||||
const [menuLoading, setMenuLoading] = useState(true);
|
||||
const [menuData, setMenuData] = useState(false);
|
||||
return (
|
||||
<Designer engine={engine} sandboxQuery={sandboxQuery}>
|
||||
<Designer
|
||||
engine={engine}
|
||||
config={{
|
||||
customActionVariables: bootHelperVariables,
|
||||
customExpressionVariables: bootHelperVariables,
|
||||
}}
|
||||
sandboxQuery={sandboxQuery}
|
||||
>
|
||||
<DesignerPanel
|
||||
logo={<Logo />}
|
||||
description={<ProjectDetail />}
|
||||
@@ -52,10 +65,11 @@ export default function App() {
|
||||
<Box px="l">
|
||||
<Toolbar>
|
||||
<Toolbar.Item key="routeSwitch" placement="left" />
|
||||
<Toolbar.Item key="preview" placement="left" />
|
||||
<Toolbar.Item key="modeSwitch" placement="right" />
|
||||
<Toolbar.Item key="togglePanel" placement="right" />
|
||||
<Toolbar.Separator />
|
||||
<Toolbar.Item key="extra" placement="right">
|
||||
<Toolbar.Item placement="right">
|
||||
<Space>
|
||||
<Button type="primary">发布</Button>
|
||||
</Space>
|
||||
@@ -65,13 +79,31 @@ export default function App() {
|
||||
}
|
||||
>
|
||||
<Sidebar>
|
||||
<Sidebar.Item key="outline" />
|
||||
<Sidebar.Item key="components">
|
||||
<ComponentsPanel menuData={menuData as any} loading={menuLoading} />
|
||||
</Sidebar.Item>
|
||||
<Sidebar.Item key="variables" isFloat width={800} />
|
||||
<Sidebar.Item key="dataSource" isFloat width={800} />
|
||||
<Sidebar.Item key="dependency" isFloat width={800} />
|
||||
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
|
||||
<Sidebar.Item
|
||||
key="components"
|
||||
label="组件"
|
||||
icon={<AppstoreAddOutlined />}
|
||||
widgetProps={{
|
||||
menuData: menuData as any,
|
||||
loading: menuLoading,
|
||||
}}
|
||||
/>
|
||||
<Sidebar.Item
|
||||
key="variables"
|
||||
label="变量"
|
||||
icon={<FunctionOutlined />}
|
||||
isFloat
|
||||
width={800}
|
||||
/>
|
||||
<Sidebar.Item key="dataSource" label="接口" icon={<ApiOutlined />} isFloat width={800} />
|
||||
<Sidebar.Item
|
||||
key="dependency"
|
||||
label="依赖"
|
||||
icon={<ClusterOutlined />}
|
||||
isFloat
|
||||
width={800}
|
||||
/>
|
||||
</Sidebar>
|
||||
<WorkspacePanel>
|
||||
<WorkspaceView mode="design">
|
||||
|
||||
@@ -6,6 +6,36 @@ import { registerSetter } from '@music163/tango-designer';
|
||||
import type { ComponentPrototypeType } from '@music163/tango-helpers';
|
||||
import { FooSetter } from '../components';
|
||||
|
||||
export const bootHelperVariables = [
|
||||
{
|
||||
key: '$helpers',
|
||||
title: '工具函数',
|
||||
children: [
|
||||
{
|
||||
title: 'setStoreValue',
|
||||
key: '() => tango.setStoreValue("variableName", "variableValue")',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
title: 'getStoreValue',
|
||||
key: '() => tango.getStoreValue("variableName")',
|
||||
type: 'function',
|
||||
},
|
||||
{ title: 'openModal', key: '() => tango.openModal("")', type: 'function' },
|
||||
{ title: 'closeModal', key: '() => tango.closeModal("")', type: 'function' },
|
||||
{ title: 'navigateTo', key: '() => tango.navigateTo("/")', type: 'function' },
|
||||
{ title: 'showToast', key: '() => tango.showToast("hello")', type: 'function' },
|
||||
{ title: 'formatDate', key: '() => tango.formatDate("2022-12-12")', type: 'function' },
|
||||
{ title: 'formatNumber', key: '() => tango.formatDate(9999)', type: 'function' },
|
||||
{
|
||||
title: 'copyToClipboard',
|
||||
key: '() => tango.copyToClipboard("hello")',
|
||||
type: 'function',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// folder-name
|
||||
// 物料列表定义
|
||||
const bizToggleButtonPrototype: ComponentPrototypeType = {
|
||||
@@ -133,7 +163,7 @@ export function ProjectDetail() {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" columnGap="l">
|
||||
<Box className="ProjectName" fontSize="18px" fontWeight="bold">
|
||||
lc-online-test
|
||||
community-test
|
||||
</Box>
|
||||
<Box className="BranchName" as="code" fontSize="14px">
|
||||
<BranchesOutlined /> feature/list
|
||||
|
||||
@@ -10,11 +10,20 @@
|
||||
|
||||
项目使用的 npm 包依赖信息,用于定义依赖的类型以及外置依赖资源。该配置项为一个 map,key 为对应的 npm 包名,value 为该依赖的配置信息。
|
||||
|
||||
- version 包的版本号,必选
|
||||
- library UMD 包中的全局变量名,必选
|
||||
- type 依赖的类型
|
||||
- baseDependency 基础依赖包
|
||||
- bizDependency 业务组件依赖包
|
||||
- dependency 其他依赖包
|
||||
- resources 外置依赖资源列表
|
||||
- designerResources 在设计器视图载入的外置资源列表,用来需要区别加载时使用
|
||||
- description 描述信息
|
||||
|
||||
依赖的类型 `type` 可以是 `baseDependency` 即项目的基础依赖,用于获取项目基础物料列表的信息;也可以是 `bizDependency` 即业务组件依赖,用于获取合并到物料面板业务组件菜单中的业务组件列表;也可以是 `depenency` 即项目的其他依赖,只用于提供 external 信息与版本信息等,不会和物料面板产生关系。
|
||||
|
||||
当配置了外置依赖资源 `resources` 与其对应的全局变量 `library` 时,当这些依赖被 import 时,相关资源不会被打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖,其等效于之前的 `sandbox.externalResources` 与 `sandbox.externals` 配置项。此外,可以将 `resource` 中的版本号替换为 `{{version}}`,其会在平台上构建时替换为版本号。特殊的,可以定义 `designerResources` 用于适配平台的设计器视图。
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"react": {
|
||||
|
||||
@@ -6,7 +6,7 @@ Tango 是一个用于快速构建低代码平台的低代码设计器框架,
|
||||
|
||||
## 引擎架构概览
|
||||
|
||||
Tango 低代码引擎在实现上进行了分层解藕,使得上层的低代码平台与底层的低代码引擎可以独立开发和维护,快速集成部署。另一方面,Tango 低代码引擎定义了一套开放的物料生态体系,开发者可以自由的贡献扩展组件配置能力的属性设置器,以及扩展低代码物料的二方三方业务组件。
|
||||
Tango 低代码引擎在实现上进行了分层解耦,使得上层的低代码平台与底层的低代码引擎可以独立开发和维护,快速集成部署。另一方面,Tango 低代码引擎定义了一套开放的物料生态体系,开发者可以自由的贡献扩展组件配置能力的属性设置器,以及扩展低代码物料的二方三方业务组件。
|
||||
|
||||
如下图所示:
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'scope-case': [0],
|
||||
'subject-case': [0],
|
||||
},
|
||||
};
|
||||
+10
-3
@@ -21,7 +21,8 @@
|
||||
"ver": "lerna version --no-private --conventional-commits",
|
||||
"release": "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"
|
||||
"up": "yarn upgrade-interactive --latest",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.19.3",
|
||||
@@ -49,11 +50,11 @@
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"http-server": "^14.1.0",
|
||||
"husky": "^8.0.1",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.1.1",
|
||||
"jest-environment-jsdom": "^29.1.1",
|
||||
"lerna": "^6.6.1",
|
||||
"lint-staged": "^13.1.0",
|
||||
"lint-staged": "^14.0.1",
|
||||
"prettier": "^2.8.7",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
@@ -63,5 +64,11 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"@yarnpkg/parsers": "3.0.0-rc.48.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.2](https://github.com/netease/tango/compare/@music163/tango-context@0.2.1...@music163/tango-context@0.2.2) (2023-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- refactor expSetter ([#41](https://github.com/netease/tango/issues/41)) ([d63517e](https://github.com/netease/tango/commit/d63517ecb936e4227e70c33e610664316625f4f4))
|
||||
|
||||
## [0.2.1](https://github.com/netease/tango/compare/@music163/tango-context@0.2.0...@music163/tango-context@0.2.1) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-context
|
||||
|
||||
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-context@0.1.4...@music163/tango-context@0.2.0) (2023-09-21)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-context",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.2",
|
||||
"description": "react context for tango-apps",
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -31,7 +31,7 @@
|
||||
"react": ">= 16.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@music163/tango-core": "^0.2.0",
|
||||
"@music163/tango-core": "^0.2.1",
|
||||
"@music163/tango-helpers": "^0.1.3",
|
||||
"mobx-react-lite": "4.0.2"
|
||||
},
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import type { Engine } from '@music163/tango-core';
|
||||
import { createContext } from '@music163/tango-helpers';
|
||||
|
||||
interface CustomVariableData {
|
||||
key: string;
|
||||
title: string;
|
||||
children?: CustomVariableData[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ITangoEngineContext {
|
||||
/**
|
||||
* 低代码引擎
|
||||
*/
|
||||
engine: Engine;
|
||||
/**
|
||||
* 自定义配置数据
|
||||
*/
|
||||
config: {
|
||||
customActionVariables: CustomVariableData[];
|
||||
customExpressionVariables: CustomVariableData[];
|
||||
};
|
||||
}
|
||||
|
||||
const [TangoEngineProvider, useTangoEngine] = createContext<ITangoEngineContext>({
|
||||
@@ -22,31 +36,8 @@ export const useDesigner = () => {
|
||||
return useTangoEngine()?.engine.designer;
|
||||
};
|
||||
|
||||
const builtinHelpers = [
|
||||
{
|
||||
title: 'setStoreValue',
|
||||
key: '() => tango.setStoreValue("variableName", "variableValue")',
|
||||
type: 'function',
|
||||
},
|
||||
{
|
||||
title: 'getStoreValue',
|
||||
key: '() => tango.getStoreValue("variableName")',
|
||||
type: 'function',
|
||||
},
|
||||
{ title: 'openModal', key: '() => tango.openModal("")', type: 'function' },
|
||||
{ title: 'closeModal', key: '() => tango.closeModal("")', type: 'function' },
|
||||
{ title: 'navigateTo', key: '() => tango.navigateTo("/")', type: 'function' },
|
||||
{ title: 'showToast', key: '() => tango.showToast("hello")', type: 'function' },
|
||||
{ title: 'formatDate', key: '() => tango.formatDate("2022-12-12")', type: 'function' },
|
||||
{ title: 'formatNumber', key: '() => tango.formatDate(9999)', type: 'function' },
|
||||
{
|
||||
title: 'copyToClipboard',
|
||||
key: '() => tango.copyToClipboard("hello")',
|
||||
type: 'function',
|
||||
},
|
||||
];
|
||||
|
||||
export const useWorkspaceData = () => {
|
||||
const ctx = useTangoEngine();
|
||||
const workspace = useWorkspace();
|
||||
const modelVariables: any[] = []; // 绑定变量列表
|
||||
const storeActionVariables: any[] = []; // 模型中的所有 actions
|
||||
@@ -111,20 +102,29 @@ export const useWorkspaceData = () => {
|
||||
value: item.path,
|
||||
}));
|
||||
|
||||
let actionVariables: CustomVariableData[] = [
|
||||
buildVariableOptions('数据模型', '$stores', storeActionVariables),
|
||||
buildVariableOptions('服务函数', '$services', serviceVariables),
|
||||
];
|
||||
|
||||
if (ctx.config?.customActionVariables) {
|
||||
actionVariables = actionVariables.concat(ctx.config?.customActionVariables);
|
||||
}
|
||||
|
||||
let expressionVariables: CustomVariableData[] = [
|
||||
buildVariableOptions('数据模型', '$stores', storeVariables),
|
||||
buildVariableOptions('服务函数', '$services', serviceVariables),
|
||||
];
|
||||
if (ctx.config?.customExpressionVariables) {
|
||||
expressionVariables = expressionVariables.concat(ctx.config?.customExpressionVariables);
|
||||
}
|
||||
|
||||
return {
|
||||
modelVariables: [buildVariableOptions('数据模型', 'stores', modelVariables)],
|
||||
actionVariables: [
|
||||
buildVariableOptions('数据模型', 'stores', storeActionVariables),
|
||||
buildVariableOptions('服务函数', 'services', serviceVariables),
|
||||
buildVariableOptions('工具函数', 'helpers', builtinHelpers),
|
||||
],
|
||||
actionVariables,
|
||||
storeVariables,
|
||||
serviceVariables,
|
||||
expressionVariables: [
|
||||
buildVariableOptions('数据模型', 'stores', storeVariables),
|
||||
buildVariableOptions('服务函数', 'services', serviceVariables),
|
||||
buildVariableOptions('工具函数', 'helpers', builtinHelpers),
|
||||
],
|
||||
expressionVariables,
|
||||
routeOptions,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.1](https://github.com/netease/tango/compare/@music163/tango-core@0.2.0...@music163/tango-core@0.2.1) (2023-09-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add listServiceFunctions ([09a1e21](https://github.com/netease/tango/commit/09a1e2135a1f51b0a5f6c0a507ba42d2e6355c24))
|
||||
|
||||
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-core@0.1.4...@music163/tango-core@0.2.0) (2023-09-21)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-core",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "tango core",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
|
||||
@@ -192,6 +192,7 @@ export interface IWorkspace {
|
||||
) => void;
|
||||
|
||||
getServiceFunction?: (serviceKey: string) => object;
|
||||
listServiceFunctions?: () => Record<string, object>;
|
||||
removeServiceFunction?: (serviceName: string, modName?: string) => void;
|
||||
addServiceFunction?: (
|
||||
payload: IServiceFunctionPayload | IServiceFunctionPayload[],
|
||||
|
||||
@@ -16,12 +16,19 @@ import { TangoModule } from './module';
|
||||
*/
|
||||
export class TangoServiceModule extends TangoModule {
|
||||
/**
|
||||
* 模块名
|
||||
* 服务函数的模块名,默认为 index
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 原始注册的服务函数
|
||||
* @example { get: {}, list: {} }
|
||||
*/
|
||||
_serviceFunctions: Dict;
|
||||
|
||||
/**
|
||||
* 原始的基础配置
|
||||
*/
|
||||
_baseConfig: Dict;
|
||||
|
||||
get serviceFunctions() {
|
||||
|
||||
@@ -647,6 +647,7 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
|
||||
/**
|
||||
* 获取服务函数的详情
|
||||
* TODO: 不要 services 前缀
|
||||
* @param serviceKey `services.list` 或 `services.sub.list`
|
||||
* @returns
|
||||
*/
|
||||
@@ -663,6 +664,23 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务函数的列表
|
||||
*/
|
||||
listServiceFunctions() {
|
||||
const ret: Record<string, object> = {};
|
||||
Object.values(this.serviceModules).forEach((module) => {
|
||||
Object.keys(module.serviceFunctions).forEach((name) => {
|
||||
const serviceKey =
|
||||
module.baseConfig.namespace !== 'index'
|
||||
? name
|
||||
: [module.baseConfig.namespace, name].join('.');
|
||||
ret[serviceKey] = module.serviceFunctions[name];
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新服务函数
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.5.0](https://github.com/netease/tango/compare/@music163/tango-designer@0.4.1...@music163/tango-designer@0.5.0) (2023-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- refactor expSetter ([#41](https://github.com/netease/tango/issues/41)) ([d63517e](https://github.com/netease/tango/commit/d63517ecb936e4227e70c33e610664316625f4f4))
|
||||
|
||||
### Features
|
||||
|
||||
- add umd resources config when modifying dependency ([#36](https://github.com/netease/tango/issues/36)) ([e1bab66](https://github.com/netease/tango/commit/e1bab66819cf9a0bd7b87b72b2962498dfd51b2c))
|
||||
|
||||
## [0.4.1](https://github.com/netease/tango/compare/@music163/tango-designer@0.4.0...@music163/tango-designer@0.4.1) (2023-10-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- always switch to design mode after preview is on ([85898de](https://github.com/netease/tango/commit/85898dee19b6031016679e33a4ce9edbd1089713))
|
||||
- update export entry ([e429610](https://github.com/netease/tango/commit/e4296106066ad290ac838135fe6a57bc50ad2680))
|
||||
|
||||
# [0.4.0](https://github.com/netease/tango/compare/@music163/tango-designer@0.3.0...@music163/tango-designer@0.4.0) (2023-09-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- remove useless api ([226d71c](https://github.com/netease/tango/commit/226d71c704f41a756ee777d6de4627a7eb7b4259))
|
||||
|
||||
### Features
|
||||
|
||||
- add global widget map ([1c72ee6](https://github.com/netease/tango/commit/1c72ee6ac287c1b6fb5eb3806a68b877f3749405))
|
||||
|
||||
# [0.3.0](https://github.com/netease/tango/compare/@music163/tango-designer@0.2.1...@music163/tango-designer@0.3.0) (2023-09-21)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-designer",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.0",
|
||||
"description": "lowcode designer",
|
||||
"keywords": [
|
||||
"react"
|
||||
@@ -33,12 +33,12 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/request": "^0.1.0",
|
||||
"@music163/tango-context": "^0.2.0",
|
||||
"@music163/tango-core": "^0.2.0",
|
||||
"@music163/tango-context": "^0.2.2",
|
||||
"@music163/tango-core": "^0.2.1",
|
||||
"@music163/tango-helpers": "^0.1.3",
|
||||
"@music163/tango-sandbox": "^0.1.5",
|
||||
"@music163/tango-setting-form": "^0.2.2",
|
||||
"@music163/tango-ui": "^0.1.4",
|
||||
"@music163/tango-sandbox": "^0.1.6",
|
||||
"@music163/tango-setting-form": "^0.2.5",
|
||||
"@music163/tango-ui": "^0.1.6",
|
||||
"antd": "^4.24.2",
|
||||
"cash-dom": "^8.1.2",
|
||||
"classnames": "^2.3.2",
|
||||
|
||||
@@ -11,10 +11,15 @@ import {
|
||||
Empty,
|
||||
Popconfirm,
|
||||
Tooltip,
|
||||
Alert,
|
||||
} from 'antd';
|
||||
import { Box, Text, css, Link } from 'coral-system';
|
||||
import { PlusOutlined, FunctionOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { Box, Text, css } from 'coral-system';
|
||||
import {
|
||||
PlusOutlined,
|
||||
FunctionOutlined,
|
||||
DeleteOutlined,
|
||||
CopyOutlined,
|
||||
EyeOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
filterTreeData,
|
||||
isFunction,
|
||||
@@ -96,10 +101,6 @@ export interface EditableVariableTreeProps {
|
||||
* 选择列表项时的回调
|
||||
*/
|
||||
onSelect?: (data: IVariableTreeNode) => void;
|
||||
/**
|
||||
* 点击使用此变量按钮时的回调
|
||||
*/
|
||||
onUse?: (data: IVariableTreeNode) => void;
|
||||
/**
|
||||
* 保存结点定义的回调
|
||||
*/
|
||||
@@ -124,10 +125,6 @@ export interface EditableVariableTreeProps {
|
||||
* 搜索框的后置结点
|
||||
*/
|
||||
searchAddonAfter?: React.ReactNode;
|
||||
/**
|
||||
* 是否展示预览值的提示信息
|
||||
*/
|
||||
showPreviewAlert?: boolean;
|
||||
}
|
||||
|
||||
const previewOptions = [
|
||||
@@ -145,11 +142,9 @@ export function EditableVariableTree({
|
||||
onDeleteStore = noop,
|
||||
getPreviewValue = noop,
|
||||
onSelect = noop,
|
||||
onUse = noop,
|
||||
onSave = noop,
|
||||
modes = ['add', 'preview', 'define'],
|
||||
defaultMode = 'preview',
|
||||
showPreviewAlert = true,
|
||||
}: EditableVariableTreeProps) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [node, setNode] = useState<IVariableTreeNode>();
|
||||
@@ -184,11 +179,16 @@ export function EditableVariableTree({
|
||||
</Box>
|
||||
<VariableTree
|
||||
dataSource={treeData}
|
||||
showViewIcon
|
||||
onSelect={(item) => {
|
||||
setNode(item);
|
||||
mode === 'add' && setMode(defaultMode);
|
||||
onSelect(item);
|
||||
}}
|
||||
onView={(item) => {
|
||||
setNode(item);
|
||||
mode === 'add' && setMode(defaultMode);
|
||||
}}
|
||||
onAdd={(item) => {
|
||||
setNode(item);
|
||||
setMode('add');
|
||||
@@ -225,33 +225,15 @@ export function EditableVariableTree({
|
||||
borderRadius="m"
|
||||
position="sticky"
|
||||
top="0"
|
||||
bodyProps={{ px: 'l' }}
|
||||
bodyProps={{ px: 'm' }}
|
||||
>
|
||||
{mode === 'preview' && (
|
||||
<>
|
||||
<Box mb="m" display={showPreviewAlert ? 'block' : 'none'}>
|
||||
<Alert
|
||||
message={
|
||||
<Space>
|
||||
<Text>
|
||||
<Text as="strong">变量路径:</Text>tango.{node.key}
|
||||
</Text>
|
||||
<Link onClick={() => onUse(node)}>使用此变量</Link>
|
||||
<CopyClipboard text={`tango.${node.key.replaceAll('.', '?.')}`}>
|
||||
{(copied) => <Link>{copied ? '已复制' : '复制变量路径'}</Link>}
|
||||
</CopyClipboard>
|
||||
</Space>
|
||||
}
|
||||
type="info"
|
||||
/>
|
||||
</Box>
|
||||
<ValuePreview
|
||||
value={getPreviewValue(node)}
|
||||
onSelect={(valuePath) => {
|
||||
return ['tango', node.key.replaceAll('.', '?.'), valuePath].join('.');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<ValuePreview
|
||||
value={getPreviewValue(node)}
|
||||
onSelect={(valuePath) => {
|
||||
return ['tango', node.key.replaceAll('.', '?.'), valuePath].join('.');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{mode === 'define' && (
|
||||
<NodeDefineForm
|
||||
@@ -308,7 +290,7 @@ export function EditableVariableTreeModal({
|
||||
{React.cloneElement(trigger, { onClick: on })}
|
||||
<Modal
|
||||
title={title}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={off}
|
||||
okButtonProps={{
|
||||
disabled: !node,
|
||||
@@ -345,6 +327,11 @@ const varTreeStyle = css`
|
||||
.ant-tree-indent-unit {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.anticon-function {
|
||||
margin-left: 4px;
|
||||
color: var(--tango-colors-text2);
|
||||
}
|
||||
`;
|
||||
|
||||
interface VariableTreeProps {
|
||||
@@ -352,7 +339,10 @@ interface VariableTreeProps {
|
||||
onSelect?: (data: IVariableTreeNode) => void;
|
||||
onAdd?: (data: IVariableTreeNode) => void;
|
||||
onRemove?: (data: IVariableTreeNode) => void;
|
||||
deletable?: boolean;
|
||||
onCopy?: (data: IVariableTreeNode) => void;
|
||||
onView?: (data: IVariableTreeNode) => void;
|
||||
showDeleteIcon?: boolean;
|
||||
showViewIcon?: boolean;
|
||||
}
|
||||
|
||||
export function VariableTree({
|
||||
@@ -360,11 +350,15 @@ export function VariableTree({
|
||||
onSelect = noop,
|
||||
onAdd = noop,
|
||||
onRemove = noop,
|
||||
deletable = false,
|
||||
onCopy = noop,
|
||||
onView = noop,
|
||||
showDeleteIcon = false,
|
||||
showViewIcon = false,
|
||||
}: VariableTreeProps) {
|
||||
return (
|
||||
<Box className="VariableTree" css={varTreeStyle}>
|
||||
<Tree.DirectoryTree
|
||||
<Tree
|
||||
blockNode
|
||||
showIcon={false}
|
||||
defaultExpandAll
|
||||
treeData={dataSource}
|
||||
@@ -372,28 +366,18 @@ export function VariableTree({
|
||||
onSelect(detail.node);
|
||||
}}
|
||||
titleRender={(node) => {
|
||||
// 只有叶子结点支持删除
|
||||
const showRemoveIcon = (!node.children && node.showDeleteIcon) ?? deletable;
|
||||
const showAddIcon = node.showAddChildIcon ?? false;
|
||||
if (showRemoveIcon || showAddIcon) {
|
||||
const isLeaf = !node.children;
|
||||
|
||||
if (isLeaf) {
|
||||
const isDeletable = node.showDeleteIcon ?? showDeleteIcon;
|
||||
return (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" pr="l">
|
||||
<Text>{node.title}</Text>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Text>
|
||||
{node.title}
|
||||
{node.type === 'function' && <FunctionOutlined />}
|
||||
</Text>
|
||||
<Box>
|
||||
{showAddIcon && (
|
||||
<Tooltip title={`向 ${node.title} 中添加变量`}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onAdd(node);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showRemoveIcon && (
|
||||
{isDeletable && (
|
||||
<Popconfirm
|
||||
title="确认删除吗?该操作会导致引用此模型的代码报错,请谨慎操作!"
|
||||
onConfirm={() => onRemove(node)}
|
||||
@@ -408,14 +392,61 @@ export function VariableTree({
|
||||
/>
|
||||
</Popconfirm>
|
||||
)}
|
||||
<CopyClipboard text={`tango.${node.key.replaceAll('.', '?.')}`}>
|
||||
{({ copied, onClick }) => {
|
||||
const label = copied ? '已复制' : '复制变量路径';
|
||||
return (
|
||||
<Tooltip title={label}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
onCopy(node);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
</CopyClipboard>
|
||||
{showViewIcon && (
|
||||
<Tooltip title="查看变量详情">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onView(node);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<Text mr="m">{node.title}</Text>
|
||||
{node.type === 'function' && <FunctionOutlined />}
|
||||
<Box>
|
||||
{node.showAddChildIcon && (
|
||||
<Tooltip title={`向 ${node.title} 中添加变量`}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onAdd(node);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface DesignerProps extends IDesignerContext, ITangoEngineContext {
|
||||
export function Designer(props: DesignerProps) {
|
||||
const {
|
||||
engine,
|
||||
config,
|
||||
theme: themeProp,
|
||||
sandboxQuery,
|
||||
remoteServices = {},
|
||||
@@ -42,7 +43,7 @@ export function Designer(props: DesignerProps) {
|
||||
return (
|
||||
<SystemProvider theme={theme} prefix="--tango">
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<TangoEngineProvider value={{ engine }}>
|
||||
<TangoEngineProvider value={{ engine, config }}>
|
||||
<DesignerProvider value={{ sandboxQuery, remoteServices }}>{children}</DesignerProvider>
|
||||
</TangoEngineProvider>
|
||||
</ConfigProvider>
|
||||
|
||||
@@ -11,6 +11,7 @@ export * from './sandbox';
|
||||
export * from './sidebar';
|
||||
export * from './toolbar';
|
||||
export * from './selection-menu';
|
||||
export * from './widgets';
|
||||
|
||||
export { register as registerSetter } from '@music163/tango-setting-form';
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ interface ISandboxEventHandlerConfig {
|
||||
export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'onMessage'> & {
|
||||
isPreview?: boolean;
|
||||
selectionTools?: SelectionToolsProps['actions'];
|
||||
builtinSelectionMenuMap?: SelectionToolsProps['builtinActionMap'];
|
||||
/**
|
||||
* tangoConfigJson 处理器
|
||||
*/
|
||||
@@ -32,10 +31,10 @@ export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'o
|
||||
onLoad?: (config?: ISandboxEventHandlerConfig) => void;
|
||||
};
|
||||
|
||||
export type CombinedSandboxRef = {
|
||||
export interface CombinedSandboxRef {
|
||||
designSandbox: CodeSandbox;
|
||||
previewSandbox: CodeSandbox;
|
||||
};
|
||||
}
|
||||
|
||||
const LANDING_PAGE_PATH = '/__background_landing_page__';
|
||||
|
||||
@@ -281,7 +280,6 @@ export const Sandbox = observer(
|
||||
({
|
||||
isPreview: isPreviewProp,
|
||||
selectionTools,
|
||||
builtinSelectionMenuMap,
|
||||
bundlerURL,
|
||||
mode = 'combined',
|
||||
...props
|
||||
@@ -352,7 +350,7 @@ export const Sandbox = observer(
|
||||
}}
|
||||
/>
|
||||
<Simulator>
|
||||
<Viewport selectionTools={selectionTools} builtinActionMap={builtinSelectionMenuMap}>
|
||||
<Viewport selectionTools={selectionTools}>
|
||||
{mode === 'single' && (
|
||||
<DesignSandbox
|
||||
ref={sandboxRef}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, css } from 'coral-system';
|
||||
import { Alert, Modal, Tabs } from 'antd';
|
||||
import { Modal } from 'antd';
|
||||
import { value2code, isValidExpressionCode } from '@music163/tango-core';
|
||||
import {
|
||||
isVariableString,
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
useBoolean,
|
||||
getValue,
|
||||
} from '@music163/tango-helpers';
|
||||
import { CloseCircleFilled, ExpandAltOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { IconButton, Panel, InputCode, ChatInput } from '@music163/tango-ui';
|
||||
import { CloseCircleFilled, ExpandAltOutlined } from '@ant-design/icons';
|
||||
import { IconButton, Panel, InputCode } from '@music163/tango-ui';
|
||||
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||
import { EditableVariableTree, IVariableTreeNode } from '../components';
|
||||
import { useRemoteServices, useSandboxQuery } from '../context';
|
||||
import { useSandboxQuery } from '../context';
|
||||
|
||||
export const expressionValueValidate = (value: string) => {
|
||||
if (isVariableString(value)) {
|
||||
@@ -190,38 +190,24 @@ export function ExpressionModal({
|
||||
[workspace],
|
||||
);
|
||||
const sandbox = useSandboxQuery();
|
||||
const remoteServices = useRemoteServices();
|
||||
const { expressionVariables } = useWorkspaceData();
|
||||
const evaluateContext = sandbox.window;
|
||||
const gptService = remoteServices?.GptService;
|
||||
|
||||
const handleExpInputChange = (val: string) => {
|
||||
setExp(val?.trim());
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
title={title}
|
||||
closable={false}
|
||||
destroyOnClose
|
||||
width="70%"
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={() => {
|
||||
onOk(exp);
|
||||
}}
|
||||
destroyOnClose
|
||||
>
|
||||
<Panel
|
||||
title="绑定的表达式"
|
||||
subTitle={subTitle}
|
||||
extra={
|
||||
<IconButton
|
||||
icon={<QuestionCircleOutlined />}
|
||||
tooltip="你可以直接编辑下面的表达式,表达式需要使用 { } 进行包裹,点击此图标可以查看详细文档。"
|
||||
href="https://music-doc.st.netease.com/st/tango-docs/docs/guide/javascript/overview"
|
||||
/>
|
||||
}
|
||||
shape="solid"
|
||||
borderRadius="m"
|
||||
>
|
||||
<Panel title={`将 ${title} 设置为引用变量或自定义表达式`} subTitle={subTitle} shape="solid">
|
||||
<InputCode
|
||||
shape="inset"
|
||||
minHeight="56px"
|
||||
@@ -233,91 +219,49 @@ export function ExpressionModal({
|
||||
autoCompleteOptions={autoCompleteOptions}
|
||||
/>
|
||||
</Panel>
|
||||
<Box mt="m" maxHeight={400} overflow="auto">
|
||||
<Tabs type="card" size="small">
|
||||
<Tabs.TabPane key="list" tab="从变量列表中选择">
|
||||
<Box my="m">
|
||||
<Alert
|
||||
type="info"
|
||||
message="你可以直接从下方的列表中选择已定义的变量"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
</Box>
|
||||
<EditableVariableTree
|
||||
// height={400}
|
||||
dataSource={dataSource || expressionVariables}
|
||||
onUse={(node) => {
|
||||
let str;
|
||||
if (/^(stores|services)\./.test(node.key)) {
|
||||
str = `{tango.${node.key.replaceAll('.', '?.')}}`;
|
||||
} else {
|
||||
str = `{${node.key}}`;
|
||||
}
|
||||
setExp(str);
|
||||
}}
|
||||
onAddVariable={(storeName, data) => {
|
||||
onAction('addStoreState', [storeName, data.name, data.initialValue]);
|
||||
}}
|
||||
onDeleteVariable={(storeName, stateName) => {
|
||||
onAction('removeStoreState', [storeName, stateName]);
|
||||
}}
|
||||
onDeleteStore={(storeName) => {
|
||||
onAction('removeStoreModule', [storeName]);
|
||||
}}
|
||||
onSave={(code, node) => {
|
||||
onAction('updateModuleCodeByVariablePath', [node.key, code]);
|
||||
}}
|
||||
getPreviewValue={(node) => {
|
||||
if (!node || !node.key) {
|
||||
return;
|
||||
}
|
||||
<Panel title="从变量列表中选中" shape="solid" borderTop="0" maxHeight={400} overflow="auto">
|
||||
<EditableVariableTree
|
||||
dataSource={dataSource || expressionVariables}
|
||||
onSelect={(node) => {
|
||||
if (!node.key) {
|
||||
return;
|
||||
}
|
||||
if (node.key.split('.').length < 2) {
|
||||
return;
|
||||
}
|
||||
let str;
|
||||
if (/^(stores|services)\./.test(node.key)) {
|
||||
str = `{tango.${node.key.replaceAll('.', '?.')}}`;
|
||||
} else {
|
||||
str = `{${node.key}}`;
|
||||
}
|
||||
setExp(str);
|
||||
}}
|
||||
onAddVariable={(storeName, data) => {
|
||||
onAction('addStoreState', [storeName, data.name, data.initialValue]);
|
||||
}}
|
||||
onDeleteVariable={(storeName, stateName) => {
|
||||
onAction('removeStoreState', [storeName, stateName]);
|
||||
}}
|
||||
onDeleteStore={(storeName) => {
|
||||
onAction('removeStoreModule', [storeName]);
|
||||
}}
|
||||
onSave={(code, node) => {
|
||||
onAction('updateModuleCodeByVariablePath', [node.key, code]);
|
||||
}}
|
||||
getPreviewValue={(node) => {
|
||||
if (!node || !node.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === 'function') {
|
||||
return node.raw;
|
||||
}
|
||||
if (node.type === 'function') {
|
||||
return node.raw;
|
||||
}
|
||||
|
||||
return getValue(evaluateContext['tango'], node.key);
|
||||
}}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="ai" tab="使用 AI 助手生成" disabled={!gptService?.textCompletion}>
|
||||
<ChatInput
|
||||
placeholder="例如:实现一个函数,从给定的数组中过滤出name为bob的数据"
|
||||
onGenerate={(text) =>
|
||||
gptService
|
||||
.textCompletion({
|
||||
prompt: getExpressionCode(text),
|
||||
model: 'gpt-3.5-turbo',
|
||||
temperature: 0.5,
|
||||
topP: 1,
|
||||
presencePenalty: 0,
|
||||
frequencyPenalty: 0,
|
||||
mode: 'chat',
|
||||
})
|
||||
.then((res: any) => {
|
||||
return res?.detail?.choices?.[0]?.text;
|
||||
})
|
||||
}
|
||||
onApply={(preview) => {
|
||||
setExp(`{${preview}}`);
|
||||
}}
|
||||
showCloseIcon={false}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Box>
|
||||
return getValue(evaluateContext['tango'], node.key);
|
||||
}}
|
||||
/>
|
||||
</Panel>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function getExpressionCode(userInput: string) {
|
||||
return `你是一个代码生成助手。你使用的编程语言是 JavaScript。不要解释代码,只返回代码块本身,代码必须包裹在 {} 内部。
|
||||
标准的输出格式如下:
|
||||
1. {this.a}
|
||||
2. {() => {}}
|
||||
3. {tango.stores.user.name}
|
||||
|
||||
${userInput}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,6 @@ export function ModelSetter({ value, onChange }: FormItemComponentProps) {
|
||||
onBlur={onInputBlur}
|
||||
suffix={
|
||||
<EditableVariableTreeModal
|
||||
showPreviewAlert={false}
|
||||
title="同步到的变量"
|
||||
trigger={
|
||||
<Tooltip title="从模型列表选择" placement="topRight">
|
||||
|
||||
@@ -148,7 +148,7 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
|
||||
</Box>
|
||||
<VariableTree
|
||||
dataSource={dataSource}
|
||||
deletable
|
||||
showDeleteIcon
|
||||
onSelect={(item) => {
|
||||
setServiceData({ key: item.key, ...workspace.getServiceFunction(item.key) });
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { observer, useWorkspace } from '@music163/tango-context';
|
||||
import {
|
||||
Button,
|
||||
@@ -9,15 +9,17 @@ import {
|
||||
Tooltip,
|
||||
Popconfirm,
|
||||
Radio,
|
||||
Select,
|
||||
message,
|
||||
Tag,
|
||||
Checkbox,
|
||||
ModalProps,
|
||||
} from 'antd';
|
||||
import { FormListProps } from 'antd/lib/form';
|
||||
import { Box, Text } from 'coral-system';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import semverValid from 'semver/functions/valid';
|
||||
import { ConfigGroup, ConfigItem } from '@music163/tango-ui';
|
||||
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { MinusCircleOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { useBoolean } from '@music163/tango-helpers';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { Workspace } from '@music163/tango-core';
|
||||
@@ -177,18 +179,9 @@ function RenderItem({
|
||||
onUpgrade,
|
||||
showTags = true,
|
||||
}: RenderItemProps) {
|
||||
const [version, setVersion] = useState<string>();
|
||||
const [versionList, setVersionList] = useState<Array<{ label: string; value: string }>>([]);
|
||||
const [open, { on, off }] = useBoolean(false);
|
||||
|
||||
const workspace = useWorkspace() as Workspace;
|
||||
// const remoteServices = useRemoteServices();
|
||||
|
||||
// useEffect(() => {
|
||||
// remoteServices?.DependencyService?.listPackageVersions({ packageName: record.name }).then(
|
||||
// (data: any) => setVersionList((data || []).map((v: string) => ({ label: v, value: v }))),
|
||||
// );
|
||||
// }, [remoteServices, record]);
|
||||
|
||||
const basePackage = useMemo(() => {
|
||||
if (type !== DependencyItemType.基础包 || !templateBaseDependencies) return undefined;
|
||||
@@ -198,9 +191,29 @@ function RenderItem({
|
||||
/**
|
||||
* 处理依赖升级的逻辑
|
||||
*/
|
||||
function onDepUpgrade() {
|
||||
function onDepUpgrade(values: Record<string, any>) {
|
||||
// 根据是否有 packages 字段,判断是否已升级为新的规范
|
||||
const hasUpgraded = !!workspace.tangoConfigJson?.getValue('packages');
|
||||
const { version, umd, library, resources, designerResources } = values || {};
|
||||
|
||||
let packageConfig = record?.package;
|
||||
if (umd === false && packageConfig) {
|
||||
packageConfig = {
|
||||
...packageConfig,
|
||||
version,
|
||||
library: undefined,
|
||||
resources: undefined,
|
||||
designerResources: undefined,
|
||||
};
|
||||
} else {
|
||||
packageConfig = {
|
||||
...packageConfig,
|
||||
version,
|
||||
library,
|
||||
resources,
|
||||
designerResources,
|
||||
};
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case DependencyItemType.业务组件:
|
||||
@@ -210,7 +223,7 @@ function RenderItem({
|
||||
version,
|
||||
hasUpgraded
|
||||
? {
|
||||
package: record?.package,
|
||||
package: packageConfig,
|
||||
}
|
||||
: {},
|
||||
);
|
||||
@@ -225,7 +238,7 @@ function RenderItem({
|
||||
basePackage?.version || 'latest',
|
||||
hasUpgraded
|
||||
? {
|
||||
package: record?.package,
|
||||
package: packageConfig,
|
||||
}
|
||||
: {},
|
||||
);
|
||||
@@ -295,7 +308,6 @@ function RenderItem({
|
||||
}
|
||||
|
||||
function onCloseModal() {
|
||||
setVersion(undefined);
|
||||
off();
|
||||
}
|
||||
|
||||
@@ -317,10 +329,8 @@ function RenderItem({
|
||||
const { resources } = packageInfo || {};
|
||||
const dependencies = workspace.packageJson?.getValue('dependencies');
|
||||
const hasUmd = !!resources?.length;
|
||||
const unsafeUmd =
|
||||
hasUmd && resources.some((e: string) => /\/\/unpkg\.f[nt]\.netease\.com\//.test(e));
|
||||
const packageJsonOnly = dependencies?.[name] && !packageInfo;
|
||||
const tangoConfigJsonOnly = !dependencies?.[name] && packageInfo;
|
||||
const packageJsonIncluded = !!dependencies?.[name];
|
||||
const tangoConfigJsonIncluded = !!packageInfo;
|
||||
|
||||
return [
|
||||
hasUmd && (
|
||||
@@ -330,21 +340,14 @@ function RenderItem({
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
),
|
||||
unsafeUmd && (
|
||||
<Tooltip title="该依赖的 UMD 资源来自自建 unpkg 服务,稳定性不可靠,建议修改 tango.config.json 更换为正式 cdn 资源地址">
|
||||
<Tag key="unsafeUmd" color="orange">
|
||||
非 CDN
|
||||
</Tag>
|
||||
packageJsonIncluded && (
|
||||
<Tooltip title="此依赖在 package.json 中存在">
|
||||
<Tag key="packageJson">package</Tag>
|
||||
</Tooltip>
|
||||
),
|
||||
packageJsonOnly && (
|
||||
<Tooltip title="此依赖只在 package.json 中存在">
|
||||
<Tag key="packageJsonOnly">package.json</Tag>
|
||||
</Tooltip>
|
||||
),
|
||||
tangoConfigJsonOnly && (
|
||||
<Tooltip title="此依赖只在 tango.config.json 中存在">
|
||||
<Tag key="packageJsonOnly">tango.config.json</Tag>
|
||||
tangoConfigJsonIncluded && (
|
||||
<Tooltip title="此依赖在 tango.config.json 中存在">
|
||||
<Tag key="tangoConfigJson">tango.config</Tag>
|
||||
</Tooltip>
|
||||
),
|
||||
].filter((e) => e);
|
||||
@@ -356,7 +359,7 @@ function RenderItem({
|
||||
type !== DependencyItemType.基础包
|
||||
? [
|
||||
<a key="upgrade" onClick={on}>
|
||||
升级
|
||||
修改
|
||||
</a>,
|
||||
<Popconfirm onConfirm={onDepDelete} title={`确认删除依赖 ${record.name}?`}>
|
||||
<a key="delete">删除</a>
|
||||
@@ -366,7 +369,7 @@ function RenderItem({
|
||||
? [
|
||||
<Popconfirm
|
||||
title={`确认升级 ${record.name} 到 ${basePackage.version} 吗?`}
|
||||
onConfirm={onDepUpgrade}
|
||||
onConfirm={() => onDepUpgrade({ version: basePackage.version })}
|
||||
>
|
||||
<a key="upgrade">升级</a>
|
||||
</Popconfirm>,
|
||||
@@ -394,28 +397,12 @@ function RenderItem({
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Modal
|
||||
<DependencyConfigModal
|
||||
open={open}
|
||||
title={`升级依赖:${record.name}`}
|
||||
record={record}
|
||||
onCancel={onCloseModal}
|
||||
onOk={onDepUpgrade}
|
||||
>
|
||||
<Text mr="6px">
|
||||
选择依赖版本号
|
||||
<Tooltip title="只提供正式版本列表,Beta版本依赖需要手动升级">
|
||||
<QuestionCircleOutlined />
|
||||
</Tooltip>
|
||||
</Text>
|
||||
<Select
|
||||
style={{ width: 160 }}
|
||||
showSearch
|
||||
defaultValue={record.version}
|
||||
options={versionList || []}
|
||||
onChange={(value: string) => setVersion(value)}
|
||||
placeholder="请选择要升级的版本号"
|
||||
optionFilterProp="value"
|
||||
/>
|
||||
</Modal>
|
||||
onOk={(event, { values }) => onDepUpgrade(values)}
|
||||
/>
|
||||
</List.Item>
|
||||
);
|
||||
}
|
||||
@@ -447,111 +434,114 @@ interface DependencyModalProps {
|
||||
templateBaseDependencies: Record<string, any>;
|
||||
onBizDependencyAdd: Function;
|
||||
onBaseDependencyAdd: Function;
|
||||
record?: Record<string, any>;
|
||||
}
|
||||
|
||||
function AddDependencyModal({
|
||||
templateBaseDependencies,
|
||||
onBizDependencyAdd,
|
||||
onBaseDependencyAdd,
|
||||
}: DependencyModalProps) {
|
||||
record,
|
||||
...rest
|
||||
}: DependencyModalProps & ModalProps) {
|
||||
// 依赖类型
|
||||
const [type, setType] = useState<DependencyItemType>(DependencyItemType.其他依赖);
|
||||
const [open, { on, off }] = useBoolean();
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const workspace = useWorkspace();
|
||||
const sandbox = useSandboxQuery();
|
||||
const hasUpgraded = !!workspace.tangoConfigJson?.getValue('packages');
|
||||
|
||||
const onFinishCallback = (name?: string) => {
|
||||
name && message.success(`${name} 添加成功`);
|
||||
// FIXME: 修复沙箱添加组件后 HMR 失效的 bug,添加完刷新
|
||||
sandbox.reload();
|
||||
off();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
form.validateFields().then((values) => {
|
||||
const { name, version } = values;
|
||||
// 根据是否有 packages 字段,判断是否已升级为新的规范
|
||||
const hasUpgraded = !!workspace.tangoConfigJson?.getValue('packages');
|
||||
const onOk: DependencyConfigModalProps['onOk'] = (event, { values }) => {
|
||||
const { type, name, version, umd, library, resources, designerResources } = values;
|
||||
|
||||
switch (type) {
|
||||
case DependencyItemType.其他依赖: {
|
||||
workspace.updateDependency(name, version);
|
||||
break;
|
||||
}
|
||||
// case DependencyItemType.业务组件: {
|
||||
// const targetBizInfo = (bizList as any)?.find(
|
||||
// (biz: { packageName: string }) => biz.packageName === name,
|
||||
// );
|
||||
// onBizDependencyAdd &&
|
||||
// onBizDependencyAdd({
|
||||
// ...values,
|
||||
// detail: targetBizInfo,
|
||||
// });
|
||||
// break;
|
||||
// }
|
||||
case DependencyItemType.基础包: {
|
||||
const basePackage = templateBaseDependencies[name];
|
||||
// 添加 package.json
|
||||
workspace.updateDependency(
|
||||
name,
|
||||
basePackage.version || 'latest',
|
||||
hasUpgraded
|
||||
? {
|
||||
package: basePackage.package,
|
||||
}
|
||||
: {},
|
||||
);
|
||||
|
||||
// 配置已经合并为新版,不需要走旧的逻辑
|
||||
if (!hasUpgraded) {
|
||||
// 更新 tango.config.json baseDependencies
|
||||
workspace.tangoConfigJson.setValue('baseDependencies', (targetValue) => {
|
||||
const bases = targetValue || [];
|
||||
(bases as string[]).push(name);
|
||||
return bases;
|
||||
});
|
||||
// 更新 tango.config.json externals
|
||||
workspace.tangoConfigJson.setValue('sandbox.externals', (targetValue) => {
|
||||
targetValue[name] = basePackage?.global;
|
||||
return targetValue;
|
||||
});
|
||||
// 更新 tango.config.json externalResources
|
||||
workspace.tangoConfigJson
|
||||
.setValue('sandbox.externalResources', (targetValue) => {
|
||||
basePackage.resources.forEach((resource: string) => {
|
||||
(targetValue as string[]).push(
|
||||
resource.replace('{latest_version}', basePackage.version),
|
||||
);
|
||||
});
|
||||
return targetValue;
|
||||
})
|
||||
.update();
|
||||
}
|
||||
onBaseDependencyAdd && onBaseDependencyAdd(basePackage);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
switch (type) {
|
||||
case DependencyItemType.其他依赖: {
|
||||
workspace.updateDependency(
|
||||
name,
|
||||
version,
|
||||
hasUpgraded && umd
|
||||
? {
|
||||
package: {
|
||||
type: 'dependency',
|
||||
...record?.package,
|
||||
version,
|
||||
library,
|
||||
resources,
|
||||
designerResources,
|
||||
},
|
||||
}
|
||||
: {},
|
||||
);
|
||||
break;
|
||||
}
|
||||
onFinishCallback(values?.name);
|
||||
});
|
||||
};
|
||||
// case DependencyItemType.业务组件: {
|
||||
// const targetBizInfo = (bizList as any)?.find(
|
||||
// (biz: { packageName: string }) => biz.packageName === name,
|
||||
// );
|
||||
// onBizDependencyAdd &&
|
||||
// onBizDependencyAdd({
|
||||
// ...values,
|
||||
// detail: targetBizInfo,
|
||||
// });
|
||||
// break;
|
||||
// }
|
||||
case DependencyItemType.基础包: {
|
||||
const basePackage = templateBaseDependencies[name];
|
||||
// 添加 package.json
|
||||
workspace.updateDependency(
|
||||
name,
|
||||
basePackage.version || 'latest',
|
||||
hasUpgraded
|
||||
? {
|
||||
package: basePackage.package,
|
||||
}
|
||||
: {},
|
||||
);
|
||||
|
||||
const onValuesChange = ({ type: depType }: { type: DependencyItemType; name: string }) => {
|
||||
if (depType) {
|
||||
// type 变更触发
|
||||
form.resetFields(['name', 'version']);
|
||||
setType(depType);
|
||||
// 配置已经合并为新版,不需要走旧的逻辑
|
||||
if (!hasUpgraded) {
|
||||
// 更新 tango.config.json baseDependencies
|
||||
workspace.tangoConfigJson.setValue('baseDependencies', (targetValue) => {
|
||||
const bases = targetValue || [];
|
||||
(bases as string[]).push(name);
|
||||
return bases;
|
||||
});
|
||||
// 更新 tango.config.json externals
|
||||
workspace.tangoConfigJson.setValue('sandbox.externals', (targetValue) => {
|
||||
targetValue[name] = basePackage?.global;
|
||||
return targetValue;
|
||||
});
|
||||
// 更新 tango.config.json externalResources
|
||||
workspace.tangoConfigJson
|
||||
.setValue('sandbox.externalResources', (targetValue) => {
|
||||
basePackage.resources.forEach((resource: string) => {
|
||||
(targetValue as string[]).push(
|
||||
resource.replace('{latest_version}', basePackage.version),
|
||||
);
|
||||
});
|
||||
return targetValue;
|
||||
})
|
||||
.update();
|
||||
}
|
||||
onBaseDependencyAdd && onBaseDependencyAdd(basePackage);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
onFinishCallback(values?.name);
|
||||
};
|
||||
|
||||
const onCloseModal = () => {
|
||||
function onCloseModal() {
|
||||
off();
|
||||
form.resetFields(['name', 'version']);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Box px="l" py="m">
|
||||
@@ -559,31 +549,155 @@ function AddDependencyModal({
|
||||
<PlusOutlined />
|
||||
添加依赖
|
||||
</Button>
|
||||
<Modal
|
||||
width={560}
|
||||
<DependencyConfigModal
|
||||
open={open}
|
||||
title="添加依赖"
|
||||
title={record ? `修改依赖:${record.name}` : '添加依赖'}
|
||||
onOk={onOk}
|
||||
onCancel={onCloseModal}
|
||||
destroyOnClose
|
||||
{...rest}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
type DependencyConfigModalProps = Omit<ModalProps, 'onOk'> & {
|
||||
record?: Record<string, any>;
|
||||
onOk: (
|
||||
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
data: {
|
||||
values: Record<string, any>;
|
||||
hasUpgraded: boolean;
|
||||
},
|
||||
) => any;
|
||||
};
|
||||
|
||||
function ResourceFormList(props: Omit<FormListProps, 'children'>) {
|
||||
return (
|
||||
<Form.List {...props}>
|
||||
{(fields, { add, remove }, { errors }) => (
|
||||
<>
|
||||
{fields.map((field) => (
|
||||
<Form.Item key={field.key}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Form.Item
|
||||
label="资源地址"
|
||||
{...field}
|
||||
rules={[{ required: true, whitespace: true, type: 'url' }]}
|
||||
noStyle
|
||||
>
|
||||
<Input
|
||||
placeholder="资源地址中的版本号可用 {{version}} 替代"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Box width="20px" fontSize="18px" ml="6px" textAlign="center" flex="none">
|
||||
<a onClick={() => remove(field.name)}>
|
||||
<MinusCircleOutlined />
|
||||
</a>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Button type="dashed" onClick={() => add()} block>
|
||||
添加
|
||||
</Button>
|
||||
<Form.ErrorList errors={errors} />
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
);
|
||||
}
|
||||
|
||||
function DependencyConfigModal({
|
||||
record,
|
||||
open,
|
||||
onOk,
|
||||
onCancel,
|
||||
...rest
|
||||
}: DependencyConfigModalProps) {
|
||||
// 依赖类型
|
||||
const [type, setType] = useState<DependencyItemType>(DependencyItemType.其他依赖);
|
||||
const [form] = Form.useForm();
|
||||
const workspace = useWorkspace();
|
||||
// 根据是否有 packages 字段,判断是否已升级为新的规范
|
||||
const hasUpgraded = !!workspace.tangoConfigJson?.getValue('packages');
|
||||
// const [versionList, setVersionList] = useState<Array<{ label: string; value: string }>>([]);
|
||||
|
||||
// const remoteServices = useRemoteServices();
|
||||
|
||||
// useEffect(() => {
|
||||
// remoteServices?.DependencyService?.listPackageVersions({ packageName: record.name }).then(
|
||||
// (data: any) => setVersionList((data || []).map((v: string) => ({ label: v, value: v }))),
|
||||
// );
|
||||
// }, [remoteServices, record]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && form) {
|
||||
form.resetFields();
|
||||
if (record) {
|
||||
setType(record?.type);
|
||||
form.setFieldsValue({
|
||||
...record,
|
||||
...record?.package,
|
||||
umd: !!record?.package?.resources?.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [record, open, form]);
|
||||
|
||||
const onSubmit = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
form.validateFields().then((values) => {
|
||||
if (onOk) {
|
||||
onOk(event, {
|
||||
values,
|
||||
hasUpgraded,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onValuesChange = ({ type: depType }: { type: DependencyItemType; name: string }) => {
|
||||
if (depType) {
|
||||
// type 变更触发
|
||||
form.resetFields();
|
||||
form.setFieldValue('type', depType);
|
||||
setType(depType);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
width={560}
|
||||
open={open}
|
||||
title={record ? `修改依赖:${record.name}` : '添加依赖'}
|
||||
onOk={onSubmit}
|
||||
onCancel={onCancel}
|
||||
destroyOnClose
|
||||
{...rest}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
autoComplete="off"
|
||||
initialValues={{ type: DependencyItemType.业务组件 }}
|
||||
initialValues={{ type }}
|
||||
onValuesChange={onValuesChange}
|
||||
>
|
||||
<Form.Item label="依赖类型" name="type">
|
||||
<Radio.Group value={type}>
|
||||
{/* <Radio.Button value={DependencyItemType.业务组件}>业务组件</Radio.Button> */}
|
||||
<Radio.Button value={DependencyItemType.基础包}>基础包</Radio.Button>
|
||||
<Radio.Button value={DependencyItemType.其他依赖}>其他依赖</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="npm包名" name="name" rules={[{ required: true }]}>
|
||||
<Input placeholder="输入依赖名" />
|
||||
</Form.Item>
|
||||
{!record && (
|
||||
<>
|
||||
<Form.Item label="依赖类型" name="type" rules={[{ required: true }]}>
|
||||
<Radio.Group>
|
||||
{/* <Radio.Button value={DependencyItemType.业务组件}>业务组件</Radio.Button> */}
|
||||
<Radio.Button value={DependencyItemType.基础包}>基础包</Radio.Button>
|
||||
<Radio.Button value={DependencyItemType.其他依赖}>其他依赖</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="npm 包名" name="name" rules={[{ required: true }]}>
|
||||
<Input placeholder="输入依赖名" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
label="版本号"
|
||||
name="version"
|
||||
@@ -591,8 +705,43 @@ function AddDependencyModal({
|
||||
>
|
||||
<Input placeholder="输入版本号" />
|
||||
</Form.Item>
|
||||
{hasUpgraded && (
|
||||
<Form.Item name="umd" valuePropName="checked">
|
||||
<Checkbox>使用预构建 UMD 资源</Checkbox>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{() =>
|
||||
form.getFieldValue('umd') && (
|
||||
<>
|
||||
<Form.Item label="全局变量名" name="library">
|
||||
<Input placeholder="输入 UMD 资源挂载在全局变量下的变量名" />
|
||||
</Form.Item>
|
||||
<Form.Item label="资源地址" required>
|
||||
<ResourceFormList
|
||||
name="resources"
|
||||
rules={[
|
||||
{
|
||||
validator: async (rule, value) => {
|
||||
if (!value?.length) {
|
||||
throw new Error('资源地址不能为空');
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
{form.getFieldValue('type') === DependencyItemType.基础包 && (
|
||||
<Form.Item label="设计器资源地址">
|
||||
<ResourceFormList name="designerResources" />
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { Box, Text, css, HTMLCoralProps } from 'coral-system';
|
||||
import {
|
||||
ClusterOutlined,
|
||||
FunctionOutlined,
|
||||
ApiOutlined,
|
||||
AppstoreOutlined,
|
||||
BuildOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Badge } from 'antd';
|
||||
import { ReactComponentProps } from '@music163/tango-helpers';
|
||||
import { observer, useDesigner } from '@music163/tango-context';
|
||||
import { ResizableBox } from './resizable-box';
|
||||
import { OutlinePanel } from './outline-panel';
|
||||
import { DependencyPanel } from './dependency-panel';
|
||||
import { VariablePanel } from './variable-panel';
|
||||
import { DataSourcePanel } from './datasource-panel';
|
||||
import { getWidget } from '../widgets';
|
||||
|
||||
const sidebarStyle = css`
|
||||
position: relative;
|
||||
@@ -51,7 +41,6 @@ export interface SidebarProps extends ReactComponentProps {
|
||||
* 底部附加内容
|
||||
*/
|
||||
footer?: React.ReactNode;
|
||||
builtinPanelMap?: Record<string, SidebarPanelItemProps>;
|
||||
}
|
||||
|
||||
export interface SidebarPanelItemProps
|
||||
@@ -79,54 +68,21 @@ export interface SidebarPanelItemProps
|
||||
* 面板宽度
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* 如果 key 匹配到内置组件的话,传递给子节点的属性
|
||||
*/
|
||||
widgetProps?: object;
|
||||
}
|
||||
|
||||
const INTERNAL_SIDEBAR_PANEL_MAP: Record<string, SidebarPanelItemProps> = {
|
||||
components: {
|
||||
label: '组件',
|
||||
title: '组件列表',
|
||||
icon: <AppstoreOutlined />,
|
||||
},
|
||||
outline: {
|
||||
label: '结构',
|
||||
title: '结构',
|
||||
icon: <BuildOutlined />,
|
||||
children: <OutlinePanel />,
|
||||
},
|
||||
dependency: {
|
||||
label: '依赖',
|
||||
title: '项目依赖',
|
||||
icon: <ClusterOutlined />,
|
||||
children: <DependencyPanel />,
|
||||
},
|
||||
variables: {
|
||||
label: '变量',
|
||||
title: '变量管理',
|
||||
icon: <FunctionOutlined />,
|
||||
children: <VariablePanel />,
|
||||
width: 440,
|
||||
},
|
||||
dataSource: {
|
||||
label: '接口',
|
||||
title: '数据源与接口',
|
||||
icon: <ApiOutlined />,
|
||||
children: <DataSourcePanel />,
|
||||
width: 600,
|
||||
},
|
||||
};
|
||||
|
||||
function BaseSidebarPanel({
|
||||
panelWidth: defaultPanelWidth = 280,
|
||||
builtinPanelMap = INTERNAL_SIDEBAR_PANEL_MAP,
|
||||
footer,
|
||||
children,
|
||||
}: SidebarProps) {
|
||||
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) {
|
||||
const items = useMemo(() => {
|
||||
const ret: Record<React.Key, SidebarPanelItemProps> = {};
|
||||
React.Children.forEach(children, (child) => {
|
||||
if (child && React.isValidElement(child)) {
|
||||
const widget = getWidget(['sidebar', child.key].join('.'));
|
||||
const fallbackNode = widget ? React.createElement(widget, child.props.widgetProps) : null;
|
||||
ret[child.key] = {
|
||||
...builtinPanelMap[child.key],
|
||||
children: fallbackNode,
|
||||
...child.props,
|
||||
};
|
||||
}
|
||||
@@ -203,7 +159,14 @@ function BaseSidebarPanel({
|
||||
);
|
||||
}
|
||||
|
||||
BaseSidebarPanel.Item = function ({ key, title, doc, isFloat, width }: SidebarPanelItemProps) {
|
||||
BaseSidebarPanel.Item = function ({
|
||||
key,
|
||||
title,
|
||||
doc,
|
||||
isFloat,
|
||||
width,
|
||||
widgetProps,
|
||||
}: SidebarPanelItemProps) {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ISelectedItemData, isString, noop } from '@music163/tango-helpers';
|
||||
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
|
||||
import { IconButton } from '@music163/tango-ui';
|
||||
import { getDragGhostElement } from '../helpers';
|
||||
import { CopyNodeAction, DeleteNodeAction, ViewSourceAction } from '../selection-menu';
|
||||
import { getWidget } from '../widgets';
|
||||
|
||||
/**
|
||||
* 选择辅助工具的对齐方式
|
||||
@@ -15,34 +15,21 @@ type SelectionHelperAlignType = 'top-right' | 'top-left' | 'inner-top-right';
|
||||
|
||||
const boundingOffset = 100; // 检测选中模块的工具栏是否溢出的偏移值
|
||||
|
||||
const internalSelectionToolsMap = {
|
||||
source: <ViewSourceAction />,
|
||||
copy: <CopyNodeAction />,
|
||||
delete: <DeleteNodeAction />,
|
||||
};
|
||||
|
||||
export const INTERNAL_SELECTION_TOOLS = ['source', 'create', 'copy', 'delete'];
|
||||
|
||||
export interface SelectionToolsProps {
|
||||
/**
|
||||
* 动作列表,内置的动作列表有 source-定位到源码, block-创建区块, copy-复制节点, delete-删除节点
|
||||
*/
|
||||
actions?: Array<string | React.ReactElement>;
|
||||
builtinActionMap?: Record<string, React.ReactElement>;
|
||||
}
|
||||
|
||||
export const SelectionTools = observer(
|
||||
({
|
||||
actions: actionsProp = INTERNAL_SELECTION_TOOLS,
|
||||
builtinActionMap = internalSelectionToolsMap,
|
||||
}: SelectionToolsProps) => {
|
||||
({ actions: actionsProp = ['viewSource', 'copyNode', 'deleteNode'] }: SelectionToolsProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const selectSource = workspace.selectSource;
|
||||
const actions = actionsProp.map((item) => {
|
||||
if (isString(item)) {
|
||||
return builtinActionMap[item]
|
||||
? React.cloneElement(builtinActionMap[item], { key: item })
|
||||
: null;
|
||||
const widget = getWidget(['selectionMenu', item].join('.'));
|
||||
return widget ? React.createElement(widget, { key: item }) : null;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -10,21 +10,14 @@ import { DraggingMask } from './mask';
|
||||
|
||||
export interface ViewportProps extends HTMLCoralProps<'div'> {
|
||||
selectionTools?: SelectionToolsProps['actions'];
|
||||
builtinActionMap?: SelectionToolsProps['builtinActionMap'];
|
||||
}
|
||||
|
||||
export function Viewport({
|
||||
selectionTools,
|
||||
builtinActionMap,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: ViewportProps) {
|
||||
export function Viewport({ selectionTools, children, className, ...rest }: ViewportProps) {
|
||||
return (
|
||||
<Box position="relative" height="100%" className={cx('Viewport', className)} {...rest}>
|
||||
<ViewportBody>{children}</ViewportBody>
|
||||
<div className="AuxTools">
|
||||
<SelectionTools actions={selectionTools} builtinActionMap={builtinActionMap} />
|
||||
<SelectionTools actions={selectionTools} />
|
||||
<InsertionPrompt />
|
||||
<DraggingMask />
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,11 @@ export const PreviewTool = observer(() => {
|
||||
shape="ghost"
|
||||
selected={designer.isPreview}
|
||||
onClick={() => {
|
||||
designer.toggleIsPreview(!designer.isPreview);
|
||||
const nextIsPreview = !designer.isPreview;
|
||||
designer.toggleIsPreview(nextIsPreview);
|
||||
if (nextIsPreview) {
|
||||
designer.setActiveView('design');
|
||||
}
|
||||
}}
|
||||
tip="预览"
|
||||
>
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Group } from 'coral-system';
|
||||
import { ReactComponentProps } from '@music163/tango-helpers';
|
||||
import { HistoryTool } from './history';
|
||||
import { ModeSwitchTool } from './mode-switch';
|
||||
import { PreviewTool } from './preview';
|
||||
import { RouteSwitchTool } from './route-switch';
|
||||
import { TogglePanelTool } from './toggle-panel';
|
||||
import { getWidget } from '../widgets';
|
||||
|
||||
export interface ToolbarProps {
|
||||
children?: React.ReactElement | React.ReactElement[];
|
||||
builtinToolMap?: Record<string, React.ReactNode>;
|
||||
}
|
||||
|
||||
const builtinToolsMap = {
|
||||
history: <HistoryTool />,
|
||||
modeSwitch: <ModeSwitchTool />,
|
||||
preview: <PreviewTool />,
|
||||
routeSwitch: <RouteSwitchTool />,
|
||||
togglePanel: <TogglePanelTool />,
|
||||
};
|
||||
|
||||
export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarProps) {
|
||||
export function Toolbar({ children }: ToolbarProps) {
|
||||
const [leftTools, centerTools, rightTools] = useMemo(() => {
|
||||
const left: React.ReactNode[] = [];
|
||||
const center: React.ReactNode[] = [];
|
||||
@@ -28,7 +15,15 @@ export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarP
|
||||
|
||||
let prevPlacement: string;
|
||||
React.Children.forEach(children, (child: React.ReactElement, index) => {
|
||||
let node = child.props.children || builtinToolMap[child.key];
|
||||
let fallbackNode;
|
||||
if (child.key) {
|
||||
const Widget = getWidget(['toolbar', child.key].join('.'));
|
||||
if (Widget) {
|
||||
fallbackNode = React.createElement(Widget, child.props.widgetProps);
|
||||
}
|
||||
}
|
||||
|
||||
let node = child.props.children ?? fallbackNode ?? null;
|
||||
if (!node) {
|
||||
node = child; // separator
|
||||
}
|
||||
@@ -75,9 +70,13 @@ export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarP
|
||||
export interface ToolbarItemProps extends ReactComponentProps {
|
||||
placement?: 'left' | 'center' | 'right';
|
||||
children?: React.ReactElement;
|
||||
/**
|
||||
* 如果 key 匹配到内置组件的话,传递给子节点的属性
|
||||
*/
|
||||
widgetProps?: object;
|
||||
}
|
||||
|
||||
function ToolbarItem({ placement, children }: ToolbarItemProps) {
|
||||
function ToolbarItem({ placement, widgetProps, children }: ToolbarItemProps) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { getValue, setValue } from '@music163/tango-helpers';
|
||||
import { ModeSwitchTool, PreviewTool, RouteSwitchTool, TogglePanelTool } from './toolbar';
|
||||
import {
|
||||
ComponentsPanel,
|
||||
DataSourcePanel,
|
||||
DependencyPanel,
|
||||
OutlinePanel,
|
||||
VariablePanel,
|
||||
} from './sidebar';
|
||||
import { CopyNodeAction, DeleteNodeAction, ViewSourceAction } from './selection-menu';
|
||||
|
||||
const widgets = {};
|
||||
|
||||
export function registerWidget(key: string, component: React.ComponentType<any>) {
|
||||
if (!/^(toolbar|sidebar|selectionMenu)\.[a-zA-z]+$/.test(key)) {
|
||||
throw new Error(
|
||||
`Invalid widget key(${key}), should start with toolbar, sidebar, or selection-menu`,
|
||||
);
|
||||
}
|
||||
setValue(widgets, key, component);
|
||||
}
|
||||
|
||||
export function getWidget(key: string) {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
return getValue(widgets, key);
|
||||
}
|
||||
|
||||
registerWidget('toolbar.modeSwitch', ModeSwitchTool);
|
||||
registerWidget('toolbar.preview', PreviewTool);
|
||||
registerWidget('toolbar.routeSwitch', RouteSwitchTool);
|
||||
registerWidget('toolbar.togglePanel', TogglePanelTool);
|
||||
|
||||
registerWidget('sidebar.components', ComponentsPanel);
|
||||
registerWidget('sidebar.outline', OutlinePanel);
|
||||
registerWidget('sidebar.dependency', DependencyPanel);
|
||||
registerWidget('sidebar.variables', VariablePanel);
|
||||
registerWidget('sidebar.dataSource', DataSourcePanel);
|
||||
|
||||
registerWidget('selectionMenu.copyNode', CopyNodeAction);
|
||||
registerWidget('selectionMenu.deleteNode', DeleteNodeAction);
|
||||
registerWidget('selectionMenu.viewSource', ViewSourceAction);
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.6](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.5...@music163/tango-sandbox@0.1.6) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
## [0.1.5](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.4...@music163/tango-sandbox@0.1.5) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-sandbox",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"description": "sandbox of tango apps",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/tango-core": "^0.2.0",
|
||||
"@music163/tango-core": "^0.2.1",
|
||||
"@music163/tango-helpers": "^0.1.3",
|
||||
"crypto-js": "^4.1.1",
|
||||
"lodash.isequal": "4.5.0",
|
||||
|
||||
@@ -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.2.5](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.4...@music163/tango-setting-form@0.2.5) (2023-10-12)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [0.2.4](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.3...@music163/tango-setting-form@0.2.4) (2023-10-09)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [0.2.3](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.2...@music163/tango-setting-form@0.2.3) (2023-09-22)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [0.2.2](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.1...@music163/tango-setting-form@0.2.2) (2023-09-21)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-setting-form",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.5",
|
||||
"description": "setting form of tango-apps",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -28,9 +28,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/tango-core": "^0.2.0",
|
||||
"@music163/tango-core": "^0.2.1",
|
||||
"@music163/tango-helpers": "^0.1.3",
|
||||
"@music163/tango-ui": "^0.1.4",
|
||||
"@music163/tango-ui": "^0.1.6",
|
||||
"antd": "^4.24.2",
|
||||
"coral-system": "^1.0.5",
|
||||
"mobx": "6.9.0",
|
||||
|
||||
@@ -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.6](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.5...@music163/tango-ui@0.1.6) (2023-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- refactor expSetter ([#41](https://github.com/netease/tango/issues/41)) ([d63517e](https://github.com/netease/tango/commit/d63517ecb936e4227e70c33e610664316625f4f4))
|
||||
|
||||
## [0.1.5](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.4...@music163/tango-ui@0.1.5) (2023-10-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update tabs style ([46b5445](https://github.com/netease/tango/commit/46b54459ceb23f9df52b9eb40dc935bc872ba1b4))
|
||||
|
||||
## [0.1.4](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.3...@music163/tango-ui@0.1.4) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-ui
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-ui",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"description": "ui widgets of tango",
|
||||
"keywords": [
|
||||
"react",
|
||||
|
||||
@@ -9,7 +9,9 @@ export interface CopyClipboardProps {
|
||||
* 拷贝成功后到回调
|
||||
*/
|
||||
onCopy?: () => void;
|
||||
children?: React.ReactNode | ((copied: boolean) => React.ReactElement);
|
||||
children?:
|
||||
| React.ReactNode
|
||||
| ((data: { copied: boolean; onClick: Function }) => React.ReactElement);
|
||||
}
|
||||
|
||||
export function CopyClipboard({ onCopy, text, children }: CopyClipboardProps) {
|
||||
@@ -37,7 +39,7 @@ export function CopyClipboard({ onCopy, text, children }: CopyClipboardProps) {
|
||||
};
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return React.cloneElement(children(copied), {
|
||||
return React.cloneElement(children({ copied, onClick }), {
|
||||
onClick,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export function Tabs({
|
||||
const tabBarStyle = useMemo(() => {
|
||||
let ret = centered ? centerTabBarStyle : defaultTabBarStyle;
|
||||
if (isTabBarSticky) {
|
||||
ret = { ...ret, position: 'sticky', top: tabBarStickyOffset, zIndex: 1, background: '#fff' };
|
||||
ret = { ...ret, position: 'sticky', top: tabBarStickyOffset, zIndex: 2, background: '#fff' };
|
||||
}
|
||||
return ret;
|
||||
}, [centered, isTabBarSticky, tabBarStickyOffset]);
|
||||
|
||||
@@ -11185,9 +11185,9 @@ humanize-ms@^1.2.1:
|
||||
dependencies:
|
||||
ms "^2.0.0"
|
||||
|
||||
husky@^8.0.1:
|
||||
husky@^8.0.3:
|
||||
version "8.0.3"
|
||||
resolved "https://registry.npmmirror.com/husky/-/husky-8.0.3.tgz"
|
||||
resolved "https://registry.npmmirror.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184"
|
||||
integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
@@ -13043,10 +13043,10 @@ lines-and-columns@~2.0.3:
|
||||
resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz"
|
||||
integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==
|
||||
|
||||
lint-staged@^13.1.0:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.npmmirror.com/lint-staged/-/lint-staged-13.3.0.tgz"
|
||||
integrity sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==
|
||||
lint-staged@^14.0.1:
|
||||
version "14.0.1"
|
||||
resolved "https://registry.npmmirror.com/lint-staged/-/lint-staged-14.0.1.tgz#57dfa3013a3d60762d9af5d9c83bdb51291a6232"
|
||||
integrity sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==
|
||||
dependencies:
|
||||
chalk "5.3.0"
|
||||
commander "11.0.0"
|
||||
|
||||
Reference in New Issue
Block a user