Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 564d50b168 | |||
| 0b3e632745 | |||
| 3f073eeb43 | |||
| 6bbbe634ad | |||
| 8fefa6344f | |||
| 5ebbb428fb | |||
| c240c1824b | |||
| d63517ecb9 | |||
| 1a17ee1e94 | |||
| e1bab66819 | |||
| 89bcd45728 | |||
| e86a6d7cd9 | |||
| 46b54459ce | |||
| 85898dee19 | |||
| e429610606 | |||
| b6814d6484 | |||
| 3d7b58f19a | |||
| b6a47acdde | |||
| 226d71c704 | |||
| 1c72ee6ac2 | |||
| 09a1e2135a | |||
| 7999f604fd | |||
| a7df29c3de | |||
| 308db9cac8 | |||
| a1dbf785a5 | |||
| 813eb83ebb |
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn commitlint --edit "$1"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn lint-staged --allow-empty
|
||||||
@@ -14,7 +14,7 @@ export default {
|
|||||||
],
|
],
|
||||||
devServer: {
|
devServer: {
|
||||||
host: 'local.netease.com',
|
host: 'local.netease.com',
|
||||||
port: 7007,
|
port: 8008,
|
||||||
https: {
|
https: {
|
||||||
key: path.resolve(__dirname, 'local.netease.com-key.pem'),
|
key: path.resolve(__dirname, 'local.netease.com-key.pem'),
|
||||||
cert: path.resolve(__dirname, 'local.netease.com.pem'),
|
cert: path.resolve(__dirname, 'local.netease.com.pem'),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "COMPRESS=none umi build",
|
"build": "COMPRESS=none umi build",
|
||||||
"start": "HTTPS=1 umi dev"
|
"start": "umi dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.8.0",
|
"@ant-design/icons": "^4.8.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Row, Switch } from 'antd';
|
import { Row, Switch } from 'antd';
|
||||||
import { observer } from '@music163/tango-designer';
|
import { observer } from '@music163/tango-context';
|
||||||
|
|
||||||
const OtherPanel = observer(({ autoRemove, setAutoRemove }: any) => {
|
const OtherPanel = observer(({ autoRemove, setAutoRemove }: any) => {
|
||||||
const onChange = (checked: boolean) => {
|
const onChange = (checked: boolean) => {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const tangoConfigJson = {
|
|||||||
resources: ['https://unpkg.com/moment@{{version}}/moment.js'],
|
resources: ['https://unpkg.com/moment@{{version}}/moment.js'],
|
||||||
},
|
},
|
||||||
'@music163/tango-boot': {
|
'@music163/tango-boot': {
|
||||||
version: '0.1.3',
|
version: '0.2.1',
|
||||||
library: 'TangoBoot',
|
library: 'TangoBoot',
|
||||||
type: 'baseDependency',
|
type: 'baseDependency',
|
||||||
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'],
|
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'],
|
||||||
@@ -149,7 +149,7 @@ class App extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Page title={tango.stores.app.title}>
|
<Page title={tango.stores.app.title}>
|
||||||
<Section>
|
<Section title="Section Title">
|
||||||
</Section>
|
</Section>
|
||||||
<Section>
|
<Section>
|
||||||
<Button>button</Button>
|
<Button>button</Button>
|
||||||
@@ -175,6 +175,7 @@ export default defineStore({
|
|||||||
|
|
||||||
const serviceCode = `
|
const serviceCode = `
|
||||||
import { defineServices } from '@music163/tango-boot';
|
import { defineServices } from '@music163/tango-boot';
|
||||||
|
import './sub';
|
||||||
|
|
||||||
export default defineServices({
|
export default defineServices({
|
||||||
get: {
|
get: {
|
||||||
@@ -201,6 +202,18 @@ export default defineServices({
|
|||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const subServiceCode = `
|
||||||
|
import { defineServices } from '@music163/tango-boot';
|
||||||
|
|
||||||
|
export default defineServices({
|
||||||
|
list: {
|
||||||
|
url: 'https://nei.hz.netease.com/api/apimock-v2/c45109399a1d33d83e32a59984b25b00/anchor-list-normal',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
namespace: 'sub',
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
const lessCode = `
|
const lessCode = `
|
||||||
body {
|
body {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -232,6 +245,8 @@ export const sampleFiles = [
|
|||||||
{ filename: '/src/stores/app.js', code: storeApp },
|
{ filename: '/src/stores/app.js', code: storeApp },
|
||||||
{ filename: '/src/stores/counter.js', code: storeCounter },
|
{ filename: '/src/stores/counter.js', code: storeCounter },
|
||||||
{ filename: '/src/services/index.js', code: serviceCode },
|
{ filename: '/src/services/index.js', code: serviceCode },
|
||||||
|
{ filename: '/src/services/sub.js', code: subServiceCode },
|
||||||
|
{ filename: '/src/utils/index.js', code: `export function foo() {}` },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const genDefaultPage = (index: number) => ({
|
export const genDefaultPage = (index: number) => ({
|
||||||
|
|||||||
@@ -9,15 +9,21 @@ import {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
WorkspacePanel,
|
WorkspacePanel,
|
||||||
WorkspaceView,
|
WorkspaceView,
|
||||||
ComponentsPanel,
|
|
||||||
CodeEditor,
|
CodeEditor,
|
||||||
Sandbox,
|
Sandbox,
|
||||||
DndQuery,
|
DndQuery,
|
||||||
} from '@music163/tango-designer';
|
} from '@music163/tango-designer';
|
||||||
import { createEngine, Workspace } from '@music163/tango-core';
|
import { createEngine, Workspace } from '@music163/tango-core';
|
||||||
import { Logo, ProjectDetail } from './share';
|
import { Logo, ProjectDetail, bootHelperVariables } from './share';
|
||||||
import { sampleFiles } from '../mock/project';
|
import { sampleFiles } from '../mock/project';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
import {
|
||||||
|
ApiOutlined,
|
||||||
|
AppstoreAddOutlined,
|
||||||
|
BuildOutlined,
|
||||||
|
ClusterOutlined,
|
||||||
|
FunctionOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
// 1. 实例化工作区
|
// 1. 实例化工作区
|
||||||
const workspace = new Workspace({
|
const workspace = new Workspace({
|
||||||
@@ -44,7 +50,14 @@ export default function App() {
|
|||||||
const [menuLoading, setMenuLoading] = useState(true);
|
const [menuLoading, setMenuLoading] = useState(true);
|
||||||
const [menuData, setMenuData] = useState(false);
|
const [menuData, setMenuData] = useState(false);
|
||||||
return (
|
return (
|
||||||
<Designer engine={engine} sandboxQuery={sandboxQuery}>
|
<Designer
|
||||||
|
engine={engine}
|
||||||
|
config={{
|
||||||
|
customActionVariables: bootHelperVariables,
|
||||||
|
customExpressionVariables: bootHelperVariables,
|
||||||
|
}}
|
||||||
|
sandboxQuery={sandboxQuery}
|
||||||
|
>
|
||||||
<DesignerPanel
|
<DesignerPanel
|
||||||
logo={<Logo />}
|
logo={<Logo />}
|
||||||
description={<ProjectDetail />}
|
description={<ProjectDetail />}
|
||||||
@@ -52,10 +65,11 @@ export default function App() {
|
|||||||
<Box px="l">
|
<Box px="l">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Toolbar.Item key="routeSwitch" placement="left" />
|
<Toolbar.Item key="routeSwitch" placement="left" />
|
||||||
|
<Toolbar.Item key="preview" placement="left" />
|
||||||
<Toolbar.Item key="modeSwitch" placement="right" />
|
<Toolbar.Item key="modeSwitch" placement="right" />
|
||||||
<Toolbar.Item key="togglePanel" placement="right" />
|
<Toolbar.Item key="togglePanel" placement="right" />
|
||||||
<Toolbar.Separator />
|
<Toolbar.Separator />
|
||||||
<Toolbar.Item key="extra" placement="right">
|
<Toolbar.Item placement="right">
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary">发布</Button>
|
<Button type="primary">发布</Button>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -65,13 +79,31 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<Sidebar.Item key="outline" />
|
<Sidebar.Item key="outline" label="结构" icon={<BuildOutlined />} />
|
||||||
<Sidebar.Item key="components">
|
<Sidebar.Item
|
||||||
<ComponentsPanel menuData={menuData as any} loading={menuLoading} />
|
key="components"
|
||||||
</Sidebar.Item>
|
label="组件"
|
||||||
<Sidebar.Item key="variables" isFloat width={800} />
|
icon={<AppstoreAddOutlined />}
|
||||||
<Sidebar.Item key="dataSource" isFloat width={800} />
|
widgetProps={{
|
||||||
<Sidebar.Item key="dependency" isFloat width={800} />
|
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>
|
</Sidebar>
|
||||||
<WorkspacePanel>
|
<WorkspacePanel>
|
||||||
<WorkspaceView mode="design">
|
<WorkspaceView mode="design">
|
||||||
|
|||||||
@@ -6,6 +6,36 @@ import { registerSetter } from '@music163/tango-designer';
|
|||||||
import type { ComponentPrototypeType } from '@music163/tango-helpers';
|
import type { ComponentPrototypeType } from '@music163/tango-helpers';
|
||||||
import { FooSetter } from '../components';
|
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
|
// folder-name
|
||||||
// 物料列表定义
|
// 物料列表定义
|
||||||
const bizToggleButtonPrototype: ComponentPrototypeType = {
|
const bizToggleButtonPrototype: ComponentPrototypeType = {
|
||||||
@@ -133,7 +163,7 @@ export function ProjectDetail() {
|
|||||||
return (
|
return (
|
||||||
<Box display="flex" alignItems="center" columnGap="l">
|
<Box display="flex" alignItems="center" columnGap="l">
|
||||||
<Box className="ProjectName" fontSize="18px" fontWeight="bold">
|
<Box className="ProjectName" fontSize="18px" fontWeight="bold">
|
||||||
lc-online-test
|
community-test
|
||||||
</Box>
|
</Box>
|
||||||
<Box className="BranchName" as="code" fontSize="14px">
|
<Box className="BranchName" as="code" fontSize="14px">
|
||||||
<BranchesOutlined /> feature/list
|
<BranchesOutlined /> feature/list
|
||||||
|
|||||||
@@ -10,11 +10,20 @@
|
|||||||
|
|
||||||
项目使用的 npm 包依赖信息,用于定义依赖的类型以及外置依赖资源。该配置项为一个 map,key 为对应的 npm 包名,value 为该依赖的配置信息。
|
项目使用的 npm 包依赖信息,用于定义依赖的类型以及外置依赖资源。该配置项为一个 map,key 为对应的 npm 包名,value 为该依赖的配置信息。
|
||||||
|
|
||||||
|
- version 包的版本号,必选
|
||||||
|
- library UMD 包中的全局变量名,必选
|
||||||
|
- type 依赖的类型
|
||||||
|
- baseDependency 基础依赖包
|
||||||
|
- bizDependency 业务组件依赖包
|
||||||
|
- dependency 其他依赖包
|
||||||
|
- resources 外置依赖资源列表
|
||||||
|
- designerResources 在设计器视图载入的外置资源列表,用来需要区别加载时使用
|
||||||
|
- description 描述信息
|
||||||
|
|
||||||
依赖的类型 `type` 可以是 `baseDependency` 即项目的基础依赖,用于获取项目基础物料列表的信息;也可以是 `bizDependency` 即业务组件依赖,用于获取合并到物料面板业务组件菜单中的业务组件列表;也可以是 `depenency` 即项目的其他依赖,只用于提供 external 信息与版本信息等,不会和物料面板产生关系。
|
依赖的类型 `type` 可以是 `baseDependency` 即项目的基础依赖,用于获取项目基础物料列表的信息;也可以是 `bizDependency` 即业务组件依赖,用于获取合并到物料面板业务组件菜单中的业务组件列表;也可以是 `depenency` 即项目的其他依赖,只用于提供 external 信息与版本信息等,不会和物料面板产生关系。
|
||||||
|
|
||||||
当配置了外置依赖资源 `resources` 与其对应的全局变量 `library` 时,当这些依赖被 import 时,相关资源不会被打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖,其等效于之前的 `sandbox.externalResources` 与 `sandbox.externals` 配置项。此外,可以将 `resource` 中的版本号替换为 `{{version}}`,其会在平台上构建时替换为版本号。特殊的,可以定义 `designerResources` 用于适配平台的设计器视图。
|
当配置了外置依赖资源 `resources` 与其对应的全局变量 `library` 时,当这些依赖被 import 时,相关资源不会被打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖,其等效于之前的 `sandbox.externalResources` 与 `sandbox.externals` 配置项。此外,可以将 `resource` 中的版本号替换为 `{{version}}`,其会在平台上构建时替换为版本号。特殊的,可以定义 `designerResources` 用于适配平台的设计器视图。
|
||||||
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"react": {
|
"react": {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import Link from '@docusaurus/Link';
|
|||||||
|
|
||||||
# 快速开始
|
# 快速开始
|
||||||
|
|
||||||
Tango 是一个用于快速构建低代码工具的前端框架。借助 Tango 只需要少量代码就可以快速的搭建一个基本的低代码设计器。本文将会帮助你借助 Tango 实现一个基本的低代码可视化设计器。
|
Tango 是一个用于快速构建低代码平台的低代码设计器框架,借助 Tango 只需要数行代码就可以完成一个基本的低代码平台前端系统的搭建。
|
||||||
|
|
||||||
:::warning 测试版免责声明
|
:::warning 测试版免责声明
|
||||||
基于 Tango 低代码引擎的低代码平台已经在网易云音乐内部服务了内部众多业务开发场景。由于开源工作涉及到大量的代码分离工作,当前开源版本为低代码引擎的 alpha 演示版本,API 可能不稳定,不建议在生产环境中使用。我们预计在 2023 年 Q4 发布可以运行在生产环境的正式版。
|
当前开源版本为低代码引擎的 beta 演示版本,API 可能不稳定,不建议在生产环境中使用。为了保障社区版本能够有效与内部版本解藕,并且项目开源涉及到大量的额外的代码分离工作,我们预计在 2023 年 Q4 发布可以运行在生产环境的正式版。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 环境要求
|
## 环境要求
|
||||||
@@ -34,6 +34,10 @@ Tango 是一个用于快速构建低代码工具的前端框架。借助 Tango
|
|||||||
|
|
||||||
本地开发除了需要启动 Tango 的 playground 外,还需要一个可用的沙箱环境来承载设计器。
|
本地开发除了需要启动 Tango 的 playground 外,还需要一个可用的沙箱环境来承载设计器。
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
沙箱部分来源于 codesandbox,不属于 tango 的核心代码库,我们在原有的 codesandbox 基础上进行了一系列的改造
|
||||||
|
:::
|
||||||
|
|
||||||
如果没有可用的在线沙箱,可以通过本地启动一个沙箱来完成开发;如果已有一个在线沙箱,且沙箱与本地的 playground 共用一级域名,请直接跳至第 7 步修改 `bundlerURL` 为在线沙箱的地址。
|
如果没有可用的在线沙箱,可以通过本地启动一个沙箱来完成开发;如果已有一个在线沙箱,且沙箱与本地的 playground 共用一级域名,请直接跳至第 7 步修改 `bundlerURL` 为在线沙箱的地址。
|
||||||
|
|
||||||
1. 克隆仓库至本地
|
1. 克隆仓库至本地
|
||||||
|
|||||||
@@ -2,36 +2,30 @@ import Link from '@docusaurus/Link';
|
|||||||
|
|
||||||
# 简介
|
# 简介
|
||||||
|
|
||||||
Tango 是一个用于快速构建低代码平台的低代码设计器框架,借助 Tango 只需要数行代码就可以完成一个基本的低代码平台前端系统的搭建。Tango 低代码设计器**直接读取前端项目的源代码,并以源代码为中心,执行和渲染前端视图,并为用户提供低代码可视化搭建能力,用户的搭建操作会转为对代码的修改**。借助 Tango 可以实现 **源码进,源码出**的效果。
|
Tango 是一个用于快速构建低代码平台的低代码设计器框架,借助 Tango 只需要数行代码就可以完成一个基本的低代码平台前端系统的搭建。Tango 低代码设计器直接读取前端项目的源代码,并以源代码为中心,执行和渲染前端视图,并为用户提供低代码可视化搭建能力,用户的搭建操作会转为对源代码的修改。借助于 Tango 构建的低代码工具或平台,可以实现 源码进,源码出的效果,无缝与企业内部现有的研发体系进行集成。
|
||||||
|
|
||||||
## 引擎架构概览
|
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/30108735057/7ba9/dced/9ac3/420f6e04b371dd47de06e7d71142560d.gif" alt="preview" />
|
||||||
|
|
||||||
Tango 低代码引擎在实现上进行了分层解藕,使得上层的低代码平台与底层的低代码引擎可以独立开发和维护,快速集成部署。另一方面,Tango 低代码引擎定义了一套开放的物料生态体系,开发者可以自由的贡献扩展组件配置能力的属性设置器,以及扩展低代码物料的二方三方业务组件。
|
## 核心特性
|
||||||
|
|
||||||
如下图所示:
|
- 经历网易云音乐内网生产环境的实际检验,可灵活集成应用于低代码平台,本地开发工具等
|
||||||
|
- 基于源码 AST 驱动,无私有 DSL 和协议
|
||||||
|
- 提供实时出码能力,支持源码进,源码出
|
||||||
|
- 开箱即用的前端低代码设计器,提供灵活易用的设计器 React 组件
|
||||||
|
- 使用 TypeScript 开发,提供完整的类型定义文件
|
||||||
|
|
||||||
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/18236990116/aabf/8a16/28ee/f0fd29a84c7aa40bb26383cdef12b88c.png" />
|
## 实现原理
|
||||||
|
|
||||||
## 引擎设计器构成概览
|
Tango 低代码引擎不依赖私有搭建协议和 DSL,而是直接使用源代码驱动,引擎内部将源码转为 AST,用户的所有的搭建操作转为对 AST 的遍历和修改,进而将 AST 重新生成为代码,将代码同步给在线沙箱执行。具体的过程如下图所示,首先将源代码解析为 AST。用户的拖拉拽等操作则映射为对 AST 的遍历和修改。最后将新的 AST 重新生成代码,交给设计器沙箱去渲染执行。而对 AST 的解析、遍历、修改、生成,则可以借助大量的社区工具,这里我们选择的是 babel!
|
||||||
|
|
||||||
Tango 低代码引擎设计器用于低成本初始化一个基本的低代码平台,其前端部分主要包括如下几个部分:
|

|
||||||
|
|
||||||
- 设计器主框架:进行外框架初始化,状态管理,拖拽引擎绑定等核心逻辑。
|
## 技术架构
|
||||||
- 侧边栏面板:提供可扩展的侧边面板,除了内置通用的面板,用户还可以任意添加自定义的面板。
|
|
||||||
- 属性设置面板:属性设置器面板,支持用户进行属性配置。
|
|
||||||
- 运行时沙箱:用于设计器运行时视图的渲染。
|
|
||||||
- Web IDE:用于进行在线源代码编辑。
|
|
||||||
|
|
||||||
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/30108642346/b8cf/e86d/ef5a/514d90b722b5d8dc0e18516ed594a07b.png" />
|
Tango 低代码引擎主要包括 3 个核心组成部分,分别是:
|
||||||
|
|
||||||
## 基于源代码的引擎内核
|
- 引擎内核:扶额建立文件,节点模型,提供输入输出能力。
|
||||||
|
- 拖拽引擎和可视化面板:提供可视化开发能力
|
||||||
|
- 渲染沙箱:提供源码在浏览器上的编译执行能力。
|
||||||
|
|
||||||
Tango 低代码引擎不依赖私有搭建协议和 DSL,而是直接使用源代码驱动,引擎内部将源码转为 AST,用户的所有的搭建操作转为对 AST 的遍历和修改,进而将 AST 重新生成为代码,将代码同步给在线沙箱执行。与传统的借助 [Schema 驱动的低代码方案](https://mp.weixin.qq.com/s/yqYey76qLGYPfDtpGkVFfA) 相比,不受到私有 DSL 和协议的限制,能够完美的实现低代码搭建与源码开发的无缝集成。
|

|
||||||
|
|
||||||
<img src="https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13140534982/ee2e/f42c/cc9a/184e2918a011b57d46e6c64a2722fa44.png" />
|
|
||||||
|
|
||||||
## 源码进,源码出
|
|
||||||
|
|
||||||
由于引擎内核完全基于源代码驱动实现,Tango 低代码引擎能够实现源代码进,源代码出的可视化搭建能力,不提供任何私有的中间产物。从而使得,Tango 构建的线上研发能力可以与团队现有的研发服务(代码托管、构建、部署、CDN)无缝衔接与集成。
|
|
||||||
|
|
||||||
<img src="https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13208022400/b809/b82e/77b0/5e4fe78a8f11c8ed89c9ec9ced43e845.png" />
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const config = {
|
|||||||
type: 'docSidebar',
|
type: 'docSidebar',
|
||||||
sidebarId: 'designer',
|
sidebarId: 'designer',
|
||||||
position: 'left',
|
position: 'left',
|
||||||
label: '引擎文档',
|
label: '文档',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'docSidebar',
|
type: 'docSidebar',
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 279 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 61 KiB |
@@ -6,7 +6,7 @@ tags: [netease, tango, open-source]
|
|||||||
|
|
||||||
## 📝 Introduction
|
## 📝 Introduction
|
||||||
|
|
||||||
Tango is a low-code designer framework for quickly building low-code platforms. With Tango, you only need a few lines of code to build a basic low-code platform frontend system. The Tango low-code designer directly reads the source code of the frontend project, executes and renders the frontend view based on the source code, and provides users with low-code visual building capabilities. User's building operations will be translated into modifications to the source code. With low-code tools or platforms built using Tango, it is possible to achieve the effect of "source code in, source code out" and seamlessly integrate with the existing R&D system within the enterprise.
|
Tango is a low-code builder framework for quickly building low-code platforms or tools. With Tango, you only need a few lines of code to build a basic low-code builder. The Tango low-code builder reads the source code of the frontend project directly, executes and renders the frontend view based on the source code, and provides users with low-code visual building capabilities. User's building operations will be translated into modifications to the source code. With low-code tools or platforms built using Tango, it is possible to achieve the effect of "source code in, source code out" and seamlessly integrate with the existing development flow within the enterprise.
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@ Tango is a low-code designer framework for quickly building low-code platforms.
|
|||||||
|
|
||||||
Due to the fact that the engine kernel is completely based on the source code-driven implementation, Tango low-code engine can achieve the visualization building capability of source code input and source code output, without providing any private intermediate products. If the company already has a complete R&D system (code hosting, building, deployment, CDN), Tango low-code engine can be directly used to integrate with existing services to build a low-code development platform.
|
Due to the fact that the engine kernel is completely based on the source code-driven implementation, Tango low-code engine can achieve the visualization building capability of source code input and source code output, without providing any private intermediate products. If the company already has a complete R&D system (code hosting, building, deployment, CDN), Tango low-code engine can be directly used to integrate with existing services to build a low-code development platform.
|
||||||
|
|
||||||
<img src="https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13140534982/ee2e/f42c/cc9a/184e2918a011b57d46e6c64a2722fa44.png" />
|

|
||||||
|
|
||||||
### 📄 Code in, Code out
|
### 📄 Code in, Code out
|
||||||
|
|
||||||
由于引擎内核完全基于源代码驱动实现,Tango 低代码引擎能够实现源代码进,源代码出的可视化搭建能力,不提供任何私有的中间产物。如果公司内部已经有了一套完善的研发体系(代码托管、构建、部署、CDN),那么可以直接使用 Tango 低代码引擎与现有的服务集成构建低代码开发平台。
|
Due to the engine kernel being completely driven by source code, the Tango low-code engine can achieve the visual construction capability of source code in and source code out, without providing any private intermediate products. If the company already has a complete development system (code hosting, building, deployment, CDN), then it can directly use the Tango low-code engine to integrate with existing services to build a low-code development platform.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -32,23 +32,24 @@ Due to the fact that the engine kernel is completely based on the source code-dr
|
|||||||
|
|
||||||
Compared to the low-code building solution based on private schema, Tango low-code engine has the following advantages:
|
Compared to the low-code building solution based on private schema, Tango low-code engine has the following advantages:
|
||||||
|
|
||||||
| Comparison Items | Schema-base low-code solution | Tango (Code-based) |
|
| Comparison Items | Schema-base low-code solution | Tango (Code-based) |
|
||||||
| ---------------- | ----------------------------------------- | ------------------------------------------- |
|
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||||
| Applicable Scenarios | Targeted vertical building scenarios, such as forms, marketing pages, etc. | 🔥 Applicable to application building scenarios centered on source code |
|
| Applicable Scenarios | Targeted vertical building scenarios, such as forms, marketing pages, etc. | 🔥 Applicable to application building scenarios centered on source code |
|
||||||
| Language Capabilities | Relies on private protocols for extension, inflexible, and difficult to align with programming language capabilities | 🔥 Based directly on JavaScript language, can use all language features, no extensibility issues |
|
| Language Capabilities | Relies on private protocols for extension, inflexible, and difficult to align with programming language capabilities | 🔥 Based directly on JavaScript language, can use all language features, no extensibility issues |
|
||||||
| Development Capability | LowCode | 🔥 LowCode + ProCode |
|
| Development Capability | LowCode | 🔥 LowCode + ProCode |
|
||||||
| Source Code Export | Centered on schema, one-way code output, irreversible | 🔥 Source code-centered, two-way transcoding |
|
| Source Code Export | Centered on schema, one-way code output, irreversible | 🔥 Source code-centered, two-way transcoding |
|
||||||
| Custom Dependencies | Need to be encapsulated according to private protocol extension, high customization cost | 🔥 Existing components can be seamlessly and low-cost integrated |
|
| Custom Dependencies | Need to be encapsulated according to private protocol extension, high customization cost | 🔥 Existing components can be seamlessly and low-cost integrated |
|
||||||
| Integrated R&D Facilities | High customization cost, additional customization required | 🔥 Low-cost integration, can directly reuse existing deployment and publishing capabilities |
|
| Integrated R&D Facilities | High customization cost, additional customization required | 🔥 Low-cost integration, can directly reuse existing deployment and publishing capabilities |
|
||||||
|
|
||||||
The specific technical architecture is shown in the following figure:
|
The specific technical architecture is shown in the following figure:
|
||||||
|
|
||||||
## 📐 Architecture
|
## 📐 Architecture
|
||||||
|
|
||||||
The Tango low-code engine has been layered and decoupled in implementation, enabling independent development and maintenance of the upper-level low-code platform and the lower-level low-code engine, allowing for fast integration and deployment. In addition, Tango's low-code engine defines an open material ecosystem, allowing developers to freely contribute to extension components and configure property setters that enhance low-code materials.
|
The Tango low-code engine has been layered and decoupled in implementation, enabling independent development and maintenance of the upper-level low-code platform and the lower-level low-code engine, allowing for fast integration and deployment. In addition, Tango's low-code engine defines an open material ecosystem, allowing developers to freely contribute to extension components and configure property setters that enhance low-code materials.
|
||||||
|
|
||||||
The specific technical architecture is shown in the following diagram.
|
The specific technical architecture is shown in the following diagram.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## ⏰ Milestones
|
## ⏰ Milestones
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ Tango low-code engine is the core component of NetEase Cloud Music's internal lo
|
|||||||
2. We plan to release the 1.0 Beta version of the low-code engine on September 30th this year. This version will refactor the core implementation for community scenarios, remove some compatibility code we have internally in Cloud Music, and optimize the implementation of the core.
|
2. We plan to release the 1.0 Beta version of the low-code engine on September 30th this year. This version will refactor the core implementation for community scenarios, remove some compatibility code we have internally in Cloud Music, and optimize the implementation of the core.
|
||||||
3. We plan to release the 1.0 RC version of the low-code engine on October 30th this year. This version will ensure that the core API is basically stable and no longer has BREAKING CHANGE. At the same time, we will provide comprehensive and detailed development guides, deployment documents, and demonstration applications.
|
3. We plan to release the 1.0 RC version of the low-code engine on October 30th this year. This version will ensure that the core API is basically stable and no longer has BREAKING CHANGE. At the same time, we will provide comprehensive and detailed development guides, deployment documents, and demonstration applications.
|
||||||
4. The official version will be released before the end of Q4 2023, and we will further improve our open source community operation mechanism.
|
4. The official version will be released before the end of Q4 2023, and we will further improve our open source community operation mechanism.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 🤝 Community
|
## 🤝 Community
|
||||||
@@ -74,3 +76,5 @@ Welcome everyone to join our community and participate in the open source constr
|
|||||||
Thanks to the Netease Cloud Music Public Technical Team, the Front-End Team, the Live Broadcasting Technical Team, and all the colleagues who participated in the Tango project.
|
Thanks to the Netease Cloud Music Public Technical Team, the Front-End Team, the Live Broadcasting Technical Team, and all the colleagues who participated in the Tango project.
|
||||||
|
|
||||||
Thanks to the Sandpack project provided by CodeSandbox, which provides powerful browser-based code building and execution capabilities for Tango.
|
Thanks to the Sandpack project provided by CodeSandbox, which provides powerful browser-based code building and execution capabilities for Tango.
|
||||||
|
|
||||||
|
Tanks to the Babel project, which provides powerful javascript ast parsing and traversing capabilities for Tango.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 259 KiB |
@@ -25,8 +25,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "2.4.1",
|
"@docusaurus/module-type-aliases": "2.4.1",
|
||||||
"@tsconfig/docusaurus": "^1.0.5",
|
"@tsconfig/docusaurus": "^1.0.5"
|
||||||
"typescript": "^4.7.4"
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
@@ -3,37 +3,37 @@ import clsx from 'clsx';
|
|||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
import Translate, { translate } from '@docusaurus/Translate';
|
import Translate, { translate } from '@docusaurus/Translate';
|
||||||
|
|
||||||
type FeatureItem = {
|
interface FeatureItem {
|
||||||
title: string;
|
title: string;
|
||||||
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
|
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
|
||||||
description: JSX.Element;
|
description: JSX.Element;
|
||||||
};
|
}
|
||||||
|
|
||||||
const FeatureList: FeatureItem[] = [
|
const FeatureList: FeatureItem[] = [
|
||||||
{
|
{
|
||||||
title: translate({ id: 'homepage.features.low-code', message: '可视化搭建' }),
|
title: translate({ id: 'homepage.features.code-driven', message: '源码驱动' }),
|
||||||
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
|
Svg: require('@site/static/img/html-d.svg').default,
|
||||||
description: (
|
description: (
|
||||||
<Translate id="homepage.features.low-code-content">
|
<Translate id="homepage.features.code-driven-content">
|
||||||
提供通用低代码引擎,支持通过拖拽和配置的方式快捷创建和修改应用,轻松完成模式化应用开发。
|
基于项目源码提供低代码能力,提供源码级的自定义扩展能力
|
||||||
</Translate>
|
</Translate>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: translate({ id: 'homepage.features.code-driven', message: '源码驱动' }),
|
title: translate({ id: 'homepage.features.low-code', message: '实时出码' }),
|
||||||
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
|
Svg: require('@site/static/img/digital-content-4.svg').default,
|
||||||
description: (
|
description: (
|
||||||
<Translate id="homepage.features.code-driven-content">
|
<Translate id="homepage.features.low-code-content">
|
||||||
低代码引擎底层使用源代码驱动,无私有搭建协议和DSL,支持在线低代码和源码双模式同步开发。
|
源码进,源码出,可视化和源码自由切换
|
||||||
</Translate>
|
</Translate>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: translate({ id: 'homepage.features.outbox', message: '开箱即用' }),
|
title: translate({ id: 'homepage.features.outbox', message: '开箱即用' }),
|
||||||
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
|
Svg: require('@site/static/img/cpu-f.svg').default,
|
||||||
description: (
|
description: (
|
||||||
<Translate id="homepage.features.outbox-content">
|
<Translate id="homepage.features.outbox-content">
|
||||||
提供开箱即用的低代码设计器组件,支持轻松网关,快捷构建基于低代码引擎的低代码生产力工具。
|
提供灵活易用的设计器框架,支持开发者自由扩展封装
|
||||||
</Translate>
|
</Translate>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300" data-imageid="digital-content-4" imageName="Coding" class="illustrations_image" style="width: 261px;"><g id="_87_coding_outline" data-name="#87_coding_outline"><path d="M289.32,90.26V223.54a3.65,3.65,0,0,1-3.65,3.65H130.85a3.65,3.65,0,0,1-3.65-3.65V90.26a3.65,3.65,0,0,1,3.65-3.66H285.67A3.65,3.65,0,0,1,289.32,90.26Z" fill="#2e8555" class="target-color"/><path d="M285.67,228.19H130.85a4.66,4.66,0,0,1-4.65-4.65V90.26a4.66,4.66,0,0,1,4.65-4.66H285.67a4.66,4.66,0,0,1,4.65,4.66V223.54A4.65,4.65,0,0,1,285.67,228.19ZM130.85,87.6a2.66,2.66,0,0,0-2.65,2.66V223.54a2.65,2.65,0,0,0,2.65,2.65H285.67a2.65,2.65,0,0,0,2.65-2.65V90.26a2.66,2.66,0,0,0-2.65-2.66Z" fill="#093f68"/><path d="M272.8,76.47V209.75a3.65,3.65,0,0,1-3.65,3.65H114.33a3.65,3.65,0,0,1-3.65-3.65V76.47a3.65,3.65,0,0,1,3.65-3.66H269.15A3.65,3.65,0,0,1,272.8,76.47Z" fill="#fff"/><path d="M269.15,214.4H114.33a4.65,4.65,0,0,1-4.65-4.65V76.47a4.66,4.66,0,0,1,4.65-4.66H269.15a4.66,4.66,0,0,1,4.65,4.66V209.75A4.65,4.65,0,0,1,269.15,214.4ZM114.33,73.81a2.66,2.66,0,0,0-2.65,2.66V209.75a2.65,2.65,0,0,0,2.65,2.65H269.15a2.65,2.65,0,0,0,2.65-2.65V76.47a2.66,2.66,0,0,0-2.65-2.66Z" fill="#093f68"/><path d="M272.8,76.47V209.75a3.65,3.65,0,0,1-3.65,3.65h-103V72.81h103A3.65,3.65,0,0,1,272.8,76.47Z" fill="#093f68"/><path d="M269.15,214.4h-103a1,1,0,0,1-1-1V72.81a1,1,0,0,1,1-1h103a4.66,4.66,0,0,1,4.65,4.66V209.75A4.65,4.65,0,0,1,269.15,214.4Zm-102-2h102a2.65,2.65,0,0,0,2.65-2.65V76.47a2.66,2.66,0,0,0-2.65-2.66h-102Z" fill="#093f68"/><path d="M272.8,76.47v15H110.68v-15a3.65,3.65,0,0,1,3.65-3.66H269.15A3.65,3.65,0,0,1,272.8,76.47Z" fill="#fff"/><path d="M272.8,92.48H110.68a1,1,0,0,1-1-1v-15a4.66,4.66,0,0,1,4.65-4.66H269.15a4.66,4.66,0,0,1,4.65,4.66v15A1,1,0,0,1,272.8,92.48Zm-161.12-2H271.8v-14a2.66,2.66,0,0,0-2.65-2.66H114.33a2.66,2.66,0,0,0-2.65,2.66Z" fill="#093f68"/><circle cx="123.19" cy="81.63" r="3.38" fill="#f56132"/><path d="M123.19,86a4.38,4.38,0,1,1,4.38-4.38A4.39,4.39,0,0,1,123.19,86Zm0-6.76a2.38,2.38,0,1,0,2.38,2.38A2.39,2.39,0,0,0,123.19,79.25Z" fill="#093f68"/><circle cx="136.84" cy="81.63" r="3.38" fill="#ffbc0e"/><path d="M136.84,86a4.38,4.38,0,1,1,4.37-4.38A4.39,4.39,0,0,1,136.84,86Zm0-6.76a2.38,2.38,0,1,0,2.37,2.38A2.38,2.38,0,0,0,136.84,79.25Z" fill="#093f68"/><circle cx="150.48" cy="81.63" r="3.38" fill="#70cc40"/><path d="M150.48,86a4.38,4.38,0,1,1,4.38-4.38A4.39,4.39,0,0,1,150.48,86Zm0-6.76a2.38,2.38,0,1,0,2.38,2.38A2.39,2.39,0,0,0,150.48,79.25Z" fill="#093f68"/><path d="M148.3,104.19H120.73a1,1,0,0,1,0-2H148.3a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M153,116.59H129.79a1,1,0,0,1,0-2H153a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M156.18,129h-19.3a1,1,0,0,1,0-2h19.3a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M149.88,141.41H125.46a1,1,0,0,1,0-2h24.42a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M143.31,153.81H120.73a1,1,0,0,1,0-2h22.58a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M148.3,166.22h-20a1,1,0,0,1,0-2h20a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M141.41,178.62H120.73a1,1,0,0,1,0-2h20.68a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M148.3,191h-20a1,1,0,0,1,0-2h20a1,1,0,0,1,0,2Z" fill="#dfeaef"/><path d="M193.62,104.19h-16.8a1,1,0,0,1,0-2h16.8a1,1,0,0,1,0,2Z" fill="#fff"/><path d="M216.82,116.59H182.34a1,1,0,0,1,0-2h34.48a1,1,0,0,1,0,2Z" fill="#ffbc0e"/><path d="M208.26,129H182.34a1,1,0,0,1,0-2h25.92a1,1,0,0,1,0,2Z" fill="#ffbc0e"/><path d="M194.58,141.41H179.7a1,1,0,0,1,0-2h14.88a1,1,0,0,1,0,2Z" fill="#fff"/><path d="M200.5,153.81H183.7a1,1,0,0,1,0-2h16.8a1,1,0,0,1,0,2Z" fill="#f56132"/><path d="M209.06,166.22H183.12a1,1,0,0,1,0-2h25.94a1,1,0,0,1,0,2Z" fill="#fff"/><path d="M189.42,178.62h-12.6a1,1,0,0,1,0-2h12.6a1,1,0,0,1,0,2Z" fill="#fff"/><path d="M209.06,191h-27.6a1,1,0,0,1,0-2h27.6a1,1,0,0,1,0,2Z" fill="#70cc40"/><path d="M235.5,191H217.38a1,1,0,0,1,0-2H235.5a1,1,0,0,1,0,2Z" fill="#ffbc0e"/><path d="M229.62,129h-12.8a1,1,0,0,1,0-2h12.8a1,1,0,0,1,0,2Z" fill="#fff"/><path d="M247.87,116.59H223.22a1,1,0,0,1,0-2h24.65a1,1,0,0,1,0,2Z" fill="#2e8555" class="target-color"/><path d="M243.79,129h-7.21a1,1,0,0,1,0-2h7.21a1,1,0,0,1,0,2Z" fill="#f56132"/><path d="M212.66,153.81h-7.2a1,1,0,1,1,0-2h7.2a1,1,0,1,1,0,2Z" fill="#f56132"/><path d="M247.87,153.81H218.58a1,1,0,1,1,0-2h29.29a1,1,0,0,1,0,2Z" fill="#ffbc0e"/><path d="M223.22,166.22h-6.4a1,1,0,0,1,0-2h6.4a1,1,0,0,1,0,2Z" fill="#2e8555" class="target-color"/><path d="M247.87,191h-4.08a1,1,0,0,1,0-2h4.08a1,1,0,0,1,0,2Z" fill="#fff"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300" data-imageid="html-d" imageName="Javascript" class="illustrations_image" style="width: 261px;"><g id="_181_javascript_outline" data-name="#181_javascript_outline"><rect x="144.72" y="69.25" width="130.1" height="153.64" rx="10.96" fill="#2e8555" class="target-color"/><path d="M263.86,223.89H155.67a12,12,0,0,1-11.95-11.95V80.21a12,12,0,0,1,11.95-12H263.86a12,12,0,0,1,12,12V211.94A12,12,0,0,1,263.86,223.89ZM155.67,70.25a10,10,0,0,0-9.95,10V211.94a10,10,0,0,0,9.95,9.95H263.86a10,10,0,0,0,10-9.95V80.21a10,10,0,0,0-10-10Z" fill="#093f68"/><path d="M257.86,230.75H141.93a7.08,7.08,0,0,1-7.08-7.08V84.75a7.08,7.08,0,0,1,7.08-7.08h93.81l29.2,27.19V223.67A7.08,7.08,0,0,1,257.86,230.75Z" fill="#fff"/><path d="M257.86,231.75H141.93a8.09,8.09,0,0,1-8.08-8.08V84.75a8.09,8.09,0,0,1,8.08-8.08h93.81a1,1,0,0,1,.68.27l29.21,27.19a1,1,0,0,1,.31.73V223.67A8.09,8.09,0,0,1,257.86,231.75ZM141.93,78.67a6.09,6.09,0,0,0-6.08,6.08V223.67a6.09,6.09,0,0,0,6.08,6.08H257.86a6.08,6.08,0,0,0,6.08-6.08V105.3l-28.6-26.63Z" fill="#093f68"/><path d="M235.74,77.67V99.06a5.46,5.46,0,0,0,5.39,5.47l23.81.33Z" fill="#2e8555" class="target-color"/><path d="M264.94,105.86h0l-23.81-.33a6.49,6.49,0,0,1-6.38-6.47V77.67a1,1,0,0,1,1.68-.73l29.21,27.19a1,1,0,0,1,.24,1.1A1,1,0,0,1,264.94,105.86ZM236.74,80V99.06a4.47,4.47,0,0,0,4.4,4.47l21.22.29Z" fill="#093f68"/><path d="M182.76,164.08,164,172.64v.14l18.78,8.56v9.39l-30-13.67v-8.7l30-13.68Z" fill="#093f68"/><path d="M182.76,191.73a.85.85,0,0,1-.41-.09l-30-13.67a1,1,0,0,1-.58-.91v-8.7a1,1,0,0,1,.58-.91l30-13.68a1,1,0,0,1,1.41.91v9.4a1,1,0,0,1-.58.91l-17,7.72,17,7.72a1,1,0,0,1,.58.91v9.39a1,1,0,0,1-.46.85A1,1,0,0,1,182.76,191.73Zm-29-15.31,28,12.76V182l-18.2-8.29a1,1,0,0,1-.58-.91v-.14a1,1,0,0,1,.58-.91l18.2-8.3v-7.19l-28,12.76Zm29-12.34h0Z" fill="#093f68"/><path d="M205,141.08l6.76,2.41L194,197.3l-6.7-2.42Z" fill="#093f68"/><path d="M194,198.3a1,1,0,0,1-.34-.06l-6.7-2.42a1,1,0,0,1-.61-1.25l17.72-53.81a1,1,0,0,1,.51-.58,1,1,0,0,1,.78,0l6.75,2.41a1,1,0,0,1,.62,1.26L195,197.61a1,1,0,0,1-.51.59A1.13,1.13,0,0,1,194,198.3Zm-5.44-4,4.81,1.74,17.15-51.9-4.87-1.75Z" fill="#093f68"/><path d="M216.36,181.34l18.78-8.56v-.14l-18.78-8.56v-9.4l30,13.68v8.7l-30,13.67Z" fill="#093f68"/><path d="M216.36,191.73a1,1,0,0,1-.54-.15,1,1,0,0,1-.46-.85v-9.39a1,1,0,0,1,.58-.91l16.94-7.72L215.94,165a1,1,0,0,1-.58-.91v-9.4a1,1,0,0,1,1.41-.91l30,13.68a1,1,0,0,1,.58.91v8.7a1,1,0,0,1-.58.91l-30,13.67A.85.85,0,0,1,216.36,191.73Zm1-9.75v7.2l28-12.76V169l-28-12.76v7.19l18.2,8.3a1,1,0,0,1,.58.91v.14a1,1,0,0,1-.58.91Z" fill="#093f68"/><rect x="120.98" y="92.53" width="71.35" height="44.25" rx="2.13" fill="#2e8555" class="target-color"/><path d="M190.2,137.78H123.11a3.13,3.13,0,0,1-3.13-3.12v-40a3.14,3.14,0,0,1,3.13-3.13H190.2a3.13,3.13,0,0,1,3.13,3.13v40A3.13,3.13,0,0,1,190.2,137.78ZM123.11,93.53A1.14,1.14,0,0,0,122,94.66v40a1.13,1.13,0,0,0,1.13,1.12H190.2a1.12,1.12,0,0,0,1.13-1.12v-40a1.13,1.13,0,0,0-1.13-1.13Z" fill="#093f68"/><path d="M151.53,122.88a7.29,7.29,0,0,1-1.07,2.59,6,6,0,0,1-2.18,2,7.46,7.46,0,0,1-3.61.77,7.3,7.3,0,0,1-4.6-1.43,6.78,6.78,0,0,1-2.46-3.67l4.64-1.56a2.7,2.7,0,0,0,.78,1.38,2,2,0,0,0,1.4.52,1.69,1.69,0,0,0,1.7-.89,5.49,5.49,0,0,0,.42-2.33V105.87h5.28v14.3A13.07,13.07,0,0,1,151.53,122.88Z" fill="#093f68"/><path d="M167.67,111.52a4.69,4.69,0,0,0-1.77-1.37,5,5,0,0,0-2.1-.51,6.44,6.44,0,0,0-1.06.1,2.81,2.81,0,0,0-1,.35,2.31,2.31,0,0,0-.72.67,1.84,1.84,0,0,0-.29,1.06,1.78,1.78,0,0,0,.23.93,2.06,2.06,0,0,0,.67.64,7,7,0,0,0,1.06.51c.41.15.87.31,1.39.47.73.25,1.5.52,2.3.82a8.49,8.49,0,0,1,2.18,1.18,6.3,6.3,0,0,1,1.62,1.82,5.29,5.29,0,0,1,.65,2.72,7.1,7.1,0,0,1-.69,3.24,6.51,6.51,0,0,1-1.86,2.26,8.14,8.14,0,0,1-2.67,1.32,11.49,11.49,0,0,1-3.1.43,12.59,12.59,0,0,1-4.51-.82A9.59,9.59,0,0,1,154.4,125l3.44-3.5a6.32,6.32,0,0,0,2.11,1.64,5.66,5.66,0,0,0,2.59.66,5.38,5.38,0,0,0,1.14-.12,3.08,3.08,0,0,0,1-.4,2,2,0,0,0,.66-.74,2.28,2.28,0,0,0,.25-1.11,1.71,1.71,0,0,0-.31-1,2.9,2.9,0,0,0-.87-.78A7,7,0,0,0,163,119l-1.92-.63a19.51,19.51,0,0,1-2-.79,7,7,0,0,1-1.78-1.19,5.49,5.49,0,0,1-1.26-1.75,6,6,0,0,1-.47-2.5,6.16,6.16,0,0,1,.73-3.1,6.57,6.57,0,0,1,1.94-2.12,8.49,8.49,0,0,1,2.7-1.21,12.06,12.06,0,0,1,3-.38,11.27,11.27,0,0,1,3.76.67,9.66,9.66,0,0,1,3.36,2Z" fill="#093f68"/><path d="M191.53,101.63H121a1,1,0,0,1,0-2h70.55a1,1,0,0,1,0,2Z" fill="#093f68"/><path d="M131.06,98.11h-5.12a1,1,0,0,1,0-2h5.12a1,1,0,0,1,0,2Z" fill="#093f68"/><path d="M257.68,212.94h-113a1,1,0,1,1,0-2h113a1,1,0,0,1,0,2Z" fill="#dfeaef"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -1,171 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
|
|
||||||
<title>Easy to Use</title>
|
|
||||||
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
|
|
||||||
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
|
|
||||||
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
|
|
||||||
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
|
|
||||||
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
|
||||||
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
|
|
||||||
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
|
||||||
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
|
|
||||||
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
|
|
||||||
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
|
|
||||||
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
|
|
||||||
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
|
|
||||||
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
|
|
||||||
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
|
|
||||||
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
|
|
||||||
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
|
|
||||||
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
|
||||||
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
|
|
||||||
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
|
||||||
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
|
|
||||||
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
|
|
||||||
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
|
||||||
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
|
||||||
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
|
||||||
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
|
||||||
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
|
||||||
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
|
||||||
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
|
||||||
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
|
||||||
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
|
||||||
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
|
||||||
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
|
||||||
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
|
||||||
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
|
||||||
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
|
||||||
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
|
||||||
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
|
||||||
</g>
|
|
||||||
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
|
|
||||||
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
|
|
||||||
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
|
|
||||||
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
|
|
||||||
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
|
|
||||||
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
|
|
||||||
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
|
|
||||||
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
|
|
||||||
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
|
|
||||||
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
</g>
|
|
||||||
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,170 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
|
|
||||||
<title>Powered by React</title>
|
|
||||||
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
|
|
||||||
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
|
|
||||||
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
|
|
||||||
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
|
|
||||||
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
|
|
||||||
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
|
||||||
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
|
|
||||||
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
|
|
||||||
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
|
|
||||||
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
|
||||||
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
|
|
||||||
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
|
|
||||||
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
|
||||||
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
|
|
||||||
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
|
||||||
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
|
|
||||||
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
|
|
||||||
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
|
|
||||||
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
|
|
||||||
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
|
|
||||||
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
|
||||||
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
|
|
||||||
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
|
|
||||||
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
|
|
||||||
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
|
||||||
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
|
|
||||||
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
|
|
||||||
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
|
||||||
</g>
|
|
||||||
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
|
|
||||||
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
|
|
||||||
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
|
|
||||||
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
|
|
||||||
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
|
|
||||||
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
|
|
||||||
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
|
|
||||||
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
|
|
||||||
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
</g>
|
|
||||||
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
|
|
||||||
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
|
||||||
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
|
|
||||||
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
|
|
||||||
</g>
|
|
||||||
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
|
|
||||||
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
|
||||||
</g>
|
|
||||||
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
|
|
||||||
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
|
|
||||||
</g>
|
|
||||||
<g id="React-icon" transform="translate(906.3 541.56)">
|
|
||||||
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
|
|
||||||
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
|
|
||||||
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
|
|
||||||
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,40 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
|
|
||||||
<title>Focus on What Matters</title>
|
|
||||||
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
|
|
||||||
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
|
|
||||||
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
|
|
||||||
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
|
|
||||||
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
|
|
||||||
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
|
|
||||||
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
|
|
||||||
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
|
|
||||||
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
|
|
||||||
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
|
|
||||||
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
|
|
||||||
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
|
|
||||||
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
|
||||||
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
|
||||||
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
|
||||||
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
|
||||||
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
|
|
||||||
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
|
|
||||||
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
|
|
||||||
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
|
|
||||||
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
|
||||||
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
|
|
||||||
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
|
|
||||||
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
|
||||||
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
|
||||||
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
|
||||||
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
|
||||||
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
|
||||||
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
|
||||||
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
|
|
||||||
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
|
|
||||||
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
|
|
||||||
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
|
||||||
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
|
||||||
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
|
||||||
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
|
||||||
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
rules: {
|
||||||
|
'scope-case': [0],
|
||||||
|
'subject-case': [0],
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -21,7 +21,8 @@
|
|||||||
"ver": "lerna version --no-private --conventional-commits",
|
"ver": "lerna version --no-private --conventional-commits",
|
||||||
"release": "yarn eslint && yarn build && yarn run ver && lerna publish from-git",
|
"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",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.19.3",
|
"@babel/preset-env": "^7.19.3",
|
||||||
@@ -49,11 +50,11 @@
|
|||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"http-server": "^14.1.0",
|
"http-server": "^14.1.0",
|
||||||
"husky": "^8.0.1",
|
"husky": "^8.0.3",
|
||||||
"jest": "^29.1.1",
|
"jest": "^29.1.1",
|
||||||
"jest-environment-jsdom": "^29.1.1",
|
"jest-environment-jsdom": "^29.1.1",
|
||||||
"lerna": "^6.6.1",
|
"lerna": "^6.6.1",
|
||||||
"lint-staged": "^13.1.0",
|
"lint-staged": "^14.0.1",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-dom": "^17.0.0",
|
"react-dom": "^17.0.0",
|
||||||
@@ -63,5 +64,11 @@
|
|||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@yarnpkg/parsers": "3.0.0-rc.48.1"
|
"@yarnpkg/parsers": "3.0.0-rc.48.1"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,jsx,ts,tsx}": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,30 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.2.3](https://github.com/netease/tango/compare/@music163/tango-context@0.2.2...@music163/tango-context@0.2.3) (2023-10-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @music163/tango-context
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
- support multiple service modules & refactor workspace ([a7df29c](https://github.com/netease/tango/commit/a7df29c3debc56b187792d3e203b470e9d368ea5))
|
||||||
|
|
||||||
|
## [0.1.4](https://github.com/netease/tango/compare/@music163/tango-context@0.1.3...@music163/tango-context@0.1.4) (2023-09-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @music163/tango-context
|
||||||
|
|
||||||
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-context@0.1.2...@music163/tango-context@0.1.3) (2023-09-13)
|
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-context@0.1.2...@music163/tango-context@0.1.3) (2023-09-13)
|
||||||
|
|
||||||
**Note:** Version bump only for package @music163/tango-context
|
**Note:** Version bump only for package @music163/tango-context
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@music163/tango-context",
|
"name": "@music163/tango-context",
|
||||||
"version": "0.1.3",
|
"version": "0.2.3",
|
||||||
"description": "react context for tango-apps",
|
"description": "react context for tango-apps",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
"prepublishOnly": "yarn build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16.8.0"
|
"react": ">= 16.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@music163/tango-core": "^0.1.3",
|
"@music163/tango-core": "^0.2.2",
|
||||||
"@music163/tango-helpers": "^0.1.2",
|
"@music163/tango-helpers": "^0.1.4",
|
||||||
"mobx-react-lite": "4.0.2"
|
"mobx-react-lite": "4.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import type { Engine } from '@music163/tango-core';
|
import type { Engine } from '@music163/tango-core';
|
||||||
import { createContext } from '@music163/tango-helpers';
|
import { createContext } from '@music163/tango-helpers';
|
||||||
|
|
||||||
|
interface CustomVariableData {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
children?: CustomVariableData[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ITangoEngineContext {
|
export interface ITangoEngineContext {
|
||||||
/**
|
/**
|
||||||
* 低代码引擎
|
* 低代码引擎
|
||||||
*/
|
*/
|
||||||
engine: Engine;
|
engine: Engine;
|
||||||
|
/**
|
||||||
|
* 自定义配置数据
|
||||||
|
*/
|
||||||
|
config: {
|
||||||
|
customActionVariables: CustomVariableData[];
|
||||||
|
customExpressionVariables: CustomVariableData[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const [TangoEngineProvider, useTangoEngine] = createContext<ITangoEngineContext>({
|
const [TangoEngineProvider, useTangoEngine] = createContext<ITangoEngineContext>({
|
||||||
@@ -22,38 +36,15 @@ export const useDesigner = () => {
|
|||||||
return useTangoEngine()?.engine.designer;
|
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 = () => {
|
export const useWorkspaceData = () => {
|
||||||
|
const ctx = useTangoEngine();
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const modelVariables: any[] = []; // 绑定变量列表
|
const modelVariables: any[] = []; // 绑定变量列表
|
||||||
const storeActionVariables: any[] = []; // 模型中的所有 actions
|
const storeActionVariables: any[] = []; // 模型中的所有 actions
|
||||||
const storeVariables: any[] = []; // 模型中的所有变量
|
const storeVariables: any[] = []; // 模型中的所有变量
|
||||||
const serviceVariables: any[] = []; // 服务中的所有变量
|
const serviceVariables: any[] = []; // 服务中的所有变量
|
||||||
|
|
||||||
workspace.listStoreModules?.().forEach((file) => {
|
Object.values(workspace.storeModules).forEach((file) => {
|
||||||
const prefix = `stores.${file.name}`;
|
const prefix = `stores.${file.name}`;
|
||||||
const states = file.states.map((item) => ({
|
const states = file.states.map((item) => ({
|
||||||
title: item.name,
|
title: item.name,
|
||||||
@@ -91,11 +82,17 @@ export const useWorkspaceData = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(workspace.serviceModule?.serviceFunctions || {}).forEach((key) => {
|
Object.values(workspace.serviceModules).forEach((file) => {
|
||||||
|
const prefix = file.name !== 'index' ? `services.${file.name}` : 'services';
|
||||||
serviceVariables.push({
|
serviceVariables.push({
|
||||||
title: key,
|
title: file.name,
|
||||||
key: `services.${key}`,
|
key: prefix,
|
||||||
type: 'function',
|
selectable: false,
|
||||||
|
children: Object.keys(file.serviceFunctions || {}).map((key) => ({
|
||||||
|
title: key,
|
||||||
|
key: [prefix, key].join('.'),
|
||||||
|
type: 'function',
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -105,19 +102,29 @@ export const useWorkspaceData = () => {
|
|||||||
value: item.path,
|
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 {
|
return {
|
||||||
modelVariables: [buildVariableOptions('数据模型', 'stores', modelVariables)],
|
modelVariables: [buildVariableOptions('数据模型', 'stores', modelVariables)],
|
||||||
actionVariables: [
|
actionVariables,
|
||||||
buildVariableOptions('数据模型', 'stores', storeActionVariables),
|
|
||||||
buildVariableOptions('服务函数', 'services', serviceVariables),
|
|
||||||
buildVariableOptions('工具函数', 'helpers', builtinHelpers),
|
|
||||||
],
|
|
||||||
storeVariables,
|
storeVariables,
|
||||||
expressionVariables: [
|
serviceVariables,
|
||||||
buildVariableOptions('数据模型', 'stores', storeVariables),
|
expressionVariables,
|
||||||
buildVariableOptions('服务函数', 'services', serviceVariables),
|
|
||||||
buildVariableOptions('工具函数', 'helpers', builtinHelpers),
|
|
||||||
],
|
|
||||||
routeOptions,
|
routeOptions,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,30 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.2.2](https://github.com/netease/tango/compare/@music163/tango-core@0.2.1...@music163/tango-core@0.2.2) (2023-10-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- add listImportSources to viewModule ([6bbbe63](https://github.com/netease/tango/commit/6bbbe634ad52acaf06a0d776aeff450993f7b69a))
|
||||||
|
- enhance expSetter ([#43](https://github.com/netease/tango/issues/43)) ([5ebbb42](https://github.com/netease/tango/commit/5ebbb428fb3fb786d330ab01959028443338d315))
|
||||||
|
- update interface ([0b3e632](https://github.com/netease/tango/commit/0b3e632745e30d578268458329d5702dbd729010))
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
- support multiple service modules & refactor workspace ([a7df29c](https://github.com/netease/tango/commit/a7df29c3debc56b187792d3e203b470e9d368ea5))
|
||||||
|
|
||||||
|
## [0.1.4](https://github.com/netease/tango/compare/@music163/tango-core@0.1.3...@music163/tango-core@0.1.4) (2023-09-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @music163/tango-core
|
||||||
|
|
||||||
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-core@0.1.2...@music163/tango-core@0.1.3) (2023-09-13)
|
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-core@0.1.2...@music163/tango-core@0.1.3) (2023-09-13)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@music163/tango-core",
|
"name": "@music163/tango-core",
|
||||||
"version": "0.1.3",
|
"version": "0.2.2",
|
||||||
"description": "tango core",
|
"description": "tango core",
|
||||||
"author": "wwsun <ww.sun@outlook.com>",
|
"author": "wwsun <ww.sun@outlook.com>",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
@@ -28,12 +28,15 @@
|
|||||||
"@babel/parser": "^7.22.15",
|
"@babel/parser": "^7.22.15",
|
||||||
"@babel/traverse": "^7.22.15",
|
"@babel/traverse": "^7.22.15",
|
||||||
"@babel/types": "^7.22.15",
|
"@babel/types": "^7.22.15",
|
||||||
"@music163/tango-helpers": "^0.1.2",
|
"@music163/tango-helpers": "^0.1.4",
|
||||||
"@types/babel__generator": "^7.6.4",
|
"@types/babel__generator": "^7.6.4",
|
||||||
"@types/babel__traverse": "^7.18.3",
|
"@types/babel__traverse": "^7.18.3",
|
||||||
"mobx": "6.9.0",
|
"mobx": "6.9.0",
|
||||||
"path-browserify": "^1.0.1"
|
"path-browserify": "^1.0.1"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"mobx": "6.9.0"
|
||||||
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://registry.npmjs.org/"
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
|||||||
@@ -11,16 +11,9 @@ import {
|
|||||||
StringOrNumber,
|
StringOrNumber,
|
||||||
Dict,
|
Dict,
|
||||||
parseDndId,
|
parseDndId,
|
||||||
upperCamelCase
|
upperCamelCase,
|
||||||
} from '@music163/tango-helpers';
|
} from '@music163/tango-helpers';
|
||||||
import {
|
import { keyNode2value, jsxAttributeValueNode2value, node2value, node2code } from './generate';
|
||||||
keyNode2value,
|
|
||||||
jsxAttributeValueNode2value,
|
|
||||||
node2value,
|
|
||||||
ast2code,
|
|
||||||
expression2code,
|
|
||||||
node2code,
|
|
||||||
} from './generate';
|
|
||||||
import {
|
import {
|
||||||
value2jsxAttributeValueNode,
|
value2jsxAttributeValueNode,
|
||||||
value2jsxChildrenValueNode,
|
value2jsxChildrenValueNode,
|
||||||
@@ -959,42 +952,22 @@ export function updateServiceConfigToServiceFile(
|
|||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
function visitObjectProperties(
|
export function updateBaseConfigToServiceFile(ast: t.File, config: object) {
|
||||||
node: t.ObjectExpression,
|
|
||||||
visitor: (propName: string, node: t.ObjectProperty) => void,
|
|
||||||
) {
|
|
||||||
for (const propNode of node.properties) {
|
|
||||||
if (t.isObjectProperty(propNode)) {
|
|
||||||
const propName = keyNode2value(propNode.key);
|
|
||||||
visitor?.(propName as string, propNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateBaseConfigToServiceFile(ast: t.File, configName: string, configValue: any) {
|
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
CallExpression(path) {
|
CallExpression(path) {
|
||||||
const calleeName = keyNode2value(path.node.callee) as string;
|
const calleeName = keyNode2value(path.node.callee) as string;
|
||||||
if (isDefineService(calleeName)) {
|
if (isDefineService(calleeName)) {
|
||||||
switch (path.node.arguments.length) {
|
switch (path.node.arguments.length) {
|
||||||
case 1:
|
case 1:
|
||||||
path.node.arguments.push(value2node({ [configName]: configValue }));
|
// 没有 baseConfig,直接创建新的参数即可
|
||||||
|
path.node.arguments.push(value2node(config));
|
||||||
break;
|
break;
|
||||||
case 2: {
|
case 2: {
|
||||||
|
// 已存在 baseConfig,需要进行参数的合并
|
||||||
const baseConfigNode = path.node.arguments[1];
|
const baseConfigNode = path.node.arguments[1];
|
||||||
if (t.isObjectExpression(baseConfigNode)) {
|
if (t.isObjectExpression(baseConfigNode)) {
|
||||||
let found = false;
|
const baseConfig = node2value(baseConfigNode);
|
||||||
visitObjectProperties(baseConfigNode, (propName, propNode) => {
|
path.node.arguments[1] = value2node({ ...baseConfig, ...config });
|
||||||
if (propName === configName) {
|
|
||||||
found = true;
|
|
||||||
propNode.value = value2node(configValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!found) {
|
|
||||||
baseConfigNode.properties.push(
|
|
||||||
t.objectProperty(t.identifier(configName), value2node(configValue)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1022,6 +995,7 @@ export function traverseServiceFile(ast: t.File) {
|
|||||||
if (path.node.arguments[1]) {
|
if (path.node.arguments[1]) {
|
||||||
const config = node2value(path.node.arguments[1]);
|
const config = node2value(path.node.arguments[1]);
|
||||||
baseConfig.encryptFetch = !!config.encryptFetch;
|
baseConfig.encryptFetch = !!config.encryptFetch;
|
||||||
|
baseConfig.namespace = config.namespace || 'index';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path.stop();
|
path.stop();
|
||||||
@@ -1134,9 +1108,6 @@ export function cloneJSXElementWithoutTrackingData(node: t.JSXElement) {
|
|||||||
|
|
||||||
export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
||||||
const importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]> = {};
|
const importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]> = {};
|
||||||
const classProperties: Dict<IClassPropertyNodeData> = {};
|
|
||||||
let stateProperties: string[] = [];
|
|
||||||
let stateCode = '';
|
|
||||||
const nodes: Array<ITangoViewNodeData<t.JSXElement>> = [];
|
const nodes: Array<ITangoViewNodeData<t.JSXElement>> = [];
|
||||||
const cloneAst = t.cloneNode(ast, true, true);
|
const cloneAst = t.cloneNode(ast, true, true);
|
||||||
const cleanAst = removeTrackingDataFromViewAst(cloneAst);
|
const cleanAst = removeTrackingDataFromViewAst(cloneAst);
|
||||||
@@ -1166,30 +1137,6 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
ClassProperty(path) {
|
|
||||||
const propName = keyNode2value(path.node.key) as string;
|
|
||||||
if (!propName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reference = `this.${propName}`;
|
|
||||||
if (['defaultProps', 'propTypes', 'constructor'].includes(propName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propName === 'state') {
|
|
||||||
stateProperties = Object.keys(node2value(path.node.value));
|
|
||||||
stateCode = ast2code(path.node);
|
|
||||||
} else {
|
|
||||||
// TODO: 这里需要完善下,并不是所有的都是 arrowFunction
|
|
||||||
classProperties[reference] = {
|
|
||||||
reference,
|
|
||||||
propertyName: propName,
|
|
||||||
propertyBody: expression2code(path.node.value),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
JSXElement(path) {
|
JSXElement(path) {
|
||||||
const attributes = getJSXElementAttributes(path.node);
|
const attributes = getJSXElementAttributes(path.node);
|
||||||
|
|
||||||
@@ -1236,9 +1183,6 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
|||||||
ast,
|
ast,
|
||||||
cleanAst,
|
cleanAst,
|
||||||
nodes,
|
nodes,
|
||||||
stateCode,
|
|
||||||
classProperties,
|
|
||||||
stateProperties,
|
|
||||||
importedModules,
|
importedModules,
|
||||||
variables,
|
variables,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { getVariableContent, isVariableString } from '@music163/tango-helpers';
|
||||||
|
import { value2node, expression2code } from './ast';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 js value 转换为代码字符串
|
||||||
|
*/
|
||||||
|
export function value2code(value: any) {
|
||||||
|
const node = value2node(value);
|
||||||
|
const code = expression2code(node);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是字符串代码
|
||||||
|
* @param code
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function isStringCode(code: string) {
|
||||||
|
return /^".*"$/.test(code?.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* js value 转为表达式代码
|
||||||
|
* @example 1 => 1
|
||||||
|
* @example hello => "hello"
|
||||||
|
* @example { foo: bar } => {{ foo: bar }}
|
||||||
|
* @example [1,2,3] => {[1,2,3]}
|
||||||
|
*
|
||||||
|
* @param val js value
|
||||||
|
* @returns 表达式代码
|
||||||
|
*/
|
||||||
|
export function value2expressionCode(val: any) {
|
||||||
|
if (!val) return '';
|
||||||
|
|
||||||
|
let ret;
|
||||||
|
|
||||||
|
switch (typeof val) {
|
||||||
|
case 'string': {
|
||||||
|
if (isVariableString(val)) {
|
||||||
|
ret = getVariableContent(val);
|
||||||
|
} else if (isStringCode(val)) {
|
||||||
|
ret = val;
|
||||||
|
} else {
|
||||||
|
ret = `"${val}"`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'number':
|
||||||
|
ret = String(val);
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
ret = value2code(val);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './ast';
|
export * from './ast';
|
||||||
export * from './assert';
|
export * from './assert';
|
||||||
|
export * from './code-helpers';
|
||||||
export * from './string';
|
export * from './string';
|
||||||
export * from './object';
|
export * from './object';
|
||||||
export * from './prototype';
|
export * from './prototype';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IImportDeclarationPayload } from '../types';
|
import { Dict } from '@music163/tango-helpers';
|
||||||
|
import { IImportDeclarationPayload, IImportSpecifierData } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入列表解析为导入声明对象
|
* 导入列表解析为导入声明对象
|
||||||
@@ -6,15 +7,12 @@ import { IImportDeclarationPayload } from '../types';
|
|||||||
* @param nameMap
|
* @param nameMap
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function namesToImportDeclarations(
|
export function namesToImportDeclarations(names: string[], nameMap: Dict<IImportSpecifierData>) {
|
||||||
names: string[],
|
|
||||||
nameMap: Record<string, { package: string; isDefault?: boolean }>,
|
|
||||||
) {
|
|
||||||
const map = {};
|
const map = {};
|
||||||
names.forEach((name) => {
|
names.forEach((name) => {
|
||||||
const mod = nameMap[name];
|
const mod = nameMap[name];
|
||||||
if (mod) {
|
if (mod) {
|
||||||
updateMod(map, mod.package, name, mod.isDefault, !map[mod.package]);
|
updateMod(map, mod.source, name, mod.isDefault, !map[mod.source]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Object.keys(map).map((sourcePath) => ({
|
return Object.keys(map).map((sourcePath) => ({
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { camelCase, upperCamelCase } from '@music163/tango-helpers';
|
import { camelCase, upperCamelCase } from '@music163/tango-helpers';
|
||||||
import { value2node, expression2code } from './ast';
|
|
||||||
import { FileType } from './../types';
|
import { FileType } from './../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +34,7 @@ export function inferFileType(filename: string): FileType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: 暂时写死,后续要支持多 services 文件解析
|
// FIXME: 暂时写死,后续要支持多 services 文件解析
|
||||||
if (/\/services\/index.js$/.test(filename)) {
|
if (/\/services\/.+\.js$/.test(filename)) {
|
||||||
return FileType.ServiceModule;
|
return FileType.ServiceModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,15 +140,6 @@ export function formatCode(code: string, parser = 'babel') {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 js value 转换为代码字符串
|
|
||||||
*/
|
|
||||||
export function value2code(value: any) {
|
|
||||||
const node = value2node(value);
|
|
||||||
const code = expression2code(node);
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否匹配路由
|
* 是否匹配路由
|
||||||
* @example isPathnameMatchRoute('/user/123', '/users/:id') -> true
|
* @example isPathnameMatchRoute('/user/123', '/users/:id') -> true
|
||||||
|
|||||||
@@ -7,5 +7,9 @@ export * from './drag-source';
|
|||||||
export * from './history';
|
export * from './history';
|
||||||
export * from './file';
|
export * from './file';
|
||||||
export * from './module';
|
export * from './module';
|
||||||
|
export * from './route-module';
|
||||||
|
export * from './service-module';
|
||||||
|
export * from './store-module';
|
||||||
|
export * from './view-module';
|
||||||
export * from './node';
|
export * from './node';
|
||||||
export * from './interfaces';
|
export * from './interfaces';
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import {
|
|||||||
InsertChildPositionType,
|
InsertChildPositionType,
|
||||||
ITangoConfigPackages,
|
ITangoConfigPackages,
|
||||||
IPageConfigData,
|
IPageConfigData,
|
||||||
IProjectData,
|
|
||||||
IServiceFunctionPayload,
|
IServiceFunctionPayload,
|
||||||
|
IImportSpecifierData,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { TangoFile, TangoJsonFile } from './file';
|
import { TangoFile, TangoJsonFile } from './file';
|
||||||
import { TangoRouteModule, TangoServiceModule, TangoStoreModule } from './module';
|
import { TangoRouteModule } from './route-module';
|
||||||
|
import { TangoStoreModule } from './store-module';
|
||||||
|
import { TangoServiceModule } from './service-module';
|
||||||
|
|
||||||
export interface IViewFile {
|
export interface IViewFile {
|
||||||
readonly workspace: IWorkspace;
|
readonly workspace: IWorkspace;
|
||||||
@@ -23,7 +25,11 @@ export interface IViewFile {
|
|||||||
/**
|
/**
|
||||||
* 通过导入组件名查找组件来自的包
|
* 通过导入组件名查找组件来自的包
|
||||||
*/
|
*/
|
||||||
importMap?: Dict<{ package: string; isDefault?: boolean }>;
|
importMap?: Dict<IImportSpecifierData>;
|
||||||
|
|
||||||
|
listImportSources?: () => string[];
|
||||||
|
listModals?: () => Array<{ label: string; value: string }>;
|
||||||
|
listForms?: () => Record<string, string[]>;
|
||||||
|
|
||||||
update: (code?: string, isFormatCode?: boolean, refreshWorkspace?: boolean) => void;
|
update: (code?: string, isFormatCode?: boolean, refreshWorkspace?: boolean) => void;
|
||||||
|
|
||||||
@@ -139,7 +145,8 @@ export interface IWorkspace {
|
|||||||
|
|
||||||
tangoConfigJson: TangoJsonFile;
|
tangoConfigJson: TangoJsonFile;
|
||||||
routeModule?: TangoRouteModule;
|
routeModule?: TangoRouteModule;
|
||||||
serviceModule?: TangoServiceModule;
|
storeModules?: Record<string, TangoStoreModule>;
|
||||||
|
serviceModules?: Record<string, TangoServiceModule>;
|
||||||
|
|
||||||
refresh: (names: string[]) => void;
|
refresh: (names: string[]) => void;
|
||||||
ready: () => void;
|
ready: () => void;
|
||||||
@@ -150,11 +157,6 @@ export interface IWorkspace {
|
|||||||
setComponentPrototypes: (prototypes: Record<string, ComponentPrototypeType>) => void;
|
setComponentPrototypes: (prototypes: Record<string, ComponentPrototypeType>) => void;
|
||||||
getPrototype: (name: string | ComponentPrototypeType) => ComponentPrototypeType;
|
getPrototype: (name: string | ComponentPrototypeType) => ComponentPrototypeType;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取项目数据
|
|
||||||
*/
|
|
||||||
getProjectData?: () => IProjectData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询节点
|
* 查询节点
|
||||||
* @param id 节点 ID
|
* @param id 节点 ID
|
||||||
@@ -162,72 +164,55 @@ export interface IWorkspace {
|
|||||||
* @returns 返回节点对象
|
* @returns 返回节点对象
|
||||||
*/
|
*/
|
||||||
getNode: (id: string, module?: string) => IViewNode;
|
getNode: (id: string, module?: string) => IViewNode;
|
||||||
listModals?: () => Array<{ label: string; value: string }>;
|
|
||||||
listForms?: () => Record<string, string[]>;
|
|
||||||
|
|
||||||
|
getFile: (filename: string) => TangoFile;
|
||||||
|
listFiles: () => Record<string, string>;
|
||||||
addFile: (filename: string, code: string, fileType?: FileType) => void;
|
addFile: (filename: string, code: string, fileType?: FileType) => void;
|
||||||
addFiles: (files: IFileConfig[]) => void;
|
addFiles: (files: IFileConfig[]) => void;
|
||||||
updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void;
|
updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void;
|
||||||
removeFile: (filename: string) => void;
|
removeFile: (filename: string) => void;
|
||||||
renameFile: (oldFilename: string, newFilename: string) => void;
|
renameFile: (oldFilename: string, newFilename: string) => void;
|
||||||
getFile: (filename: string) => TangoFile;
|
renameFolder: (oldFoldername: string, newFoldername: string) => void;
|
||||||
listFiles: () => Record<string, string>;
|
|
||||||
|
|
||||||
addViewPage: (name: string, code: string) => void;
|
|
||||||
|
|
||||||
removeSelectedNode: () => void;
|
removeSelectedNode: () => void;
|
||||||
|
|
||||||
cloneSelectedNode: () => void;
|
cloneSelectedNode: () => void;
|
||||||
|
|
||||||
copySelectedNode: () => void;
|
copySelectedNode: () => void;
|
||||||
|
|
||||||
pasteSelectedNode: () => void;
|
pasteSelectedNode: () => void;
|
||||||
|
|
||||||
insertToSelectedNode: (childNameOrPrototype: string | ComponentPrototypeType) => void;
|
insertToSelectedNode: (childNameOrPrototype: string | ComponentPrototypeType) => void;
|
||||||
|
|
||||||
dropNode: () => void;
|
dropNode: () => void;
|
||||||
|
|
||||||
insertToNode: (
|
insertToNode: (
|
||||||
targetNodeId: string,
|
targetNodeId: string,
|
||||||
sourceNameOrPrototype: string | ComponentPrototypeType,
|
sourceNameOrPrototype: string | ComponentPrototypeType,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
replaceNode: (
|
replaceNode: (
|
||||||
targetNodeId: string,
|
targetNodeId: string,
|
||||||
sourceNameOrPrototype: string | ComponentPrototypeType,
|
sourceNameOrPrototype: string | ComponentPrototypeType,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
updateSelectedNodeAttributes: (
|
updateSelectedNodeAttributes: (
|
||||||
attributes: Record<string, any>,
|
attributes: Record<string, any>,
|
||||||
relatedImports?: string[],
|
relatedImports?: string[],
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
addBlock?: (files: object, name: string) => void;
|
getServiceFunction?: (serviceKey: string) => object;
|
||||||
|
listServiceFunctions?: () => Record<string, object>;
|
||||||
generateBlockFilesBySelectedNode?: () => Record<string, string>;
|
removeServiceFunction?: (serviceName: string, modName?: string) => void;
|
||||||
|
addServiceFunction?: (
|
||||||
removeServiceFunction?: (serviceName: string) => void;
|
payload: IServiceFunctionPayload | IServiceFunctionPayload[],
|
||||||
|
modName?: string,
|
||||||
addServiceFunction?: (payload: IServiceFunctionPayload | IServiceFunctionPayload[]) => void;
|
) => void;
|
||||||
|
updateServiceFunction?: (payload: IServiceFunctionPayload, modName?: string) => void;
|
||||||
updateServiceFunction?: (payload: any) => void;
|
updateServiceBaseConfig?: (IServiceFunctionPayload: object, modName?: string) => void;
|
||||||
|
|
||||||
updateServiceBaseConfig?: (name: string, value: any) => void;
|
|
||||||
|
|
||||||
listStoreModules?: () => TangoStoreModule[];
|
|
||||||
|
|
||||||
addStoreModule?: (storeName: string, code: string) => void;
|
addStoreModule?: (storeName: string, code: string) => void;
|
||||||
|
|
||||||
removeStoreModule?: (storeName: string) => void;
|
removeStoreModule?: (storeName: string) => void;
|
||||||
|
|
||||||
addStoreState?: (storeName: string, stateName: string, initValue: string) => void;
|
addStoreState?: (storeName: string, stateName: string, initValue: string) => void;
|
||||||
|
|
||||||
removeStoreState?: (storeName: string, stateName: string) => void;
|
removeStoreState?: (storeName: string, stateName: string) => void;
|
||||||
|
|
||||||
updateModuleCodeByVariablePath?: (variablePath: string, code: string) => void;
|
updateModuleCodeByVariablePath?: (variablePath: string, code: string) => void;
|
||||||
|
|
||||||
|
// TODO: 是否需要,和 addFile 有啥区别 ???
|
||||||
|
addViewPage: (name: string, code: string) => void;
|
||||||
removeViewModule: (routePath: string) => void;
|
removeViewModule: (routePath: string) => void;
|
||||||
|
|
||||||
copyViewPage: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
|
copyViewPage: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
|
||||||
|
|
||||||
updateRoute: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
|
updateRoute: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
|
||||||
@@ -257,14 +242,8 @@ export interface IWorkspace {
|
|||||||
removeBizComp?: (name: string) => void;
|
removeBizComp?: (name: string) => void;
|
||||||
|
|
||||||
get activeViewModule(): IViewFile;
|
get activeViewModule(): IViewFile;
|
||||||
// TODO: -> getStoreModules
|
|
||||||
get storeModules(): TangoStoreModule[];
|
|
||||||
// TODO: -> getPages
|
|
||||||
get pages(): any[];
|
get pages(): any[];
|
||||||
// TODO: getBizComps
|
|
||||||
get bizComps(): string[];
|
get bizComps(): string[];
|
||||||
// TODO: getBaseComps
|
|
||||||
get baseComps(): string[];
|
get baseComps(): string[];
|
||||||
// TODO: getBlocks
|
|
||||||
get blocks(): any[];
|
get blocks(): any[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,17 @@
|
|||||||
import * as t from '@babel/types';
|
import * as t from '@babel/types';
|
||||||
import { action, computed, makeObservable, observable, toJS } from 'mobx';
|
import { action, computed, makeObservable, observable } from 'mobx';
|
||||||
import { clone, ComponentPrototypeType, Dict, isNil, logger } from '@music163/tango-helpers';
|
import { isNil } from '@music163/tango-helpers';
|
||||||
import {
|
import { code2ast, ast2code, formatCode } from '../helpers';
|
||||||
code2ast,
|
|
||||||
ast2code,
|
|
||||||
traverseRouteFile,
|
|
||||||
traverseViewFile,
|
|
||||||
traverseServiceFile,
|
|
||||||
traverseStoreFile,
|
|
||||||
traverseStoreEntryFile,
|
|
||||||
addStoreToEntryFile,
|
|
||||||
updateServiceConfigToServiceFile,
|
|
||||||
removeJSXElement,
|
|
||||||
insertSiblingAfterJSXElement,
|
|
||||||
getModuleNameByFilename,
|
|
||||||
addRouteToRouteFile,
|
|
||||||
removeRouteFromRouteFile,
|
|
||||||
appendChildToJSXElement,
|
|
||||||
addImportDeclaration,
|
|
||||||
updateImportDeclaration,
|
|
||||||
deleteServiceConfigFromServiceFile,
|
|
||||||
replaceJSXElement,
|
|
||||||
formatCode,
|
|
||||||
getImportDeclarationPayloadByPrototype,
|
|
||||||
addStoreState,
|
|
||||||
removeUnusedImportSpecifiers,
|
|
||||||
insertSiblingBeforeJSXElement,
|
|
||||||
updateStoreState,
|
|
||||||
removeStoreState,
|
|
||||||
removeStoreToEntryFile,
|
|
||||||
replaceRootJSXElementChildren,
|
|
||||||
updateRouteToRouteFile,
|
|
||||||
updateBaseConfigToServiceFile,
|
|
||||||
IdGenerator,
|
|
||||||
updateJSXAttributes,
|
|
||||||
} from '../helpers';
|
|
||||||
import { TangoNode } from './node';
|
|
||||||
import { TangoFile } from './file';
|
import { TangoFile } from './file';
|
||||||
import {
|
import { IFileConfig } from '../types';
|
||||||
IRouteData,
|
import { IWorkspace } from './interfaces';
|
||||||
IFileConfig,
|
|
||||||
IClassPropertyNodeData,
|
|
||||||
IServiceFunctionPayload,
|
|
||||||
ITangoViewNodeData,
|
|
||||||
IStorePropertyData,
|
|
||||||
IImportDeclarationPayload,
|
|
||||||
InsertChildPositionType,
|
|
||||||
} from '../types';
|
|
||||||
import { IViewFile, IWorkspace } from './interfaces';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模块实现规范
|
* JS 模块实现规范
|
||||||
* - ast 操纵类方法,统一返回 this,支持外层链式调用
|
* - ast 操纵类方法,统一返回 this,支持外层链式调用
|
||||||
* - observable state 统一用 _foo 格式,并提供 getter 方法
|
* - observable state 统一用 _foo 格式,并提供 getter 方法
|
||||||
*/
|
*/
|
||||||
class TangoModule extends TangoFile {
|
export class TangoModule extends TangoFile {
|
||||||
ast: t.File;
|
ast: t.File;
|
||||||
|
|
||||||
constructor(workspace: IWorkspace, props: IFileConfig, isSyncCode = true) {
|
constructor(workspace: IWorkspace, props: IFileConfig, isSyncCode = true) {
|
||||||
@@ -132,628 +89,3 @@ export class TangoJsModule extends TangoModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 入口配置模块
|
|
||||||
*/
|
|
||||||
export class TangoStoreEntryModule extends TangoModule {
|
|
||||||
_stores: string[] = [];
|
|
||||||
|
|
||||||
get stores() {
|
|
||||||
return toJS(this._stores);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(workspace: IWorkspace, props: IFileConfig) {
|
|
||||||
super(workspace, props, false);
|
|
||||||
this.update(props.code, true, false);
|
|
||||||
|
|
||||||
makeObservable(this, {
|
|
||||||
_stores: observable,
|
|
||||||
_code: observable,
|
|
||||||
_cleanCode: observable,
|
|
||||||
stores: computed,
|
|
||||||
code: computed,
|
|
||||||
cleanCode: computed,
|
|
||||||
update: action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_analysisAst() {
|
|
||||||
this._stores = traverseStoreEntryFile(this.ast);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新建模型
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
addStore(name: string) {
|
|
||||||
this.ast = addStoreToEntryFile(this.ast, name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除模型
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
removeStore(name: string) {
|
|
||||||
this.ast = removeStoreToEntryFile(this.ast, name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由配置模块
|
|
||||||
*/
|
|
||||||
export class TangoRouteModule extends TangoModule {
|
|
||||||
_routes: IRouteData[];
|
|
||||||
|
|
||||||
get routes() {
|
|
||||||
return toJS(this._routes);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(workspace: IWorkspace, props: IFileConfig) {
|
|
||||||
super(workspace, props, false);
|
|
||||||
this.update(props.code, true, false);
|
|
||||||
|
|
||||||
makeObservable(this, {
|
|
||||||
_routes: observable,
|
|
||||||
_code: observable,
|
|
||||||
_cleanCode: observable,
|
|
||||||
routes: computed,
|
|
||||||
code: computed,
|
|
||||||
cleanCode: computed,
|
|
||||||
update: action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据路由地址获取 route 对象
|
|
||||||
*/
|
|
||||||
getRouteByRoutePath(route: string) {
|
|
||||||
let record;
|
|
||||||
for (const item of this.routes) {
|
|
||||||
if (item.path === route) {
|
|
||||||
record = item;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加一条新路由
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
addRoute(routePath: string, importFilePath: string) {
|
|
||||||
this.ast = addRouteToRouteFile(this.ast, routePath, importFilePath);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新页面路由
|
|
||||||
* @param oldRoutePath
|
|
||||||
* @param newRoutePath
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
updateRoute(oldRoutePath: string, newRoutePath: string) {
|
|
||||||
this.ast = updateRouteToRouteFile(this.ast, oldRoutePath, newRoutePath);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除一条路由
|
|
||||||
* @param route 路由地址
|
|
||||||
*/
|
|
||||||
removeRoute(route: string) {
|
|
||||||
if (route === '/') {
|
|
||||||
console.warn('index route should not be removed!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const record = this.getRouteByRoutePath(route);
|
|
||||||
this.ast = removeRouteFromRouteFile(this.ast, route, record.importPath);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
_analysisAst() {
|
|
||||||
this._routes = traverseRouteFile(this.ast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图模块
|
|
||||||
*/
|
|
||||||
export class TangoViewModule extends TangoModule implements IViewFile {
|
|
||||||
// 解析为树结构的 jsxNodes 数组
|
|
||||||
_nodesTree: ITangoViewNodeData[];
|
|
||||||
/**
|
|
||||||
* 通过导入组件名查找组件来自的包
|
|
||||||
*/
|
|
||||||
importMap: Dict<{ package: string; isDefault?: boolean }>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图中依赖的 tango 变量,仅 stores 和 services
|
|
||||||
*/
|
|
||||||
variables: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点列表
|
|
||||||
*/
|
|
||||||
private _nodes: Map<string, TangoNode>;
|
|
||||||
/**
|
|
||||||
* 导入的模块
|
|
||||||
*/
|
|
||||||
private _importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>;
|
|
||||||
/**
|
|
||||||
* 类属性
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
private _classProperties: Dict<IClassPropertyNodeData>;
|
|
||||||
/**
|
|
||||||
* 状态属性
|
|
||||||
*/
|
|
||||||
private _stateProperties: string[];
|
|
||||||
/**
|
|
||||||
* 状态代码
|
|
||||||
*/
|
|
||||||
private _stateCode: string;
|
|
||||||
/**
|
|
||||||
* ID 生成器
|
|
||||||
*/
|
|
||||||
private _idGenerator: IdGenerator;
|
|
||||||
|
|
||||||
get classProperties() {
|
|
||||||
return this._classProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
get stateProperties() {
|
|
||||||
return this._stateProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nodes() {
|
|
||||||
return this._nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nodesTree() {
|
|
||||||
return toJS(this._nodesTree);
|
|
||||||
}
|
|
||||||
|
|
||||||
get stateCode() {
|
|
||||||
return this._stateCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tree() {
|
|
||||||
return this.ast;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(workspace: IWorkspace, props: IFileConfig) {
|
|
||||||
super(workspace, props, false);
|
|
||||||
this._nodes = new Map();
|
|
||||||
this._idGenerator = new IdGenerator({ prefix: props.filename });
|
|
||||||
this.update(props.code, true, false);
|
|
||||||
makeObservable(this, {
|
|
||||||
_nodesTree: observable,
|
|
||||||
|
|
||||||
_code: observable,
|
|
||||||
_cleanCode: observable,
|
|
||||||
|
|
||||||
code: computed,
|
|
||||||
cleanCode: computed,
|
|
||||||
|
|
||||||
update: action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_syncByAst() {
|
|
||||||
// 空方法,逻辑合并到 this._analysisAst
|
|
||||||
}
|
|
||||||
|
|
||||||
_analysisAst() {
|
|
||||||
const {
|
|
||||||
ast: newAst,
|
|
||||||
cleanAst,
|
|
||||||
nodes,
|
|
||||||
stateCode,
|
|
||||||
stateProperties,
|
|
||||||
classProperties,
|
|
||||||
importedModules,
|
|
||||||
variables,
|
|
||||||
} = traverseViewFile(this.ast, this._idGenerator);
|
|
||||||
this.ast = newAst;
|
|
||||||
|
|
||||||
this._code = ast2code(newAst);
|
|
||||||
this._cleanCode = ast2code(cleanAst);
|
|
||||||
|
|
||||||
this._stateCode = stateCode;
|
|
||||||
|
|
||||||
this._stateProperties = stateProperties;
|
|
||||||
this._classProperties = classProperties;
|
|
||||||
this._importedModules = importedModules;
|
|
||||||
this.importMap = this.buildImportMap(importedModules);
|
|
||||||
this.variables = variables;
|
|
||||||
|
|
||||||
this._nodes.clear();
|
|
||||||
|
|
||||||
nodes.forEach((cur) => {
|
|
||||||
const node = new TangoNode({
|
|
||||||
...cur,
|
|
||||||
file: this,
|
|
||||||
});
|
|
||||||
this._nodes.set(cur.id, node);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._nodesTree = nodeListToTreeData(nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基于组件的 prototype 信息更新导入信息
|
|
||||||
* @param prototype
|
|
||||||
* @param shouldUpdateCode
|
|
||||||
*/
|
|
||||||
updateImportSpecifiersByPrototype(sourcePrototype: string | ComponentPrototypeType) {
|
|
||||||
const prototype = this.workspace.getPrototype(sourcePrototype);
|
|
||||||
if (prototype) {
|
|
||||||
const importDeclaration = getImportDeclarationPayloadByPrototype(prototype, this.filename);
|
|
||||||
this.updateImportSpecifiers(importDeclaration);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新导入的变量(新版)
|
|
||||||
*/
|
|
||||||
updateImportSpecifiers(importDeclaration: IImportDeclarationPayload) {
|
|
||||||
const mods = this._importedModules[importDeclaration.sourcePath];
|
|
||||||
let ast;
|
|
||||||
// 如果模块已存在,需要去重
|
|
||||||
if (mods) {
|
|
||||||
const targetMod = Array.isArray(mods) ? mods[0] : mods;
|
|
||||||
const specifiers = Array.isArray(mods)
|
|
||||||
? mods.reduce((prev, cur) => prev.concat(cur.specifiers || []), [])
|
|
||||||
: mods.specifiers;
|
|
||||||
|
|
||||||
// 去掉已存在的导入声明
|
|
||||||
const newSpecifiers = importDeclaration.specifiers.filter(
|
|
||||||
(name) => !specifiers.includes(name),
|
|
||||||
);
|
|
||||||
|
|
||||||
ast = updateImportDeclaration(this.ast, {
|
|
||||||
...importDeclaration,
|
|
||||||
specifiers: newSpecifiers.concat(targetMod.specifiers),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ast = addImportDeclaration(this.ast, importDeclaration);
|
|
||||||
}
|
|
||||||
this.ast = ast;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除无效的导入声明
|
|
||||||
*/
|
|
||||||
removeUnusedImportSpecifiers() {
|
|
||||||
this.ast = removeUnusedImportSpecifiers(this.ast);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getNode(nodeId: string) {
|
|
||||||
return this._nodes.get(nodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除节点
|
|
||||||
* @param nodeId
|
|
||||||
*/
|
|
||||||
removeNode(nodeId: string) {
|
|
||||||
this.ast = removeJSXElement(this.ast, nodeId);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新节点的属性
|
|
||||||
* @deprecated 使用 updateNodeAttributes 代替
|
|
||||||
*/
|
|
||||||
updateNodeAttribute(
|
|
||||||
nodeId: string,
|
|
||||||
attrName: string,
|
|
||||||
attrValue?: any,
|
|
||||||
relatedImports?: string[],
|
|
||||||
) {
|
|
||||||
return this.updateNodeAttributes(nodeId, { [attrName]: attrValue }, relatedImports);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNodeAttributes(nodeId: string, config: Record<string, any>, relatedImports?: string[]) {
|
|
||||||
if (relatedImports && relatedImports.length) {
|
|
||||||
// 导入依赖的组件
|
|
||||||
relatedImports.forEach((name: string) => {
|
|
||||||
const proto = this.workspace.getPrototype(name);
|
|
||||||
this.updateImportSpecifiersByPrototype(proto);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.ast = updateJSXAttributes(this.ast, nodeId, config);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入子节点的最后面
|
|
||||||
* @param targetNodeId
|
|
||||||
* @param newNode
|
|
||||||
* @param sourceName
|
|
||||||
* @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,
|
|
||||||
) {
|
|
||||||
this.ast = insertSiblingAfterJSXElement(this.ast, targetNodeId, newNode);
|
|
||||||
if (sourceName) {
|
|
||||||
this.updateImportSpecifiersByPrototype(sourceName);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertBefore(
|
|
||||||
targetNodeId: string,
|
|
||||||
newNode: t.JSXElement,
|
|
||||||
sourceName?: string | ComponentPrototypeType,
|
|
||||||
) {
|
|
||||||
this.ast = insertSiblingBeforeJSXElement(this.ast, targetNodeId, newNode);
|
|
||||||
if (sourceName) {
|
|
||||||
this.updateImportSpecifiersByPrototype(sourceName);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 替换目标节点为新节点
|
|
||||||
* @param targetNodeId
|
|
||||||
* @param newNode
|
|
||||||
* @param sourcePrototype
|
|
||||||
*/
|
|
||||||
replaceNode(
|
|
||||||
targetNodeId: string,
|
|
||||||
newNode: t.JSXElement,
|
|
||||||
sourceName?: string | ComponentPrototypeType,
|
|
||||||
) {
|
|
||||||
this.ast = replaceJSXElement(this.ast, targetNodeId, newNode);
|
|
||||||
if (sourceName) {
|
|
||||||
this.updateImportSpecifiersByPrototype(sourceName);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 替换 jsx 跟结点的子元素
|
|
||||||
*/
|
|
||||||
replaceViewChildren(
|
|
||||||
childrenNodes: t.JSXElement[],
|
|
||||||
importDeclarations?: IImportDeclarationPayload[],
|
|
||||||
) {
|
|
||||||
if (childrenNodes.length) {
|
|
||||||
this.ast = replaceRootJSXElementChildren(this.ast, childrenNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importDeclarations?.length) {
|
|
||||||
importDeclarations.forEach((item) => {
|
|
||||||
this.updateImportSpecifiers(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildImportMap(
|
|
||||||
importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>,
|
|
||||||
) {
|
|
||||||
const map = {};
|
|
||||||
Object.keys(importedModules).forEach((modName) => {
|
|
||||||
const mod = importedModules[modName];
|
|
||||||
(Array.isArray(mod) ? mod : [mod]).forEach((item) => {
|
|
||||||
if (item.defaultSpecifier) {
|
|
||||||
map[item.defaultSpecifier] = {
|
|
||||||
package: modName,
|
|
||||||
isDefault: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (item.specifiers.length) {
|
|
||||||
item.specifiers.forEach((spe) => {
|
|
||||||
map[spe] = { package: modName };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将节点列表转换为 tree data 嵌套数组
|
|
||||||
* @param list
|
|
||||||
*/
|
|
||||||
export 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据服务模块
|
|
||||||
*/
|
|
||||||
export class TangoServiceModule extends TangoModule {
|
|
||||||
/**
|
|
||||||
* 模块名
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
_serviceFunctions: Dict;
|
|
||||||
|
|
||||||
_baseConfig: Dict;
|
|
||||||
|
|
||||||
get serviceFunctions() {
|
|
||||||
return toJS(this._serviceFunctions);
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseConfig() {
|
|
||||||
return toJS(this._baseConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(workspace: IWorkspace, props: IFileConfig) {
|
|
||||||
super(workspace, props, false);
|
|
||||||
this.name = getModuleNameByFilename(props.filename);
|
|
||||||
this.update(props.code, true, false);
|
|
||||||
|
|
||||||
makeObservable(this, {
|
|
||||||
_serviceFunctions: observable,
|
|
||||||
_baseConfig: observable,
|
|
||||||
_code: observable,
|
|
||||||
_cleanCode: observable,
|
|
||||||
serviceFunctions: computed,
|
|
||||||
baseConfig: computed,
|
|
||||||
cleanCode: computed,
|
|
||||||
code: computed,
|
|
||||||
update: action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_analysisAst() {
|
|
||||||
const { services, baseConfig } = traverseServiceFile(this.ast);
|
|
||||||
this._serviceFunctions = services;
|
|
||||||
this._baseConfig = baseConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
addServiceFunction(payload: IServiceFunctionPayload) {
|
|
||||||
const { name, ...rest } = payload;
|
|
||||||
this.ast = updateServiceConfigToServiceFile(this.ast, { [name]: clone(rest, false) });
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addServiceFunctions(payloads: IServiceFunctionPayload[]) {
|
|
||||||
const config = payloads.reduce((acc, cur) => {
|
|
||||||
const { name, ...rest } = cur;
|
|
||||||
acc[name] = clone(rest, false);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
this.ast = updateServiceConfigToServiceFile(this.ast, config);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateServiceFunction(payload: IServiceFunctionPayload) {
|
|
||||||
const { name, ...rest } = payload;
|
|
||||||
this.ast = updateServiceConfigToServiceFile(this.ast, { [name]: clone(rest, false) });
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteServiceFunction(serviceFunctionName: string) {
|
|
||||||
try {
|
|
||||||
this.ast = deleteServiceConfigFromServiceFile(this.ast, serviceFunctionName);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新服务的基础配置
|
|
||||||
*/
|
|
||||||
updateBaseConfig(optionName: string, optionValue: any) {
|
|
||||||
this.ast = updateBaseConfigToServiceFile(this.ast, optionName, optionValue);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态模型模块
|
|
||||||
*/
|
|
||||||
export class TangoStoreModule extends TangoModule {
|
|
||||||
/**
|
|
||||||
* 模块名
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
namespace: string;
|
|
||||||
|
|
||||||
states: IStorePropertyData[];
|
|
||||||
|
|
||||||
actions: IStorePropertyData[];
|
|
||||||
|
|
||||||
constructor(workspace: IWorkspace, props: IFileConfig) {
|
|
||||||
super(workspace, props, false);
|
|
||||||
this.name = getModuleNameByFilename(props.filename);
|
|
||||||
this.update(props.code, true, false);
|
|
||||||
|
|
||||||
makeObservable(this, {
|
|
||||||
states: observable,
|
|
||||||
actions: observable,
|
|
||||||
_code: observable,
|
|
||||||
_cleanCode: observable,
|
|
||||||
cleanCode: computed,
|
|
||||||
code: computed,
|
|
||||||
update: action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加状态属性
|
|
||||||
* @param stateName
|
|
||||||
* @param initValue
|
|
||||||
*/
|
|
||||||
addState(stateName: string, initValue: string) {
|
|
||||||
this.ast = addStoreState(this.ast, stateName, initValue);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除状态
|
|
||||||
*/
|
|
||||||
removeState(stateName: string) {
|
|
||||||
this.ast = removeStoreState(this.ast, stateName);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新状态代码
|
|
||||||
* @param stateName 状态名
|
|
||||||
* @param code 代码
|
|
||||||
*/
|
|
||||||
updateState(stateName: string, code: string) {
|
|
||||||
this.ast = updateStoreState(this.ast, stateName, code);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
_analysisAst() {
|
|
||||||
const { namespace, states, actions } = traverseStoreFile(this.ast);
|
|
||||||
this.namespace = namespace || this.name;
|
|
||||||
this.states = states;
|
|
||||||
this.actions = actions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { JSXElement, SourceLocation } from '@babel/types';
|
import { JSXElement, SourceLocation } from '@babel/types';
|
||||||
import { cloneJSXElementWithoutTrackingData, getJSXElementAttributes } from '../helpers';
|
import { cloneJSXElementWithoutTrackingData, getJSXElementAttributes } from '../helpers';
|
||||||
import { ITangoViewNodeData } from '../types';
|
import { ITangoViewNodeData } from '../types';
|
||||||
import { TangoViewModule } from './module';
|
import { TangoViewModule } from './view-module';
|
||||||
import { IViewNode } from './interfaces';
|
import { IViewNode } from './interfaces';
|
||||||
|
|
||||||
type TangoNodeConstructorPropsType = ITangoViewNodeData & {
|
type TangoNodeConstructorPropsType = ITangoViewNodeData & {
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { action, computed, makeObservable, observable, toJS } from 'mobx';
|
||||||
|
import {
|
||||||
|
traverseRouteFile,
|
||||||
|
addRouteToRouteFile,
|
||||||
|
removeRouteFromRouteFile,
|
||||||
|
updateRouteToRouteFile,
|
||||||
|
} from '../helpers';
|
||||||
|
import { IRouteData, IFileConfig } from '../types';
|
||||||
|
import { IWorkspace } from './interfaces';
|
||||||
|
import { TangoModule } from './module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由配置模块
|
||||||
|
*/
|
||||||
|
export class TangoRouteModule extends TangoModule {
|
||||||
|
_routes: IRouteData[];
|
||||||
|
|
||||||
|
get routes() {
|
||||||
|
return toJS(this._routes);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(workspace: IWorkspace, props: IFileConfig) {
|
||||||
|
super(workspace, props, false);
|
||||||
|
this.update(props.code, true, false);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
_routes: observable,
|
||||||
|
_code: observable,
|
||||||
|
_cleanCode: observable,
|
||||||
|
routes: computed,
|
||||||
|
code: computed,
|
||||||
|
cleanCode: computed,
|
||||||
|
update: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路由地址获取 route 对象
|
||||||
|
*/
|
||||||
|
getRouteByRoutePath(route: string) {
|
||||||
|
let record;
|
||||||
|
for (const item of this.routes) {
|
||||||
|
if (item.path === route) {
|
||||||
|
record = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一条新路由
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
addRoute(routePath: string, importFilePath: string) {
|
||||||
|
this.ast = addRouteToRouteFile(this.ast, routePath, importFilePath);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新页面路由
|
||||||
|
* @param oldRoutePath
|
||||||
|
* @param newRoutePath
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
updateRoute(oldRoutePath: string, newRoutePath: string) {
|
||||||
|
this.ast = updateRouteToRouteFile(this.ast, oldRoutePath, newRoutePath);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一条路由
|
||||||
|
* @param route 路由地址
|
||||||
|
*/
|
||||||
|
removeRoute(route: string) {
|
||||||
|
if (route === '/') {
|
||||||
|
console.warn('index route should not be removed!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const record = this.getRouteByRoutePath(route);
|
||||||
|
this.ast = removeRouteFromRouteFile(this.ast, route, record.importPath);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
_analysisAst() {
|
||||||
|
this._routes = traverseRouteFile(this.ast);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { action, computed, makeObservable, observable, toJS } from 'mobx';
|
||||||
|
import { clone, Dict, logger } from '@music163/tango-helpers';
|
||||||
|
import {
|
||||||
|
traverseServiceFile,
|
||||||
|
updateServiceConfigToServiceFile,
|
||||||
|
getModuleNameByFilename,
|
||||||
|
deleteServiceConfigFromServiceFile,
|
||||||
|
updateBaseConfigToServiceFile,
|
||||||
|
} from '../helpers';
|
||||||
|
import { IFileConfig, IServiceFunctionPayload } from '../types';
|
||||||
|
import { IWorkspace } from './interfaces';
|
||||||
|
import { TangoModule } from './module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据服务模块
|
||||||
|
*/
|
||||||
|
export class TangoServiceModule extends TangoModule {
|
||||||
|
/**
|
||||||
|
* 服务函数的模块名,默认为 index
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始注册的服务函数
|
||||||
|
* @example { get: {}, list: {} }
|
||||||
|
*/
|
||||||
|
_serviceFunctions: Dict;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始的基础配置
|
||||||
|
*/
|
||||||
|
_baseConfig: Dict;
|
||||||
|
|
||||||
|
get serviceFunctions() {
|
||||||
|
return toJS(this._serviceFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseConfig() {
|
||||||
|
return toJS(this._baseConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(workspace: IWorkspace, props: IFileConfig) {
|
||||||
|
super(workspace, props, false);
|
||||||
|
this.name = getModuleNameByFilename(props.filename);
|
||||||
|
this.update(props.code, true, false);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
_serviceFunctions: observable,
|
||||||
|
_baseConfig: observable,
|
||||||
|
_code: observable,
|
||||||
|
_cleanCode: observable,
|
||||||
|
serviceFunctions: computed,
|
||||||
|
baseConfig: computed,
|
||||||
|
cleanCode: computed,
|
||||||
|
code: computed,
|
||||||
|
update: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_analysisAst() {
|
||||||
|
const { services, baseConfig } = traverseServiceFile(this.ast);
|
||||||
|
this._serviceFunctions = services;
|
||||||
|
this._baseConfig = baseConfig;
|
||||||
|
if (baseConfig.namespace) {
|
||||||
|
this.name = baseConfig.namespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addServiceFunction(payload: IServiceFunctionPayload) {
|
||||||
|
const { name, ...rest } = payload;
|
||||||
|
this.ast = updateServiceConfigToServiceFile(this.ast, { [name]: clone(rest, false) });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addServiceFunctions(payloads: IServiceFunctionPayload[]) {
|
||||||
|
const config = payloads.reduce((acc, cur) => {
|
||||||
|
const { name, ...rest } = cur;
|
||||||
|
acc[name] = clone(rest, false);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
this.ast = updateServiceConfigToServiceFile(this.ast, config);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateServiceFunction(payload: IServiceFunctionPayload) {
|
||||||
|
const { name, ...rest } = payload;
|
||||||
|
this.ast = updateServiceConfigToServiceFile(this.ast, { [name]: clone(rest, false) });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteServiceFunction(serviceFunctionName: string) {
|
||||||
|
try {
|
||||||
|
this.ast = deleteServiceConfigFromServiceFile(this.ast, serviceFunctionName);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新服务的基础配置
|
||||||
|
*/
|
||||||
|
updateBaseConfig(data: object) {
|
||||||
|
this.ast = updateBaseConfigToServiceFile(this.ast, data);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { action, computed, makeObservable, observable, toJS } from 'mobx';
|
||||||
|
import {
|
||||||
|
traverseStoreFile,
|
||||||
|
traverseStoreEntryFile,
|
||||||
|
addStoreToEntryFile,
|
||||||
|
getModuleNameByFilename,
|
||||||
|
addStoreState,
|
||||||
|
updateStoreState,
|
||||||
|
removeStoreState,
|
||||||
|
removeStoreToEntryFile,
|
||||||
|
} from '../helpers';
|
||||||
|
import { IFileConfig, IStorePropertyData } from '../types';
|
||||||
|
import { IWorkspace } from './interfaces';
|
||||||
|
import { TangoModule } from './module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入口配置模块
|
||||||
|
*/
|
||||||
|
export class TangoStoreEntryModule extends TangoModule {
|
||||||
|
_stores: string[] = [];
|
||||||
|
|
||||||
|
get stores() {
|
||||||
|
return toJS(this._stores);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(workspace: IWorkspace, props: IFileConfig) {
|
||||||
|
super(workspace, props, false);
|
||||||
|
this.update(props.code, true, false);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
_stores: observable,
|
||||||
|
_code: observable,
|
||||||
|
_cleanCode: observable,
|
||||||
|
stores: computed,
|
||||||
|
code: computed,
|
||||||
|
cleanCode: computed,
|
||||||
|
update: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_analysisAst() {
|
||||||
|
this._stores = traverseStoreEntryFile(this.ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建模型
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
addStore(name: string) {
|
||||||
|
this.ast = addStoreToEntryFile(this.ast, name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除模型
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
removeStore(name: string) {
|
||||||
|
this.ast = removeStoreToEntryFile(this.ast, name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态模型模块
|
||||||
|
*/
|
||||||
|
export class TangoStoreModule extends TangoModule {
|
||||||
|
/**
|
||||||
|
* 模块名
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
namespace: string;
|
||||||
|
|
||||||
|
states: IStorePropertyData[];
|
||||||
|
|
||||||
|
actions: IStorePropertyData[];
|
||||||
|
|
||||||
|
constructor(workspace: IWorkspace, props: IFileConfig) {
|
||||||
|
super(workspace, props, false);
|
||||||
|
this.name = getModuleNameByFilename(props.filename);
|
||||||
|
this.update(props.code, true, false);
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
states: observable,
|
||||||
|
actions: observable,
|
||||||
|
_code: observable,
|
||||||
|
_cleanCode: observable,
|
||||||
|
cleanCode: computed,
|
||||||
|
code: computed,
|
||||||
|
update: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加状态属性
|
||||||
|
* @param stateName
|
||||||
|
* @param initValue
|
||||||
|
*/
|
||||||
|
addState(stateName: string, initValue: string) {
|
||||||
|
this.ast = addStoreState(this.ast, stateName, initValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除状态
|
||||||
|
*/
|
||||||
|
removeState(stateName: string) {
|
||||||
|
this.ast = removeStoreState(this.ast, stateName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新状态代码
|
||||||
|
* @param stateName 状态名
|
||||||
|
* @param code 代码
|
||||||
|
*/
|
||||||
|
updateState(stateName: string, code: string) {
|
||||||
|
this.ast = updateStoreState(this.ast, stateName, code);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
_analysisAst() {
|
||||||
|
const { namespace, states, actions } = traverseStoreFile(this.ast);
|
||||||
|
this.namespace = namespace || this.name;
|
||||||
|
this.states = states;
|
||||||
|
this.actions = actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
import * as t from '@babel/types';
|
||||||
|
import { action, computed, makeObservable, observable, toJS } from 'mobx';
|
||||||
|
import { ComponentPrototypeType, Dict } from '@music163/tango-helpers';
|
||||||
|
import {
|
||||||
|
ast2code,
|
||||||
|
traverseViewFile,
|
||||||
|
removeJSXElement,
|
||||||
|
insertSiblingAfterJSXElement,
|
||||||
|
appendChildToJSXElement,
|
||||||
|
addImportDeclaration,
|
||||||
|
updateImportDeclaration,
|
||||||
|
replaceJSXElement,
|
||||||
|
getImportDeclarationPayloadByPrototype,
|
||||||
|
removeUnusedImportSpecifiers,
|
||||||
|
insertSiblingBeforeJSXElement,
|
||||||
|
replaceRootJSXElementChildren,
|
||||||
|
IdGenerator,
|
||||||
|
updateJSXAttributes,
|
||||||
|
queryXFormItemFields,
|
||||||
|
} from '../helpers';
|
||||||
|
import { TangoNode } from './node';
|
||||||
|
import {
|
||||||
|
IFileConfig,
|
||||||
|
ITangoViewNodeData,
|
||||||
|
IImportDeclarationPayload,
|
||||||
|
InsertChildPositionType,
|
||||||
|
IImportSpecifierData,
|
||||||
|
} from '../types';
|
||||||
|
import { IViewFile, IWorkspace } from './interfaces';
|
||||||
|
import { TangoModule } from './module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视图模块
|
||||||
|
*/
|
||||||
|
export class TangoViewModule extends TangoModule implements IViewFile {
|
||||||
|
// 解析为树结构的 jsxNodes 数组
|
||||||
|
_nodesTree: ITangoViewNodeData[];
|
||||||
|
/**
|
||||||
|
* 通过导入组件名查找组件来自的包
|
||||||
|
*/
|
||||||
|
importMap: Dict<IImportSpecifierData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视图中依赖的 tango 变量,仅 stores 和 services
|
||||||
|
*/
|
||||||
|
variables: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点列表
|
||||||
|
*/
|
||||||
|
private _nodes: Map<string, TangoNode>;
|
||||||
|
/**
|
||||||
|
* 导入的模块
|
||||||
|
*/
|
||||||
|
private _importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID 生成器
|
||||||
|
*/
|
||||||
|
private _idGenerator: IdGenerator;
|
||||||
|
|
||||||
|
get nodes() {
|
||||||
|
return this._nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get nodesTree() {
|
||||||
|
return toJS(this._nodesTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
get tree() {
|
||||||
|
return this.ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(workspace: IWorkspace, props: IFileConfig) {
|
||||||
|
super(workspace, props, false);
|
||||||
|
this._nodes = new Map();
|
||||||
|
this._idGenerator = new IdGenerator({ prefix: props.filename });
|
||||||
|
this.update(props.code, true, false);
|
||||||
|
makeObservable(this, {
|
||||||
|
_nodesTree: observable,
|
||||||
|
|
||||||
|
_code: observable,
|
||||||
|
_cleanCode: observable,
|
||||||
|
|
||||||
|
code: computed,
|
||||||
|
cleanCode: computed,
|
||||||
|
|
||||||
|
update: action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncByAst() {
|
||||||
|
// 空方法,逻辑合并到 this._analysisAst
|
||||||
|
}
|
||||||
|
|
||||||
|
_analysisAst() {
|
||||||
|
const {
|
||||||
|
ast: newAst,
|
||||||
|
cleanAst,
|
||||||
|
nodes,
|
||||||
|
importedModules,
|
||||||
|
variables,
|
||||||
|
} = traverseViewFile(this.ast, this._idGenerator);
|
||||||
|
this.ast = newAst;
|
||||||
|
|
||||||
|
this._code = ast2code(newAst);
|
||||||
|
this._cleanCode = ast2code(cleanAst);
|
||||||
|
|
||||||
|
this._importedModules = importedModules;
|
||||||
|
this.importMap = this.buildImportMap(importedModules);
|
||||||
|
this.variables = variables;
|
||||||
|
|
||||||
|
this._nodes.clear();
|
||||||
|
|
||||||
|
nodes.forEach((cur) => {
|
||||||
|
const node = new TangoNode({
|
||||||
|
...cur,
|
||||||
|
file: this,
|
||||||
|
});
|
||||||
|
this._nodes.set(cur.id, node);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._nodesTree = nodeListToTreeData(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 依赖列表
|
||||||
|
*/
|
||||||
|
listImportSources() {
|
||||||
|
return Object.keys(this._importedModules);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹窗列表
|
||||||
|
*/
|
||||||
|
listModals() {
|
||||||
|
const modals: Array<{ label: string; value: string }> = [];
|
||||||
|
const activeViewNodes = this.nodes || new Map();
|
||||||
|
|
||||||
|
Array.from(activeViewNodes.values()).forEach((node) => {
|
||||||
|
if (['Modal', 'Drawer'].includes(node.component) && node.props.id) {
|
||||||
|
modals.push({
|
||||||
|
label: `${node.component}(${node.props.id})`,
|
||||||
|
value: node.props.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return modals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单列表
|
||||||
|
*/
|
||||||
|
listForms() {
|
||||||
|
const forms: Record<string, string[]> = {};
|
||||||
|
const activeViewNodes = this.nodes;
|
||||||
|
Array.from(activeViewNodes.values()).forEach((node) => {
|
||||||
|
if (
|
||||||
|
['XAction', 'XColumnAction', 'XForm', 'XStepForm', 'XSearchForm', 'XFormList'].includes(
|
||||||
|
node.component,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
forms[node.id] = queryXFormItemFields(node.rawNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return forms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于组件的 prototype 信息更新导入信息
|
||||||
|
* @param prototype
|
||||||
|
* @param shouldUpdateCode
|
||||||
|
*/
|
||||||
|
updateImportSpecifiersByPrototype(sourcePrototype: string | ComponentPrototypeType) {
|
||||||
|
const prototype = this.workspace.getPrototype(sourcePrototype);
|
||||||
|
if (prototype) {
|
||||||
|
const importDeclaration = getImportDeclarationPayloadByPrototype(prototype, this.filename);
|
||||||
|
this.updateImportSpecifiers(importDeclaration);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新导入的变量(新版)
|
||||||
|
*/
|
||||||
|
updateImportSpecifiers(importDeclaration: IImportDeclarationPayload) {
|
||||||
|
const mods = this._importedModules[importDeclaration.sourcePath];
|
||||||
|
let ast;
|
||||||
|
// 如果模块已存在,需要去重
|
||||||
|
if (mods) {
|
||||||
|
const targetMod = Array.isArray(mods) ? mods[0] : mods;
|
||||||
|
const specifiers = Array.isArray(mods)
|
||||||
|
? mods.reduce((prev, cur) => prev.concat(cur.specifiers || []), [])
|
||||||
|
: mods.specifiers;
|
||||||
|
|
||||||
|
// 去掉已存在的导入声明
|
||||||
|
const newSpecifiers = importDeclaration.specifiers.filter(
|
||||||
|
(name) => !specifiers.includes(name),
|
||||||
|
);
|
||||||
|
|
||||||
|
ast = updateImportDeclaration(this.ast, {
|
||||||
|
...importDeclaration,
|
||||||
|
specifiers: newSpecifiers.concat(targetMod.specifiers),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ast = addImportDeclaration(this.ast, importDeclaration);
|
||||||
|
}
|
||||||
|
this.ast = ast;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除无效的导入声明
|
||||||
|
*/
|
||||||
|
removeUnusedImportSpecifiers() {
|
||||||
|
this.ast = removeUnusedImportSpecifiers(this.ast);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNode(nodeId: string) {
|
||||||
|
return this._nodes.get(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除节点
|
||||||
|
* @param nodeId
|
||||||
|
*/
|
||||||
|
removeNode(nodeId: string) {
|
||||||
|
this.ast = removeJSXElement(this.ast, nodeId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新节点的属性
|
||||||
|
* @deprecated 使用 updateNodeAttributes 代替
|
||||||
|
*/
|
||||||
|
updateNodeAttribute(
|
||||||
|
nodeId: string,
|
||||||
|
attrName: string,
|
||||||
|
attrValue?: any,
|
||||||
|
relatedImports?: string[],
|
||||||
|
) {
|
||||||
|
return this.updateNodeAttributes(nodeId, { [attrName]: attrValue }, relatedImports);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNodeAttributes(nodeId: string, config: Record<string, any>, relatedImports?: string[]) {
|
||||||
|
if (relatedImports && relatedImports.length) {
|
||||||
|
// 导入依赖的组件
|
||||||
|
relatedImports.forEach((name: string) => {
|
||||||
|
const proto = this.workspace.getPrototype(name);
|
||||||
|
this.updateImportSpecifiersByPrototype(proto);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.ast = updateJSXAttributes(this.ast, nodeId, config);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入子节点的最后面
|
||||||
|
* @param targetNodeId
|
||||||
|
* @param newNode
|
||||||
|
* @param sourceName
|
||||||
|
* @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,
|
||||||
|
) {
|
||||||
|
this.ast = insertSiblingAfterJSXElement(this.ast, targetNodeId, newNode);
|
||||||
|
if (sourceName) {
|
||||||
|
this.updateImportSpecifiersByPrototype(sourceName);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertBefore(
|
||||||
|
targetNodeId: string,
|
||||||
|
newNode: t.JSXElement,
|
||||||
|
sourceName?: string | ComponentPrototypeType,
|
||||||
|
) {
|
||||||
|
this.ast = insertSiblingBeforeJSXElement(this.ast, targetNodeId, newNode);
|
||||||
|
if (sourceName) {
|
||||||
|
this.updateImportSpecifiersByPrototype(sourceName);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换目标节点为新节点
|
||||||
|
* @param targetNodeId
|
||||||
|
* @param newNode
|
||||||
|
* @param sourcePrototype
|
||||||
|
*/
|
||||||
|
replaceNode(
|
||||||
|
targetNodeId: string,
|
||||||
|
newNode: t.JSXElement,
|
||||||
|
sourceName?: string | ComponentPrototypeType,
|
||||||
|
) {
|
||||||
|
this.ast = replaceJSXElement(this.ast, targetNodeId, newNode);
|
||||||
|
if (sourceName) {
|
||||||
|
this.updateImportSpecifiersByPrototype(sourceName);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换 jsx 跟结点的子元素
|
||||||
|
*/
|
||||||
|
replaceViewChildren(
|
||||||
|
childrenNodes: t.JSXElement[],
|
||||||
|
importDeclarations?: IImportDeclarationPayload[],
|
||||||
|
) {
|
||||||
|
if (childrenNodes.length) {
|
||||||
|
this.ast = replaceRootJSXElementChildren(this.ast, childrenNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importDeclarations?.length) {
|
||||||
|
importDeclarations.forEach((item) => {
|
||||||
|
this.updateImportSpecifiers(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildImportMap(
|
||||||
|
importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>,
|
||||||
|
) {
|
||||||
|
const map: Dict<IImportSpecifierData> = {};
|
||||||
|
Object.keys(importedModules).forEach((modName) => {
|
||||||
|
const mod = importedModules[modName];
|
||||||
|
(Array.isArray(mod) ? mod : [mod]).forEach((item) => {
|
||||||
|
if (item.defaultSpecifier) {
|
||||||
|
map[item.defaultSpecifier] = {
|
||||||
|
source: modName,
|
||||||
|
isDefault: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (item.specifiers.length) {
|
||||||
|
item.specifiers.forEach((spe) => {
|
||||||
|
map[spe] = { source: modName };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将节点列表转换为 tree data 嵌套数组
|
||||||
|
* @param list
|
||||||
|
*/
|
||||||
|
function nodeListToTreeData(list: ITangoViewNodeData[]) {
|
||||||
|
const map: Record<string, ITangoViewNodeData> = {};
|
||||||
|
|
||||||
|
list.forEach((item) => {
|
||||||
|
// 如果不存在,则初始化
|
||||||
|
if (!map[item.id]) {
|
||||||
|
map[item.id] = {
|
||||||
|
...item,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否找到父节点,找到则塞进去
|
||||||
|
if (item.parentId && map[item.parentId]) {
|
||||||
|
map[item.parentId].children.push(map[item.id]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保留根节点
|
||||||
|
const ret = Object.values(map).filter((item) => !item.parentId);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -16,20 +16,12 @@ import {
|
|||||||
getJSXElementChildrenNames,
|
getJSXElementChildrenNames,
|
||||||
namesToImportDeclarations,
|
namesToImportDeclarations,
|
||||||
getBlockNameByFilename,
|
getBlockNameByFilename,
|
||||||
queryXFormItemFields,
|
|
||||||
getPrivilegeCode,
|
getPrivilegeCode,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { DropMethod } from './drop-target';
|
import { DropMethod } from './drop-target';
|
||||||
import { HistoryMessage, TangoHistory } from './history';
|
import { HistoryMessage, TangoHistory } from './history';
|
||||||
import { TangoNode } from './node';
|
import { TangoNode } from './node';
|
||||||
import {
|
import { TangoJsModule } from './module';
|
||||||
TangoStoreEntryModule,
|
|
||||||
TangoRouteModule,
|
|
||||||
TangoServiceModule,
|
|
||||||
TangoStoreModule,
|
|
||||||
TangoViewModule,
|
|
||||||
TangoJsModule,
|
|
||||||
} from './module';
|
|
||||||
import { TangoFile, TangoJsonFile, TangoLessFile } from './file';
|
import { TangoFile, TangoJsonFile, TangoLessFile } from './file';
|
||||||
import { IWorkspace } from './interfaces';
|
import { IWorkspace } from './interfaces';
|
||||||
import {
|
import {
|
||||||
@@ -37,11 +29,14 @@ import {
|
|||||||
FileType,
|
FileType,
|
||||||
ITangoConfigPackages,
|
ITangoConfigPackages,
|
||||||
IPageConfigData,
|
IPageConfigData,
|
||||||
IProjectData,
|
|
||||||
IServiceFunctionPayload,
|
IServiceFunctionPayload,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { SelectSource } from './select-source';
|
import { SelectSource } from './select-source';
|
||||||
import { DragSource } from './drag-source';
|
import { DragSource } from './drag-source';
|
||||||
|
import { TangoRouteModule } from './route-module';
|
||||||
|
import { TangoStoreEntryModule, TangoStoreModule } from './store-module';
|
||||||
|
import { TangoServiceModule } from './service-module';
|
||||||
|
import { TangoViewModule } from './view-module';
|
||||||
|
|
||||||
export interface IWorkspaceOptions {
|
export interface IWorkspaceOptions {
|
||||||
/**
|
/**
|
||||||
@@ -96,14 +91,10 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 本地区块 { [blockName]: filePath }
|
* 本地区块 { [blockName]: filePath }
|
||||||
|
* FIXME: 修正类型 Record<string, TangoBlockModule>
|
||||||
*/
|
*/
|
||||||
localBlocks: Record<string, string>;
|
localBlocks: Record<string, string>;
|
||||||
|
|
||||||
/**
|
|
||||||
* service 模块
|
|
||||||
*/
|
|
||||||
serviceModule: TangoServiceModule;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由配置模块
|
* 路由配置模块
|
||||||
*/
|
*/
|
||||||
@@ -114,6 +105,10 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
*/
|
*/
|
||||||
storeEntryModule: TangoStoreEntryModule;
|
storeEntryModule: TangoStoreEntryModule;
|
||||||
|
|
||||||
|
storeModules: Record<string, TangoStoreModule> = {};
|
||||||
|
|
||||||
|
serviceModules: Record<string, TangoServiceModule> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* package.json 文件
|
* package.json 文件
|
||||||
*/
|
*/
|
||||||
@@ -161,19 +156,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
return this.files.get(this.activeViewFile) as TangoViewModule;
|
return this.files.get(this.activeViewFile) as TangoViewModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* store 模块列表
|
|
||||||
*/
|
|
||||||
get storeModules() {
|
|
||||||
const mods: TangoStoreModule[] = [];
|
|
||||||
this.files.forEach((file) => {
|
|
||||||
if (file instanceof TangoStoreModule) {
|
|
||||||
mods.push(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取页面列表
|
* 获取页面列表
|
||||||
*/
|
*/
|
||||||
@@ -270,36 +252,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
return name as ComponentPrototypeType;
|
return name as ComponentPrototypeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProjectData() {
|
|
||||||
const pages = this.pages.map((item) => {
|
|
||||||
const filename = this.getFilenameByRoutePath(item.path);
|
|
||||||
return {
|
|
||||||
title: item.name,
|
|
||||||
path: item.path,
|
|
||||||
filename,
|
|
||||||
includes: {
|
|
||||||
variables: (this.getFile(filename) as TangoViewModule).variables,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const stores = this.storeModules.reduce((acc, item) => {
|
|
||||||
acc[item.name] = { filename: item.filename };
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
const services = {
|
|
||||||
index: {
|
|
||||||
filename: this.serviceModule?.filename,
|
|
||||||
functions: Object.keys(this.serviceModule.serviceFunctions),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const data: IProjectData = {
|
|
||||||
pages,
|
|
||||||
stores,
|
|
||||||
services,
|
|
||||||
};
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置当前路由
|
* 设置当前路由
|
||||||
* @param routePath 路由路径
|
* @param routePath 路由路径
|
||||||
@@ -395,10 +347,11 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
break;
|
break;
|
||||||
case FileType.ServiceModule:
|
case FileType.ServiceModule:
|
||||||
module = new TangoServiceModule(this, props);
|
module = new TangoServiceModule(this, props);
|
||||||
this.serviceModule = module; // 仅支持单个 service 文件
|
this.serviceModules[module.name] = module;
|
||||||
break;
|
break;
|
||||||
case FileType.StoreModule:
|
case FileType.StoreModule:
|
||||||
module = new TangoStoreModule(this, props);
|
module = new TangoStoreModule(this, props);
|
||||||
|
this.storeModules[module.name] = module;
|
||||||
break;
|
break;
|
||||||
case FileType.BlockEntryModule: {
|
case FileType.BlockEntryModule: {
|
||||||
const blockName = getBlockNameByFilename(props.filename);
|
const blockName = getBlockNameByFilename(props.filename);
|
||||||
@@ -483,8 +436,24 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
*/
|
*/
|
||||||
renameFile(oldFilename: string, newFilename: string) {
|
renameFile(oldFilename: string, newFilename: string) {
|
||||||
const file = this.files.get(oldFilename);
|
const file = this.files.get(oldFilename);
|
||||||
this.removeFile(oldFilename);
|
if (file) {
|
||||||
this.addFile(newFilename, file.code);
|
this.removeFile(oldFilename);
|
||||||
|
this.addFile(newFilename, file.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重命名文件夹
|
||||||
|
* @param oldFoldername 旧文件夹名
|
||||||
|
* @param newFoldername 新文件夹名
|
||||||
|
*/
|
||||||
|
renameFolder(oldFoldername: string, newFoldername: string) {
|
||||||
|
Array.from(this.files.keys()).forEach((key) => {
|
||||||
|
if (key.startsWith(`${oldFoldername}/`)) {
|
||||||
|
const newKey = key.replace(oldFoldername, newFoldername);
|
||||||
|
this.renameFile(key, newKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -508,24 +477,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
listStoreModules() {
|
|
||||||
return Array.from(this.files.values()).filter(
|
|
||||||
(file) => file instanceof TangoStoreModule,
|
|
||||||
) as TangoStoreModule[];
|
|
||||||
}
|
|
||||||
|
|
||||||
getStoreModuleByName(storeName: string) {
|
|
||||||
let mod;
|
|
||||||
const files = this.files.values();
|
|
||||||
for (const file of files) {
|
|
||||||
if (file instanceof TangoStoreModule && file.name === storeName) {
|
|
||||||
mod = file;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除视图模块
|
* 删除视图模块
|
||||||
* @param route 路由名称
|
* @param route 路由名称
|
||||||
@@ -625,45 +576,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: 仅获取当前视图即可
|
|
||||||
* 获取当前视图的弹窗列表
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
listModals() {
|
|
||||||
const modals: Array<{ label: string; value: string }> = [];
|
|
||||||
const activeViewNodes = this.activeViewModule?.nodes || new Map();
|
|
||||||
|
|
||||||
Array.from(activeViewNodes.values()).forEach((node) => {
|
|
||||||
if (['Modal', 'Drawer'].includes(node.component) && node.props.id) {
|
|
||||||
modals.push({
|
|
||||||
label: `${node.component}(${node.props.id})`,
|
|
||||||
value: node.props.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return modals;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: 获取当前视图的表单列表
|
|
||||||
*/
|
|
||||||
listForms() {
|
|
||||||
const forms: Record<string, string[]> = {};
|
|
||||||
const activeViewNodes = this.activeViewModule?.nodes;
|
|
||||||
Array.from(activeViewNodes.values()).forEach((node) => {
|
|
||||||
if (
|
|
||||||
['XAction', 'XColumnAction', 'XForm', 'XStepForm', 'XSearchForm', 'XFormList'].includes(
|
|
||||||
node.component,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
forms[node.id] = queryXFormItemFields(node.rawNode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return forms;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用代码初始化完成
|
* 应用代码初始化完成
|
||||||
*/
|
*/
|
||||||
@@ -680,34 +592,6 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加区块文件
|
|
||||||
* @param files 文件
|
|
||||||
* @param name 区块文件名
|
|
||||||
*/
|
|
||||||
addBlock(files: Object, name: string) {
|
|
||||||
const blockFiles = {};
|
|
||||||
Object.keys(files).forEach((path) => {
|
|
||||||
if (!path.includes('/src')) return;
|
|
||||||
blockFiles[path.replace('/src/', '')] = files[path];
|
|
||||||
});
|
|
||||||
this.addFiles(
|
|
||||||
Object.keys(blockFiles).map((filepath) => ({
|
|
||||||
filename: `/src/blocks/${name}/${filepath}`,
|
|
||||||
code: blockFiles[filepath],
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
const { dependencies } = JSON.parse(files['/package.json']);
|
|
||||||
Object.keys(dependencies).forEach((pkg) => {
|
|
||||||
this.packageJson?.setValue('dependencies', (deps) => {
|
|
||||||
if (!deps[pkg]) {
|
|
||||||
deps[pkg] = dependencies[pkg];
|
|
||||||
}
|
|
||||||
return deps;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加新的模型文件
|
* 添加新的模型文件
|
||||||
* @param name 模型名
|
* @param name 模型名
|
||||||
@@ -736,7 +620,7 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
* @param initValue
|
* @param initValue
|
||||||
*/
|
*/
|
||||||
addStoreState(storeName: string, stateName: string, initValue: string) {
|
addStoreState(storeName: string, stateName: string, initValue: string) {
|
||||||
this.getStoreModuleByName(storeName).addState(stateName, initValue).update();
|
this.storeModules[storeName]?.addState(stateName, initValue).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -745,7 +629,7 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
* @param stateName
|
* @param stateName
|
||||||
*/
|
*/
|
||||||
removeStoreState(storeName: string, stateName: string) {
|
removeStoreState(storeName: string, stateName: string) {
|
||||||
this.getStoreModuleByName(storeName).removeState(stateName).update();
|
this.storeModules[storeName]?.removeState(stateName).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -756,26 +640,65 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
*/
|
*/
|
||||||
updateModuleCodeByVariablePath(variablePath: string, code: string) {
|
updateModuleCodeByVariablePath(variablePath: string, code: string) {
|
||||||
if (/^stores\.\w+\.\w+$/.test(variablePath)) {
|
if (/^stores\.\w+\.\w+$/.test(variablePath)) {
|
||||||
const [type, storeName, stateName] = variablePath.split('.');
|
const [, storeName, stateName] = variablePath.split('.');
|
||||||
this.getStoreModuleByName(storeName).updateState(stateName, code).update();
|
this.storeModules[storeName]?.updateState(stateName, code).update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务函数的详情
|
||||||
|
* TODO: 不要 services 前缀
|
||||||
|
* @param serviceKey `services.list` 或 `services.sub.list`
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getServiceFunction(serviceKey: string) {
|
||||||
|
const { name, moduleName } = this.parseServiceKey(serviceKey);
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
moduleName,
|
||||||
|
config: this.serviceModules[moduleName]?.serviceFunctions[name],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务函数的列表
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新服务函数
|
* 更新服务函数
|
||||||
*/
|
*/
|
||||||
updateServiceFunction(payload: any) {
|
updateServiceFunction(payload: IServiceFunctionPayload, moduleName = 'index') {
|
||||||
this.serviceModule.updateServiceFunction(payload).update();
|
this.serviceModules[moduleName].updateServiceFunction(payload).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增服务函数,支持批量添加
|
* 新增服务函数,支持批量添加
|
||||||
*/
|
*/
|
||||||
addServiceFunction(payload: IServiceFunctionPayload | IServiceFunctionPayload[]) {
|
addServiceFunction(
|
||||||
|
payload: IServiceFunctionPayload | IServiceFunctionPayload[],
|
||||||
|
moduleName = 'index',
|
||||||
|
) {
|
||||||
if (Array.isArray(payload)) {
|
if (Array.isArray(payload)) {
|
||||||
this.serviceModule.addServiceFunctions(payload).update();
|
this.serviceModules[moduleName]?.addServiceFunctions(payload).update();
|
||||||
} else {
|
} else {
|
||||||
this.serviceModule.addServiceFunction(payload).update();
|
this.serviceModules[moduleName]?.addServiceFunction(payload).update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,15 +706,15 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
* 删除服务函数
|
* 删除服务函数
|
||||||
* @param name
|
* @param name
|
||||||
*/
|
*/
|
||||||
removeServiceFunction(name: string) {
|
removeServiceFunction(name: string, moduleName = 'index') {
|
||||||
this.serviceModule.deleteServiceFunction(name).update();
|
this.serviceModules[moduleName]?.deleteServiceFunction(name).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新服务的基础配置
|
* 更新服务的基础配置
|
||||||
*/
|
*/
|
||||||
updateServiceBaseConfig(name: string, value: any) {
|
updateServiceBaseConfig(config: object, moduleName = 'index') {
|
||||||
this.serviceModule.updateBaseConfig(name, value).update();
|
this.serviceModules[moduleName]?.updateBaseConfig(config).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1279,4 +1202,40 @@ export class Workspace extends EventTarget implements IWorkspace {
|
|||||||
logger.error('copyFiles failed, source: %s, target: %s', sourceFilePath, targetFilePath);
|
logger.error('copyFiles failed, source: %s, target: %s', sourceFilePath, targetFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 serviceKey
|
||||||
|
* @param serviceKey
|
||||||
|
* @returns
|
||||||
|
*
|
||||||
|
* @example services.list => { moduleName: 'index', name: 'list' }
|
||||||
|
* @example services.sub.list => { moduleName: 'sub', name: 'list' }
|
||||||
|
* @example foo => undefined
|
||||||
|
*/
|
||||||
|
private parseServiceKey(serviceKey: string) {
|
||||||
|
const parts = serviceKey.split('.');
|
||||||
|
if (parts[0] !== 'services') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let moduleName = 'index';
|
||||||
|
let name = '';
|
||||||
|
switch (parts.length) {
|
||||||
|
case 2: {
|
||||||
|
name = parts[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
moduleName = parts[1];
|
||||||
|
name = parts[2];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
moduleName,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,14 @@ export interface ITangoViewNodeData<T = JSXElement> {
|
|||||||
children?: Array<ITangoViewNodeData<T>>;
|
children?: Array<ITangoViewNodeData<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入变量的解析数据
|
||||||
|
*/
|
||||||
|
export interface IImportSpecifierData {
|
||||||
|
source: string;
|
||||||
|
isDefault?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模块导入的参数类型
|
* 模块导入的参数类型
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ describe('engine', () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
expect(engine.workspace.activeViewModule).toBeUndefined();
|
expect(engine.workspace.activeViewModule).toBeUndefined();
|
||||||
expect(engine.workspace.serviceModule).toBeUndefined();
|
|
||||||
expect(engine.workspace.routeModule).toBeUndefined();
|
expect(engine.workspace.routeModule).toBeUndefined();
|
||||||
expect(engine.workspace?.storeModules.length).toEqual(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,51 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.5.1](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.0...@music163/tango-designer@0.5.1) (2023-10-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- add url validator ([8fefa63](https://github.com/netease/tango/commit/8fefa6344fc884355c32c4e62a5f1199ef31d65c))
|
||||||
|
- custom store template ([3f073ee](https://github.com/netease/tango/commit/3f073eeb436b7a929c9cad7af3daa15ba20e6be5))
|
||||||
|
- enhance expSetter ([#43](https://github.com/netease/tango/issues/43)) ([5ebbb42](https://github.com/netease/tango/commit/5ebbb428fb3fb786d330ab01959028443338d315))
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|
||||||
|
- support multiple service modules & refactor workspace ([a7df29c](https://github.com/netease/tango/commit/a7df29c3debc56b187792d3e203b470e9d368ea5))
|
||||||
|
|
||||||
|
## [0.2.1](https://github.com/netease/tango/compare/@music163/tango-designer@0.2.0...@music163/tango-designer@0.2.1) (2023-09-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @music163/tango-designer
|
||||||
|
|
||||||
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-designer@0.1.3...@music163/tango-designer@0.2.0) (2023-09-13)
|
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-designer@0.1.3...@music163/tango-designer@0.2.0) (2023-09-13)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@music163/tango-designer",
|
"name": "@music163/tango-designer",
|
||||||
"version": "0.2.0",
|
"version": "0.5.1",
|
||||||
"description": "lowcode designer",
|
"description": "lowcode designer",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react"
|
"react"
|
||||||
@@ -27,18 +27,18 @@
|
|||||||
"prepublishOnly": "yarn build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16.8.0",
|
"react": ">= 16.8",
|
||||||
"styled-components": "5"
|
"styled-components": ">= 4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.8.0",
|
"@ant-design/icons": "^4.8.0",
|
||||||
"@music163/request": "^0.1.0",
|
"@music163/request": "^0.1.0",
|
||||||
"@music163/tango-context": "^0.1.3",
|
"@music163/tango-context": "^0.2.3",
|
||||||
"@music163/tango-core": "^0.1.3",
|
"@music163/tango-core": "^0.2.2",
|
||||||
"@music163/tango-helpers": "^0.1.2",
|
"@music163/tango-helpers": "^0.1.4",
|
||||||
"@music163/tango-sandbox": "^0.1.3",
|
"@music163/tango-sandbox": "^0.1.7",
|
||||||
"@music163/tango-setting-form": "^0.2.0",
|
"@music163/tango-setting-form": "^0.2.6",
|
||||||
"@music163/tango-ui": "^0.1.3",
|
"@music163/tango-ui": "^0.1.7",
|
||||||
"antd": "^4.24.2",
|
"antd": "^4.24.2",
|
||||||
"cash-dom": "^8.1.2",
|
"cash-dom": "^8.1.2",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useRef, useEffect, memo } from 'react';
|
import React, { useState, useRef, useEffect, memo } from 'react';
|
||||||
import { Input } from 'antd';
|
import { Input } from 'antd';
|
||||||
import { css } from 'styled-components';
|
import { css, Box } from 'coral-system';
|
||||||
import { Box } from 'coral-system';
|
|
||||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const rowCss = css`
|
const rowCss = css`
|
||||||
|
|||||||
@@ -11,10 +11,15 @@ import {
|
|||||||
Empty,
|
Empty,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Alert,
|
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { Box, Text, css, Link } from 'coral-system';
|
import { Box, Text, css } from 'coral-system';
|
||||||
import { PlusOutlined, FunctionOutlined, DeleteOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
FunctionOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
CopyOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
filterTreeData,
|
filterTreeData,
|
||||||
isFunction,
|
isFunction,
|
||||||
@@ -96,10 +101,6 @@ export interface EditableVariableTreeProps {
|
|||||||
* 选择列表项时的回调
|
* 选择列表项时的回调
|
||||||
*/
|
*/
|
||||||
onSelect?: (data: IVariableTreeNode) => void;
|
onSelect?: (data: IVariableTreeNode) => void;
|
||||||
/**
|
|
||||||
* 点击使用此变量按钮时的回调
|
|
||||||
*/
|
|
||||||
onUse?: (data: IVariableTreeNode) => void;
|
|
||||||
/**
|
/**
|
||||||
* 保存结点定义的回调
|
* 保存结点定义的回调
|
||||||
*/
|
*/
|
||||||
@@ -124,10 +125,6 @@ export interface EditableVariableTreeProps {
|
|||||||
* 搜索框的后置结点
|
* 搜索框的后置结点
|
||||||
*/
|
*/
|
||||||
searchAddonAfter?: React.ReactNode;
|
searchAddonAfter?: React.ReactNode;
|
||||||
/**
|
|
||||||
* 是否展示预览值的提示信息
|
|
||||||
*/
|
|
||||||
showPreviewAlert?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewOptions = [
|
const previewOptions = [
|
||||||
@@ -145,11 +142,9 @@ export function EditableVariableTree({
|
|||||||
onDeleteStore = noop,
|
onDeleteStore = noop,
|
||||||
getPreviewValue = noop,
|
getPreviewValue = noop,
|
||||||
onSelect = noop,
|
onSelect = noop,
|
||||||
onUse = noop,
|
|
||||||
onSave = noop,
|
onSave = noop,
|
||||||
modes = ['add', 'preview', 'define'],
|
modes = ['add', 'preview', 'define'],
|
||||||
defaultMode = 'preview',
|
defaultMode = 'preview',
|
||||||
showPreviewAlert = true,
|
|
||||||
}: EditableVariableTreeProps) {
|
}: EditableVariableTreeProps) {
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
const [node, setNode] = useState<IVariableTreeNode>();
|
const [node, setNode] = useState<IVariableTreeNode>();
|
||||||
@@ -184,11 +179,16 @@ export function EditableVariableTree({
|
|||||||
</Box>
|
</Box>
|
||||||
<VariableTree
|
<VariableTree
|
||||||
dataSource={treeData}
|
dataSource={treeData}
|
||||||
|
showViewIcon
|
||||||
onSelect={(item) => {
|
onSelect={(item) => {
|
||||||
setNode(item);
|
setNode(item);
|
||||||
mode === 'add' && setMode(defaultMode);
|
mode === 'add' && setMode(defaultMode);
|
||||||
onSelect(item);
|
onSelect(item);
|
||||||
}}
|
}}
|
||||||
|
onView={(item) => {
|
||||||
|
setNode(item);
|
||||||
|
mode === 'add' && setMode(defaultMode);
|
||||||
|
}}
|
||||||
onAdd={(item) => {
|
onAdd={(item) => {
|
||||||
setNode(item);
|
setNode(item);
|
||||||
setMode('add');
|
setMode('add');
|
||||||
@@ -225,33 +225,15 @@ export function EditableVariableTree({
|
|||||||
borderRadius="m"
|
borderRadius="m"
|
||||||
position="sticky"
|
position="sticky"
|
||||||
top="0"
|
top="0"
|
||||||
bodyProps={{ px: 'l' }}
|
bodyProps={{ px: 'm' }}
|
||||||
>
|
>
|
||||||
{mode === 'preview' && (
|
{mode === 'preview' && (
|
||||||
<>
|
<ValuePreview
|
||||||
<Box mb="m" display={showPreviewAlert ? 'block' : 'none'}>
|
value={getPreviewValue(node)}
|
||||||
<Alert
|
onSelect={(valuePath) => {
|
||||||
message={
|
return ['tango', node.key.replaceAll('.', '?.'), valuePath].join('.');
|
||||||
<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('.');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{mode === 'define' && (
|
{mode === 'define' && (
|
||||||
<NodeDefineForm
|
<NodeDefineForm
|
||||||
@@ -308,7 +290,7 @@ export function EditableVariableTreeModal({
|
|||||||
{React.cloneElement(trigger, { onClick: on })}
|
{React.cloneElement(trigger, { onClick: on })}
|
||||||
<Modal
|
<Modal
|
||||||
title={title}
|
title={title}
|
||||||
visible={visible}
|
open={visible}
|
||||||
onCancel={off}
|
onCancel={off}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
disabled: !node,
|
disabled: !node,
|
||||||
@@ -345,6 +327,11 @@ const varTreeStyle = css`
|
|||||||
.ant-tree-indent-unit {
|
.ant-tree-indent-unit {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.anticon-function {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: var(--tango-colors-text2);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface VariableTreeProps {
|
interface VariableTreeProps {
|
||||||
@@ -352,17 +339,26 @@ interface VariableTreeProps {
|
|||||||
onSelect?: (data: IVariableTreeNode) => void;
|
onSelect?: (data: IVariableTreeNode) => void;
|
||||||
onAdd?: (data: IVariableTreeNode) => void;
|
onAdd?: (data: IVariableTreeNode) => void;
|
||||||
onRemove?: (data: IVariableTreeNode) => void;
|
onRemove?: (data: IVariableTreeNode) => void;
|
||||||
|
onCopy?: (data: IVariableTreeNode) => void;
|
||||||
|
onView?: (data: IVariableTreeNode) => void;
|
||||||
|
showDeleteIcon?: boolean;
|
||||||
|
showViewIcon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function VariableTree({
|
export function VariableTree({
|
||||||
dataSource = [],
|
dataSource = [],
|
||||||
onSelect = noop,
|
onSelect = noop,
|
||||||
onAdd = noop,
|
onAdd = noop,
|
||||||
onRemove = noop,
|
onRemove = noop,
|
||||||
|
onCopy = noop,
|
||||||
|
onView = noop,
|
||||||
|
showDeleteIcon = false,
|
||||||
|
showViewIcon = false,
|
||||||
}: VariableTreeProps) {
|
}: VariableTreeProps) {
|
||||||
return (
|
return (
|
||||||
<Box className="VariableTree" css={varTreeStyle}>
|
<Box className="VariableTree" css={varTreeStyle}>
|
||||||
<Tree.DirectoryTree
|
<Tree
|
||||||
|
blockNode
|
||||||
showIcon={false}
|
showIcon={false}
|
||||||
defaultExpandAll
|
defaultExpandAll
|
||||||
treeData={dataSource}
|
treeData={dataSource}
|
||||||
@@ -370,25 +366,18 @@ function VariableTree({
|
|||||||
onSelect(detail.node);
|
onSelect(detail.node);
|
||||||
}}
|
}}
|
||||||
titleRender={(node) => {
|
titleRender={(node) => {
|
||||||
if (node.showAddChildIcon || node.showRemoveIcon) {
|
const isLeaf = !node.children;
|
||||||
|
|
||||||
|
if (isLeaf) {
|
||||||
|
const isDeletable = node.showDeleteIcon ?? showDeleteIcon;
|
||||||
return (
|
return (
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center" pr="l">
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
<Text>{node.title}</Text>
|
<Text>
|
||||||
|
{node.title}
|
||||||
|
{node.type === 'function' && <FunctionOutlined />}
|
||||||
|
</Text>
|
||||||
<Box>
|
<Box>
|
||||||
{node.showAddChildIcon && (
|
{isDeletable && (
|
||||||
<Tooltip title={`向 ${node.title} 中添加变量`}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onAdd(node);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{node.showRemoveIcon && (
|
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确认删除吗?该操作会导致引用此模型的代码报错,请谨慎操作!"
|
title="确认删除吗?该操作会导致引用此模型的代码报错,请谨慎操作!"
|
||||||
onConfirm={() => onRemove(node)}
|
onConfirm={() => onRemove(node)}
|
||||||
@@ -403,14 +392,61 @@ function VariableTree({
|
|||||||
/>
|
/>
|
||||||
</Popconfirm>
|
</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>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||||
<Text mr="m">{node.title}</Text>
|
<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>
|
</Box>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface DesignerProps extends IDesignerContext, ITangoEngineContext {
|
|||||||
export function Designer(props: DesignerProps) {
|
export function Designer(props: DesignerProps) {
|
||||||
const {
|
const {
|
||||||
engine,
|
engine,
|
||||||
|
config,
|
||||||
theme: themeProp,
|
theme: themeProp,
|
||||||
sandboxQuery,
|
sandboxQuery,
|
||||||
remoteServices = {},
|
remoteServices = {},
|
||||||
@@ -42,7 +43,7 @@ export function Designer(props: DesignerProps) {
|
|||||||
return (
|
return (
|
||||||
<SystemProvider theme={theme} prefix="--tango">
|
<SystemProvider theme={theme} prefix="--tango">
|
||||||
<ConfigProvider locale={zhCN}>
|
<ConfigProvider locale={zhCN}>
|
||||||
<TangoEngineProvider value={{ engine }}>
|
<TangoEngineProvider value={{ engine, config }}>
|
||||||
<DesignerProvider value={{ sandboxQuery, remoteServices }}>{children}</DesignerProvider>
|
<DesignerProvider value={{ sandboxQuery, remoteServices }}>{children}</DesignerProvider>
|
||||||
</TangoEngineProvider>
|
</TangoEngineProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
|||||||
@@ -69,14 +69,6 @@ export const CodeEditor = observer(
|
|||||||
[workspace, autoRemoveUnusedImports, activeFile, files],
|
[workspace, autoRemoveUnusedImports, activeFile, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRenameFile = useCallback(
|
|
||||||
(oldFilename: string, newFilename: string) => {
|
|
||||||
workspace.renameFile(oldFilename, newFilename);
|
|
||||||
workspace.setActiveFile(newFilename);
|
|
||||||
},
|
|
||||||
[workspace],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleFileChange = useCallback(
|
const handleFileChange = useCallback(
|
||||||
(type: string, info: any) => {
|
(type: string, info: any) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -88,7 +80,12 @@ export const CodeEditor = observer(
|
|||||||
workspace.removeFile(info.path);
|
workspace.removeFile(info.path);
|
||||||
break;
|
break;
|
||||||
case 'renameFile':
|
case 'renameFile':
|
||||||
|
workspace.renameFile(info.path, info.newpath);
|
||||||
|
workspace.setActiveFile(info.newpath);
|
||||||
|
break;
|
||||||
case 'renameFolder':
|
case 'renameFolder':
|
||||||
|
workspace.renameFolder(info.path, info.newpath);
|
||||||
|
break;
|
||||||
case 'addFolder':
|
case 'addFolder':
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -114,7 +111,6 @@ export const CodeEditor = observer(
|
|||||||
}}
|
}}
|
||||||
ideConfig={ideConfig}
|
ideConfig={ideConfig}
|
||||||
onFileSave={fileSave}
|
onFileSave={fileSave}
|
||||||
onRenameFile={handleRenameFile}
|
|
||||||
onPathChange={handlePathChange}
|
onPathChange={handlePathChange}
|
||||||
onFileChange={handleFileChange}
|
onFileChange={handleFileChange}
|
||||||
defaultPath={activeFile}
|
defaultPath={activeFile}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export * from './sandbox';
|
|||||||
export * from './sidebar';
|
export * from './sidebar';
|
||||||
export * from './toolbar';
|
export * from './toolbar';
|
||||||
export * from './selection-menu';
|
export * from './selection-menu';
|
||||||
|
export * from './widgets';
|
||||||
|
|
||||||
export { register as registerSetter } from '@music163/tango-setting-form';
|
export { register as registerSetter } from '@music163/tango-setting-form';
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ interface ISandboxEventHandlerConfig {
|
|||||||
export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'onMessage'> & {
|
export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'onMessage'> & {
|
||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
selectionTools?: SelectionToolsProps['actions'];
|
selectionTools?: SelectionToolsProps['actions'];
|
||||||
builtinSelectionMenuMap?: SelectionToolsProps['builtinActionMap'];
|
|
||||||
/**
|
/**
|
||||||
* tangoConfigJson 处理器
|
* tangoConfigJson 处理器
|
||||||
*/
|
*/
|
||||||
@@ -32,10 +31,10 @@ export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'o
|
|||||||
onLoad?: (config?: ISandboxEventHandlerConfig) => void;
|
onLoad?: (config?: ISandboxEventHandlerConfig) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CombinedSandboxRef = {
|
export interface CombinedSandboxRef {
|
||||||
designSandbox: CodeSandbox;
|
designSandbox: CodeSandbox;
|
||||||
previewSandbox: CodeSandbox;
|
previewSandbox: CodeSandbox;
|
||||||
};
|
}
|
||||||
|
|
||||||
const LANDING_PAGE_PATH = '/__background_landing_page__';
|
const LANDING_PAGE_PATH = '/__background_landing_page__';
|
||||||
|
|
||||||
@@ -281,7 +280,6 @@ export const Sandbox = observer(
|
|||||||
({
|
({
|
||||||
isPreview: isPreviewProp,
|
isPreview: isPreviewProp,
|
||||||
selectionTools,
|
selectionTools,
|
||||||
builtinSelectionMenuMap,
|
|
||||||
bundlerURL,
|
bundlerURL,
|
||||||
mode = 'combined',
|
mode = 'combined',
|
||||||
...props
|
...props
|
||||||
@@ -352,7 +350,7 @@ export const Sandbox = observer(
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Simulator>
|
<Simulator>
|
||||||
<Viewport selectionTools={selectionTools} builtinActionMap={builtinSelectionMenuMap}>
|
<Viewport selectionTools={selectionTools}>
|
||||||
{mode === 'single' && (
|
{mode === 'single' && (
|
||||||
<DesignSandbox
|
<DesignSandbox
|
||||||
ref={sandboxRef}
|
ref={sandboxRef}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch } from 'antd';
|
import { Switch } from 'antd';
|
||||||
import { FormItemComponentProps } from '../form-item';
|
import { FormItemComponentProps } from '@music163/tango-setting-form/src/form-item';
|
||||||
|
|
||||||
export function BoolSetter({ value, onChange, ...props }: FormItemComponentProps<boolean>) {
|
export function BoolSetter({ value, onChange, ...props }: FormItemComponentProps<boolean>) {
|
||||||
return <Switch checked={value} onChange={(val) => onChange?.(val)} {...props} />;
|
return <Switch checked={value} onChange={(val) => onChange?.(val)} {...props} />;
|
||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Radio, RadioProps, Tooltip } from 'antd';
|
import { Radio, RadioProps, Tooltip } from 'antd';
|
||||||
import { IconFont } from '@music163/tango-ui';
|
import { IconFont } from '@music163/tango-ui';
|
||||||
import type { OptionType } from '@music163/tango-helpers';
|
import type { OptionType } from '@music163/tango-helpers';
|
||||||
import { FormItemComponentProps } from '../form-item';
|
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||||
|
|
||||||
interface ChoiceSetterProps {
|
interface ChoiceSetterProps {
|
||||||
mode?: 'text' | 'icon';
|
mode?: 'text' | 'icon';
|
||||||
@@ -3,7 +3,8 @@ import { Box } from 'coral-system';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { toJSON } from 'cssjson';
|
import { toJSON } from 'cssjson';
|
||||||
import { InputNumber, Space } from 'antd';
|
import { InputNumber, Space } from 'antd';
|
||||||
import { FormItemComponentProps, SliderSetter } from '@music163/tango-setting-form';
|
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||||
|
import { SliderSetter } from './number-setter';
|
||||||
import {
|
import {
|
||||||
BgSetter,
|
BgSetter,
|
||||||
BorderSetter,
|
BorderSetter,
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { AutoComplete } from 'antd';
|
|||||||
import { ActionSelect } from '@music163/tango-ui';
|
import { ActionSelect } from '@music163/tango-ui';
|
||||||
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||||
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||||
import { ExpressionModal, getInputValue } from './expression-setter';
|
import { wrapCode } from '@music163/tango-helpers';
|
||||||
|
import { ExpressionModal } from './expression-setter';
|
||||||
|
|
||||||
enum EventAction {
|
enum EventAction {
|
||||||
NoAction = 'noAction',
|
NoAction = 'noAction',
|
||||||
@@ -43,7 +44,7 @@ export function EventSetter(props: EventSetterProps) {
|
|||||||
|
|
||||||
const { actionVariables, routeOptions } = useWorkspaceData();
|
const { actionVariables, routeOptions } = useWorkspaceData();
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const modalOptions = workspace.listModals() || [];
|
const modalOptions = workspace.activeViewModule.listModals() || [];
|
||||||
|
|
||||||
const handleChange = useCallback<FormItemComponentProps['onChange']>(
|
const handleChange = useCallback<FormItemComponentProps['onChange']>(
|
||||||
(nextValue: any, ...args) => {
|
(nextValue: any, ...args) => {
|
||||||
@@ -71,19 +72,19 @@ export function EventSetter(props: EventSetterProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const actionText = getActionText(type, temp, value);
|
const actionText = getActionText(type, temp, value);
|
||||||
const inputValue = getInputValue(value);
|
const inputValue = value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box css={wrapperStyle}>
|
<Box css={wrapperStyle}>
|
||||||
<ActionSelect options={options} onSelect={onAction} text={actionText} />
|
<ActionSelect options={options} onSelect={onAction} text={actionText} />
|
||||||
<ExpressionModal
|
<ExpressionModal
|
||||||
title={modalTitle}
|
title={modalTitle}
|
||||||
defaultValue={inputValue}
|
value={inputValue}
|
||||||
visible={expModalVisible}
|
visible={expModalVisible}
|
||||||
onCancel={() => setExpModalVisible(false)}
|
onCancel={() => setExpModalVisible(false)}
|
||||||
onOk={(nextValue) => {
|
onOk={(nextValue) => {
|
||||||
setExpModalVisible(false);
|
setExpModalVisible(false);
|
||||||
handleChange(nextValue);
|
handleChange(wrapCode(nextValue));
|
||||||
}}
|
}}
|
||||||
dataSource={actionVariables}
|
dataSource={actionVariables}
|
||||||
/>
|
/>
|
||||||
@@ -141,7 +142,7 @@ function getActionText(type: EventAction, temp: string, value: any) {
|
|||||||
if (handlerMap[type]) {
|
if (handlerMap[type]) {
|
||||||
text = getExpressionValue(type, temp);
|
text = getExpressionValue(type, temp);
|
||||||
} else if (value) {
|
} else if (value) {
|
||||||
text = getInputValue(value);
|
text = value;
|
||||||
}
|
}
|
||||||
text = text || '请选择';
|
text = text || '请选择';
|
||||||
return text;
|
return text;
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { Box, css } from 'coral-system';
|
import { Box, Text, css } from 'coral-system';
|
||||||
import { Alert, Modal, Tabs } from 'antd';
|
import { Modal } from 'antd';
|
||||||
import { value2code, isValidExpressionCode } from '@music163/tango-core';
|
import { isValidExpressionCode, value2expressionCode } from '@music163/tango-core';
|
||||||
import {
|
import {
|
||||||
isVariableString,
|
isVariableString,
|
||||||
getVariableContent,
|
getVariableContent,
|
||||||
noop,
|
noop,
|
||||||
useBoolean,
|
useBoolean,
|
||||||
getValue,
|
getValue,
|
||||||
|
wrapCode,
|
||||||
} from '@music163/tango-helpers';
|
} from '@music163/tango-helpers';
|
||||||
import { CloseCircleFilled, ExpandAltOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
import { CloseCircleFilled, ExpandAltOutlined } from '@ant-design/icons';
|
||||||
import { IconButton, Panel, InputCode, ChatInput } from '@music163/tango-ui';
|
import { IconButton, Panel, InputCode } from '@music163/tango-ui';
|
||||||
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||||
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||||
import { EditableVariableTree, IVariableTreeNode } from '../components';
|
import { EditableVariableTree, IVariableTreeNode } from '../components';
|
||||||
import { useRemoteServices, useSandboxQuery } from '../context';
|
import { useSandboxQuery } from '../context';
|
||||||
|
|
||||||
export const expressionValueValidate = (value: string) => {
|
export const expressionValueValidate = (value: string) => {
|
||||||
if (isVariableString(value)) {
|
if (isVariableString(value)) {
|
||||||
@@ -36,31 +37,6 @@ export const jsonValueValidate = (value: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: 这里应该用原始的代码 raw value
|
|
||||||
export const getInputValue = (val: any) => {
|
|
||||||
if (!val) return '';
|
|
||||||
|
|
||||||
let ret;
|
|
||||||
|
|
||||||
switch (typeof val) {
|
|
||||||
case 'string':
|
|
||||||
ret = val;
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
ret = String(val);
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
ret = value2code(val);
|
|
||||||
ret = `{${ret}}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ret = '';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
const suffixStyle = css`
|
const suffixStyle = css`
|
||||||
.anticon-close-circle {
|
.anticon-close-circle {
|
||||||
color: rgba(0, 0, 0, 0.25);
|
color: rgba(0, 0, 0, 0.25);
|
||||||
@@ -83,32 +59,37 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
|
|||||||
modalTitle,
|
modalTitle,
|
||||||
modalTip,
|
modalTip,
|
||||||
autoCompleteOptions,
|
autoCompleteOptions,
|
||||||
placeholder = '表达式请使用 {} 包裹',
|
placeholder = '输入JS表达式代码',
|
||||||
value: valueProp,
|
value: valueProp,
|
||||||
status,
|
status,
|
||||||
allowClear = true,
|
allowClear = true,
|
||||||
} = props;
|
} = props;
|
||||||
const [visible, { on, off }] = useBoolean();
|
const [visible, { on, off }] = useBoolean();
|
||||||
const [inputValue, setInputValue] = useState(() => {
|
const [inputValue, setInputValue] = useState(() => {
|
||||||
return getInputValue(valueProp);
|
return value2expressionCode(valueProp);
|
||||||
});
|
});
|
||||||
const sandbox = useSandboxQuery();
|
const sandbox = useSandboxQuery();
|
||||||
const evaluateContext = sandbox.window;
|
const evaluateContext = sandbox.window;
|
||||||
|
|
||||||
// when receive new value, sync state
|
// when receive new value, sync state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInputValue(getInputValue(valueProp));
|
setInputValue(value2expressionCode(valueProp));
|
||||||
}, [valueProp]);
|
}, [valueProp]);
|
||||||
|
|
||||||
const change = (code: string) => {
|
const change = useCallback(
|
||||||
if (!code) {
|
(code: string) => {
|
||||||
code = undefined;
|
if (!code) {
|
||||||
}
|
onChange(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (getVariableContent(code) === value2expressionCode(valueProp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (code !== valueProp) {
|
onChange(wrapCode(code));
|
||||||
onChange(code);
|
},
|
||||||
}
|
[valueProp, onChange],
|
||||||
};
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="ExpressionSetter">
|
<Box className="ExpressionSetter">
|
||||||
@@ -147,7 +128,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
autoCompleteOptions={autoCompleteOptions}
|
autoCompleteOptions={autoCompleteOptions}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
defaultValue={inputValue}
|
value={inputValue}
|
||||||
onCancel={() => off()}
|
onCancel={() => off()}
|
||||||
onOk={(value) => {
|
onOk={(value) => {
|
||||||
onChange(value);
|
onChange(value);
|
||||||
@@ -164,6 +145,7 @@ export interface ExpressionModalProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
|
value?: string;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
onOk?: (value: string) => void;
|
onOk?: (value: string) => void;
|
||||||
dataSource?: IVariableTreeNode[];
|
dataSource?: IVariableTreeNode[];
|
||||||
@@ -178,10 +160,12 @@ export function ExpressionModal({
|
|||||||
onCancel = noop,
|
onCancel = noop,
|
||||||
onOk = noop,
|
onOk = noop,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
value,
|
||||||
dataSource,
|
dataSource,
|
||||||
autoCompleteOptions,
|
autoCompleteOptions,
|
||||||
}: ExpressionModalProps) {
|
}: ExpressionModalProps) {
|
||||||
const [exp, setExp] = useState(defaultValue);
|
const [exp, setExp] = useState(value ?? defaultValue);
|
||||||
|
const [error, setError] = useState('');
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const onAction = useCallback(
|
const onAction = useCallback(
|
||||||
(action: string, args: unknown[]) => {
|
(action: string, args: unknown[]) => {
|
||||||
@@ -190,38 +174,35 @@ export function ExpressionModal({
|
|||||||
[workspace],
|
[workspace],
|
||||||
);
|
);
|
||||||
const sandbox = useSandboxQuery();
|
const sandbox = useSandboxQuery();
|
||||||
const remoteServices = useRemoteServices();
|
|
||||||
const { expressionVariables } = useWorkspaceData();
|
const { expressionVariables } = useWorkspaceData();
|
||||||
const evaluateContext = sandbox.window;
|
const evaluateContext = sandbox.window;
|
||||||
const gptService = remoteServices?.GptService;
|
|
||||||
|
|
||||||
const handleExpInputChange = (val: string) => {
|
const handleExpInputChange = (val: string) => {
|
||||||
setExp(val?.trim());
|
setExp(val?.trim());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExp(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={title}
|
closable={false}
|
||||||
|
destroyOnClose
|
||||||
width="70%"
|
width="70%"
|
||||||
open={visible}
|
open={visible}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
onOk(exp);
|
onOk(exp);
|
||||||
}}
|
}}
|
||||||
destroyOnClose
|
okButtonProps={{
|
||||||
|
disabled: !!error,
|
||||||
|
}}
|
||||||
|
bodyStyle={{
|
||||||
|
padding: 0,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Panel
|
<Panel title={`将 ${title} 设置为引用变量或自定义表达式`} subTitle={subTitle} shape="solid">
|
||||||
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"
|
|
||||||
>
|
|
||||||
<InputCode
|
<InputCode
|
||||||
shape="inset"
|
shape="inset"
|
||||||
minHeight="56px"
|
minHeight="56px"
|
||||||
@@ -229,95 +210,58 @@ export function ExpressionModal({
|
|||||||
value={exp}
|
value={exp}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={handleExpInputChange}
|
onChange={handleExpInputChange}
|
||||||
|
onBlur={() => {
|
||||||
|
setError(expressionValueValidate(exp));
|
||||||
|
}}
|
||||||
autoCompleteContext={evaluateContext}
|
autoCompleteContext={evaluateContext}
|
||||||
autoCompleteOptions={autoCompleteOptions}
|
autoCompleteOptions={autoCompleteOptions}
|
||||||
/>
|
/>
|
||||||
|
{error ? <Text color="red">输入的表达式存在语法错误,请修改后再提交!</Text> : null}
|
||||||
</Panel>
|
</Panel>
|
||||||
<Box mt="m" maxHeight={400} overflow="auto">
|
<Panel title="从变量列表中选中" shape="solid" borderTop="0">
|
||||||
<Tabs type="card" size="small">
|
<EditableVariableTree
|
||||||
<Tabs.TabPane key="list" tab="从变量列表中选择">
|
height={380}
|
||||||
<Box my="m">
|
dataSource={dataSource || expressionVariables}
|
||||||
<Alert
|
onSelect={(node) => {
|
||||||
type="info"
|
if (!node.key) {
|
||||||
message="你可以直接从下方的列表中选择已定义的变量"
|
return;
|
||||||
banner
|
}
|
||||||
closable
|
if (node.key.split('.').length < 2) {
|
||||||
/>
|
return;
|
||||||
</Box>
|
}
|
||||||
<EditableVariableTree
|
let str;
|
||||||
// height={400}
|
if (/^(stores|services)\./.test(node.key)) {
|
||||||
dataSource={dataSource || expressionVariables}
|
str = `{tango.${node.key.replaceAll('.', '?.')}}`;
|
||||||
onUse={(node) => {
|
} else {
|
||||||
let str;
|
str = `{${node.key}}`;
|
||||||
if (/^(stores|services)\./.test(node.key)) {
|
}
|
||||||
str = `{tango.${node.key.replaceAll('.', '?.')}}`;
|
setExp(str);
|
||||||
} else {
|
}}
|
||||||
str = `{${node.key}}`;
|
onAddVariable={(storeName, data) => {
|
||||||
}
|
onAction('addStoreState', [storeName, data.name, data.initialValue]);
|
||||||
setExp(str);
|
}}
|
||||||
}}
|
onDeleteVariable={(storeName, stateName) => {
|
||||||
onAddVariable={(storeName, data) => {
|
onAction('removeStoreState', [storeName, stateName]);
|
||||||
onAction('addStoreState', [storeName, data.name, data.initialValue]);
|
}}
|
||||||
}}
|
onDeleteStore={(storeName) => {
|
||||||
onDeleteVariable={(storeName, stateName) => {
|
onAction('removeStoreModule', [storeName]);
|
||||||
onAction('removeStoreState', [storeName, stateName]);
|
}}
|
||||||
}}
|
onSave={(code, node) => {
|
||||||
onDeleteStore={(storeName) => {
|
onAction('updateModuleCodeByVariablePath', [node.key, code]);
|
||||||
onAction('removeStoreModule', [storeName]);
|
}}
|
||||||
}}
|
getPreviewValue={(node) => {
|
||||||
onSave={(code, node) => {
|
if (!node || !node.key) {
|
||||||
onAction('updateModuleCodeByVariablePath', [node.key, code]);
|
return;
|
||||||
}}
|
}
|
||||||
getPreviewValue={(node) => {
|
|
||||||
if (!node || !node.key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === 'function') {
|
if (node.type === 'function') {
|
||||||
return node.raw;
|
return node.raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getValue(evaluateContext['tango'], node.key);
|
return getValue(evaluateContext['tango'], node.key);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Panel>
|
||||||
<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>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExpressionCode(userInput: string) {
|
|
||||||
return `你是一个代码生成助手。你使用的编程语言是 JavaScript。不要解释代码,只返回代码块本身,代码必须包裹在 {} 内部。
|
|
||||||
标准的输出格式如下:
|
|
||||||
1. {this.a}
|
|
||||||
2. {() => {}}
|
|
||||||
3. {tango.stores.user.name}
|
|
||||||
|
|
||||||
${userInput}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,8 +25,38 @@ import {
|
|||||||
FlexAlignItemsSetter,
|
FlexAlignItemsSetter,
|
||||||
FlexDirectionSetter,
|
FlexDirectionSetter,
|
||||||
} from './style-setter';
|
} from './style-setter';
|
||||||
|
import { BoolSetter } from './bool-setter';
|
||||||
|
import { ChoiceSetter } from './choice-setter';
|
||||||
|
import { NumberSetter, SliderSetter } from './number-setter';
|
||||||
|
import { TextAreaSetter, TextSetter } from './text-setter';
|
||||||
|
|
||||||
const setters: IFormItemCreateOptions[] = [
|
const setters: IFormItemCreateOptions[] = [
|
||||||
|
{
|
||||||
|
name: 'boolSetter',
|
||||||
|
component: BoolSetter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'choiceSetter',
|
||||||
|
component: ChoiceSetter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expSetter',
|
||||||
|
alias: ['expressionSetter'],
|
||||||
|
component: ExpressionSetter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'numberSetter',
|
||||||
|
component: NumberSetter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'textSetter',
|
||||||
|
component: TextSetter,
|
||||||
|
},
|
||||||
|
{ name: 'textAreaSetter', component: TextAreaSetter },
|
||||||
|
{
|
||||||
|
name: 'sliderSetter',
|
||||||
|
component: SliderSetter,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'actionListSetter',
|
name: 'actionListSetter',
|
||||||
component: ActionListSetter,
|
component: ActionListSetter,
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ export function ModelSetter({ value, onChange }: FormItemComponentProps) {
|
|||||||
onBlur={onInputBlur}
|
onBlur={onInputBlur}
|
||||||
suffix={
|
suffix={
|
||||||
<EditableVariableTreeModal
|
<EditableVariableTreeModal
|
||||||
showPreviewAlert={false}
|
|
||||||
title="同步到的变量"
|
title="同步到的变量"
|
||||||
trigger={
|
trigger={
|
||||||
<Tooltip title="从模型列表选择" placement="topRight">
|
<Tooltip title="从模型列表选择" placement="topRight">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InputNumber, InputNumberProps, Slider } from 'antd';
|
import { InputNumber, InputNumberProps, Slider } from 'antd';
|
||||||
import { FormItemComponentProps } from '../form-item';
|
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -5,7 +5,9 @@ import { SketchPicker } from 'react-color';
|
|||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
import { BgColorsOutlined, FileImageOutlined } from '@ant-design/icons';
|
import { BgColorsOutlined, FileImageOutlined } from '@ant-design/icons';
|
||||||
import { SingleMonacoEditor, IconFont } from '@music163/tango-ui';
|
import { SingleMonacoEditor, IconFont } from '@music163/tango-ui';
|
||||||
import { FormItemComponentProps, ChoiceSetter, TextSetter } from '@music163/tango-setting-form';
|
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||||
|
import { ChoiceSetter } from './choice-setter';
|
||||||
|
import { TextSetter } from './text-setter';
|
||||||
// import { ImageSetter } from './image-setter';
|
// import { ImageSetter } from './image-setter';
|
||||||
|
|
||||||
function getRawCssValue(value: string) {
|
function getRawCssValue(value: string) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Input, InputProps } from 'antd';
|
import { Input, InputProps } from 'antd';
|
||||||
import { FormItemComponentProps } from '../form-item';
|
import { FormItemComponentProps } from '@music163/tango-setting-form';
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { observer, useWorkspace } from '@music163/tango-context';
|
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||||
import { Box, css } from 'coral-system';
|
import { Box, css } from 'coral-system';
|
||||||
import { Button, FormProps, Empty, Space, Dropdown } from 'antd';
|
import { Button, FormProps, Empty, Space, Dropdown } from 'antd';
|
||||||
import { PlayCircleOutlined } from '@ant-design/icons';
|
import { PlayCircleOutlined } from '@ant-design/icons';
|
||||||
import { Form, InputCode, Menu, Panel, JsonView, Search } from '@music163/tango-ui';
|
import { Form, InputCode, Panel, JsonView, Search } from '@music163/tango-ui';
|
||||||
import {
|
import {
|
||||||
isNil,
|
isNil,
|
||||||
isVariableString,
|
isVariableString,
|
||||||
@@ -11,8 +11,10 @@ import {
|
|||||||
isValidFunctionCode,
|
isValidFunctionCode,
|
||||||
logger,
|
logger,
|
||||||
code2object,
|
code2object,
|
||||||
|
filterTreeData,
|
||||||
} from '@music163/tango-helpers';
|
} from '@music163/tango-helpers';
|
||||||
import { useSandboxQuery } from '../../context';
|
import { useSandboxQuery } from '../../context';
|
||||||
|
import { VariableTree } from '../../components';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 服务函数的操作类型
|
* 服务函数的操作类型
|
||||||
@@ -39,7 +41,7 @@ enum ServiceFunctionMethodType {
|
|||||||
* requestType
|
* requestType
|
||||||
* headers: Content-Type
|
* headers: Content-Type
|
||||||
*/
|
*/
|
||||||
enum ESFHTTRequestType {
|
enum DataServiceRequestType {
|
||||||
'application/json' = 'json',
|
'application/json' = 'json',
|
||||||
'application/x-www-form-urlencoded' = 'x-www-form-urlencoded',
|
'application/x-www-form-urlencoded' = 'x-www-form-urlencoded',
|
||||||
}
|
}
|
||||||
@@ -65,11 +67,11 @@ export const getApiDefaultName = (url: string) =>
|
|||||||
// 方法名以数字开头,添加 api 前缀
|
// 方法名以数字开头,添加 api 前缀
|
||||||
.replace(/^\d/, (str) => `api${str}`);
|
.replace(/^\d/, (str) => `api${str}`);
|
||||||
|
|
||||||
type DataServiceViewProps = {
|
interface DataServiceViewProps {
|
||||||
onAdd?: (values: Record<string, string> | Array<Record<string, string>>) => void;
|
onAdd?: (values: Record<string, string> | Array<Record<string, string>>) => void;
|
||||||
onUpdate?: (values: Record<string, string>) => void;
|
onUpdate?: (values: Record<string, string>) => void;
|
||||||
onDelete?: (values: Record<string, string>) => void;
|
onDelete?: (values: Record<string, string>) => void;
|
||||||
};
|
}
|
||||||
|
|
||||||
// http 方法类型
|
// http 方法类型
|
||||||
const httpMethods = Object.keys(ServiceFunctionMethodType).map((key) => ({
|
const httpMethods = Object.keys(ServiceFunctionMethodType).map((key) => ({
|
||||||
@@ -78,9 +80,9 @@ const httpMethods = Object.keys(ServiceFunctionMethodType).map((key) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// requestType 类型
|
// requestType 类型
|
||||||
const requestTypes = Object.keys(ESFHTTRequestType).map((key) => ({
|
const requestTypes = Object.keys(DataServiceRequestType).map((key) => ({
|
||||||
label: key,
|
label: key,
|
||||||
value: ESFHTTRequestType[key],
|
value: DataServiceRequestType[key],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const detailFormStyle = css`
|
const detailFormStyle = css`
|
||||||
@@ -90,31 +92,32 @@ const detailFormStyle = css`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewProps) => {
|
const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewProps) => {
|
||||||
const [serviceKey, setServiceKey] = useState<string>();
|
const [serviceData, setServiceData] = useState<any>();
|
||||||
const [keyword, setKeyword] = useState<string>();
|
const [keyword, setKeyword] = useState<string>();
|
||||||
const [visible, setVisible] = useState(null);
|
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const sandbox = useSandboxQuery();
|
const sandbox = useSandboxQuery();
|
||||||
|
const { serviceVariables } = useWorkspaceData();
|
||||||
|
const serviceModules = Object.keys(workspace.serviceModules).map((key) => ({
|
||||||
|
label: key === 'index' ? '默认模块' : key,
|
||||||
|
value: key,
|
||||||
|
}));
|
||||||
|
const serviceFunctions = serviceVariables.reduce((acc, cur) => {
|
||||||
|
acc = acc.concat(cur.children.map((item: any) => item.key));
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
const dataSource = useMemo(() => {
|
||||||
|
if (!keyword) {
|
||||||
|
return serviceVariables;
|
||||||
|
}
|
||||||
|
return filterTreeData(
|
||||||
|
serviceVariables,
|
||||||
|
(leaf) => leaf.title.includes(keyword),
|
||||||
|
'children',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}, [serviceVariables, keyword]);
|
||||||
|
|
||||||
const serviceMap = workspace.serviceModule.serviceFunctions;
|
const isAddMode = serviceData && !serviceData.name;
|
||||||
const serviceItems = useMemo(() => {
|
|
||||||
if (!serviceMap) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
let list = Object.keys(serviceMap).map((key) => ({
|
|
||||||
key,
|
|
||||||
label: key,
|
|
||||||
note: serviceMap[key].app || 'http',
|
|
||||||
deletable: true,
|
|
||||||
deletableConfirm: '确定删除此服务函数吗?',
|
|
||||||
}));
|
|
||||||
if (keyword) {
|
|
||||||
list = list.filter((item) => item.label.includes(keyword));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}, [serviceMap, keyword]);
|
|
||||||
const serviceData = serviceMap?.[serviceKey];
|
|
||||||
const isAddMode = !serviceKey;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="ServiceFunctionList" display="flex" borderTopColor="line.normal">
|
<Box className="ServiceFunctionList" display="flex" borderTopColor="line.normal">
|
||||||
@@ -136,52 +139,48 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
|
|||||||
<Button
|
<Button
|
||||||
block
|
block
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setServiceKey(undefined);
|
setServiceData({});
|
||||||
setVisible('detail');
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
新建服务函数
|
新建服务函数
|
||||||
</Button>
|
</Button>
|
||||||
<Search placeholder="搜索服务函数" onChange={setKeyword} />
|
<Search placeholder="搜索服务函数" onChange={setKeyword} />
|
||||||
</Box>
|
</Box>
|
||||||
<Menu
|
<VariableTree
|
||||||
activeKey={serviceKey}
|
dataSource={dataSource}
|
||||||
items={serviceItems}
|
showDeleteIcon
|
||||||
onItemClick={(key) => {
|
onSelect={(item) => {
|
||||||
setServiceKey(key);
|
setServiceData({ key: item.key, ...workspace.getServiceFunction(item.key) });
|
||||||
setVisible('detail');
|
|
||||||
}}
|
}}
|
||||||
onDelete={(key) => {
|
onRemove={(item) => {
|
||||||
const values = {
|
workspace.removeServiceFunction(item.key);
|
||||||
name: key,
|
onDelete?.(item);
|
||||||
...workspace.serviceModule.serviceFunctions[key],
|
|
||||||
};
|
|
||||||
workspace.removeServiceFunction(key);
|
|
||||||
onDelete && onDelete(values);
|
|
||||||
setVisible(null);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="65%" overflow="auto" css={detailFormStyle}>
|
<Box width="65%" overflow="auto" css={detailFormStyle}>
|
||||||
{visible === 'detail' && (
|
{serviceData && (
|
||||||
<>
|
<>
|
||||||
<Panel title={isAddMode ? '新建服务函数' : '服务函数详情'}>
|
<Panel title={isAddMode ? '新建服务函数' : '服务函数详情'}>
|
||||||
<ServiceDetailForm
|
<ServiceDetailForm
|
||||||
key={serviceKey || 'createSF'}
|
key={serviceData.key || 'createSF'}
|
||||||
serviceKeys={Object.keys(serviceMap)}
|
serviceModules={serviceModules}
|
||||||
|
serviceKeys={serviceFunctions}
|
||||||
initialValues={
|
initialValues={
|
||||||
isAddMode
|
isAddMode
|
||||||
? {}
|
? {
|
||||||
|
moduleName: 'index',
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
name: serviceKey,
|
name: serviceData.name,
|
||||||
|
moduleName: serviceData.moduleName,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
...serviceData,
|
...serviceData.config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setServiceKey(undefined);
|
setServiceData(undefined);
|
||||||
setVisible(null);
|
|
||||||
}}
|
}}
|
||||||
onSubmit={(values, mode: ServiceFunctionOperationModeType) => {
|
onSubmit={(values, mode: ServiceFunctionOperationModeType) => {
|
||||||
function shapeServiceValues(val: any) {
|
function shapeServiceValues(val: any) {
|
||||||
@@ -194,28 +193,27 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
|
|||||||
return shapeValues;
|
return shapeValues;
|
||||||
}
|
}
|
||||||
// 移除掉不必要的属性
|
// 移除掉不必要的属性
|
||||||
const data = shapeServiceValues(values);
|
const { moduleName, ...data } = shapeServiceValues(values);
|
||||||
if (mode === ServiceFunctionOperationModeType.ADD) {
|
if (mode === ServiceFunctionOperationModeType.ADD) {
|
||||||
workspace.addServiceFunction(data);
|
workspace.addServiceFunction(data, moduleName);
|
||||||
onAdd && onAdd(values);
|
onAdd && onAdd(values);
|
||||||
} else if (mode === ServiceFunctionOperationModeType.UPDATE) {
|
} else if (mode === ServiceFunctionOperationModeType.UPDATE) {
|
||||||
workspace.updateServiceFunction(data);
|
workspace.updateServiceFunction(data, moduleName);
|
||||||
onUpdate && onUpdate(values);
|
onUpdate && onUpdate(values);
|
||||||
}
|
}
|
||||||
setServiceKey(values.name);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
{serviceKey ? (
|
{serviceData?.key ? (
|
||||||
<ServiceFunctionPreview
|
<ServiceFunctionPreview
|
||||||
key={serviceKey}
|
key={serviceData.key}
|
||||||
appContext={sandbox?.window['tango']}
|
appContext={sandbox?.window['tango']}
|
||||||
functionName={serviceKey}
|
functionName={serviceData.key}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!visible && (
|
{!serviceData && (
|
||||||
<Box py="xxl">
|
<Box py="xxl">
|
||||||
<Empty description="请从左侧选择数据服务函数或新建数据服务函数" />
|
<Empty description="请从左侧选择数据服务函数或新建数据服务函数" />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -227,12 +225,14 @@ const DataSourceView = observer(({ onAdd, onUpdate, onDelete }: DataServiceViewP
|
|||||||
|
|
||||||
interface ServiceDetailFormProps extends FormProps {
|
interface ServiceDetailFormProps extends FormProps {
|
||||||
serviceKeys?: string[];
|
serviceKeys?: string[];
|
||||||
|
serviceModules?: any[];
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
onSubmit?: (values: any, mode: ServiceFunctionOperationModeType) => void;
|
onSubmit?: (values: any, mode: ServiceFunctionOperationModeType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ServiceDetailForm({
|
function ServiceDetailForm({
|
||||||
serviceKeys = [],
|
serviceKeys = [],
|
||||||
|
serviceModules = [],
|
||||||
onCancel,
|
onCancel,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -274,6 +274,20 @@ function ServiceDetailForm({
|
|||||||
}}
|
}}
|
||||||
{...formProps}
|
{...formProps}
|
||||||
>
|
>
|
||||||
|
<Form.Item
|
||||||
|
label="所属模块"
|
||||||
|
name="moduleName"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
component="select"
|
||||||
|
componentProps={{
|
||||||
|
options: serviceModules,
|
||||||
|
disabled: disabled || isModifyMode,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="方法名"
|
label="方法名"
|
||||||
name="name"
|
name="name"
|
||||||
@@ -282,7 +296,9 @@ function ServiceDetailForm({
|
|||||||
{ pattern: /^[a-z]\w+$/, message: '请输入合法的方法名称' },
|
{ pattern: /^[a-z]\w+$/, message: '请输入合法的方法名称' },
|
||||||
!isModifyMode && {
|
!isModifyMode && {
|
||||||
validator(_, value) {
|
validator(_, value) {
|
||||||
const isExist = serviceKeys.includes(value);
|
const isExist = serviceKeys.includes(
|
||||||
|
['services', form.getFieldValue('moduleName'), value].join('.'),
|
||||||
|
);
|
||||||
return isExist
|
return isExist
|
||||||
? Promise.reject(new Error('重复的方法名,请换一个!'))
|
? Promise.reject(new Error('重复的方法名,请换一个!'))
|
||||||
: Promise.resolve();
|
: Promise.resolve();
|
||||||
@@ -316,7 +332,7 @@ function ServiceDetailForm({
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label="路径"
|
label="路径"
|
||||||
name="url"
|
name="url"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true, type: 'url' }]}
|
||||||
component="input"
|
component="input"
|
||||||
componentProps={{
|
componentProps={{
|
||||||
placeholder: '请输入数据服务调用名称',
|
placeholder: '请输入数据服务调用名称',
|
||||||
|
|||||||
@@ -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 { observer, useWorkspace } from '@music163/tango-context';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -9,15 +9,17 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Radio,
|
Radio,
|
||||||
Select,
|
|
||||||
message,
|
message,
|
||||||
Tag,
|
Tag,
|
||||||
|
Checkbox,
|
||||||
|
ModalProps,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
|
import { FormListProps } from 'antd/lib/form';
|
||||||
import { Box, Text } from 'coral-system';
|
import { Box, Text } from 'coral-system';
|
||||||
import semverLt from 'semver/functions/lt';
|
import semverLt from 'semver/functions/lt';
|
||||||
import semverValid from 'semver/functions/valid';
|
import semverValid from 'semver/functions/valid';
|
||||||
import { ConfigGroup, ConfigItem } from '@music163/tango-ui';
|
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 { useBoolean } from '@music163/tango-helpers';
|
||||||
import { isUndefined } from 'lodash-es';
|
import { isUndefined } from 'lodash-es';
|
||||||
import { Workspace } from '@music163/tango-core';
|
import { Workspace } from '@music163/tango-core';
|
||||||
@@ -177,18 +179,9 @@ function RenderItem({
|
|||||||
onUpgrade,
|
onUpgrade,
|
||||||
showTags = true,
|
showTags = true,
|
||||||
}: RenderItemProps) {
|
}: RenderItemProps) {
|
||||||
const [version, setVersion] = useState<string>();
|
|
||||||
const [versionList, setVersionList] = useState<Array<{ label: string; value: string }>>([]);
|
|
||||||
const [open, { on, off }] = useBoolean(false);
|
const [open, { on, off }] = useBoolean(false);
|
||||||
|
|
||||||
const workspace = useWorkspace() as Workspace;
|
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(() => {
|
const basePackage = useMemo(() => {
|
||||||
if (type !== DependencyItemType.基础包 || !templateBaseDependencies) return undefined;
|
if (type !== DependencyItemType.基础包 || !templateBaseDependencies) return undefined;
|
||||||
@@ -198,9 +191,29 @@ function RenderItem({
|
|||||||
/**
|
/**
|
||||||
* 处理依赖升级的逻辑
|
* 处理依赖升级的逻辑
|
||||||
*/
|
*/
|
||||||
function onDepUpgrade() {
|
function onDepUpgrade(values: Record<string, any>) {
|
||||||
// 根据是否有 packages 字段,判断是否已升级为新的规范
|
// 根据是否有 packages 字段,判断是否已升级为新的规范
|
||||||
const hasUpgraded = !!workspace.tangoConfigJson?.getValue('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) {
|
switch (type) {
|
||||||
case DependencyItemType.业务组件:
|
case DependencyItemType.业务组件:
|
||||||
@@ -210,7 +223,7 @@ function RenderItem({
|
|||||||
version,
|
version,
|
||||||
hasUpgraded
|
hasUpgraded
|
||||||
? {
|
? {
|
||||||
package: record?.package,
|
package: packageConfig,
|
||||||
}
|
}
|
||||||
: {},
|
: {},
|
||||||
);
|
);
|
||||||
@@ -225,7 +238,7 @@ function RenderItem({
|
|||||||
basePackage?.version || 'latest',
|
basePackage?.version || 'latest',
|
||||||
hasUpgraded
|
hasUpgraded
|
||||||
? {
|
? {
|
||||||
package: record?.package,
|
package: packageConfig,
|
||||||
}
|
}
|
||||||
: {},
|
: {},
|
||||||
);
|
);
|
||||||
@@ -295,7 +308,6 @@ function RenderItem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onCloseModal() {
|
function onCloseModal() {
|
||||||
setVersion(undefined);
|
|
||||||
off();
|
off();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,10 +329,8 @@ function RenderItem({
|
|||||||
const { resources } = packageInfo || {};
|
const { resources } = packageInfo || {};
|
||||||
const dependencies = workspace.packageJson?.getValue('dependencies');
|
const dependencies = workspace.packageJson?.getValue('dependencies');
|
||||||
const hasUmd = !!resources?.length;
|
const hasUmd = !!resources?.length;
|
||||||
const unsafeUmd =
|
const packageJsonIncluded = !!dependencies?.[name];
|
||||||
hasUmd && resources.some((e: string) => /\/\/unpkg\.f[nt]\.netease\.com\//.test(e));
|
const tangoConfigJsonIncluded = !!packageInfo;
|
||||||
const packageJsonOnly = dependencies?.[name] && !packageInfo;
|
|
||||||
const tangoConfigJsonOnly = !dependencies?.[name] && packageInfo;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
hasUmd && (
|
hasUmd && (
|
||||||
@@ -330,21 +340,14 @@ function RenderItem({
|
|||||||
</Tag>
|
</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
unsafeUmd && (
|
packageJsonIncluded && (
|
||||||
<Tooltip title="该依赖的 UMD 资源来自自建 unpkg 服务,稳定性不可靠,建议修改 tango.config.json 更换为正式 cdn 资源地址">
|
<Tooltip title="此依赖在 package.json 中存在">
|
||||||
<Tag key="unsafeUmd" color="orange">
|
<Tag key="packageJson">package</Tag>
|
||||||
非 CDN
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
packageJsonOnly && (
|
tangoConfigJsonIncluded && (
|
||||||
<Tooltip title="此依赖只在 package.json 中存在">
|
<Tooltip title="此依赖在 tango.config.json 中存在">
|
||||||
<Tag key="packageJsonOnly">package.json</Tag>
|
<Tag key="tangoConfigJson">tango.config</Tag>
|
||||||
</Tooltip>
|
|
||||||
),
|
|
||||||
tangoConfigJsonOnly && (
|
|
||||||
<Tooltip title="此依赖只在 tango.config.json 中存在">
|
|
||||||
<Tag key="packageJsonOnly">tango.config.json</Tag>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
].filter((e) => e);
|
].filter((e) => e);
|
||||||
@@ -356,7 +359,7 @@ function RenderItem({
|
|||||||
type !== DependencyItemType.基础包
|
type !== DependencyItemType.基础包
|
||||||
? [
|
? [
|
||||||
<a key="upgrade" onClick={on}>
|
<a key="upgrade" onClick={on}>
|
||||||
升级
|
修改
|
||||||
</a>,
|
</a>,
|
||||||
<Popconfirm onConfirm={onDepDelete} title={`确认删除依赖 ${record.name}?`}>
|
<Popconfirm onConfirm={onDepDelete} title={`确认删除依赖 ${record.name}?`}>
|
||||||
<a key="delete">删除</a>
|
<a key="delete">删除</a>
|
||||||
@@ -366,7 +369,7 @@ function RenderItem({
|
|||||||
? [
|
? [
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`确认升级 ${record.name} 到 ${basePackage.version} 吗?`}
|
title={`确认升级 ${record.name} 到 ${basePackage.version} 吗?`}
|
||||||
onConfirm={onDepUpgrade}
|
onConfirm={() => onDepUpgrade({ version: basePackage.version })}
|
||||||
>
|
>
|
||||||
<a key="upgrade">升级</a>
|
<a key="upgrade">升级</a>
|
||||||
</Popconfirm>,
|
</Popconfirm>,
|
||||||
@@ -394,28 +397,12 @@ function RenderItem({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<DependencyConfigModal
|
||||||
open={open}
|
open={open}
|
||||||
title={`升级依赖:${record.name}`}
|
record={record}
|
||||||
onCancel={onCloseModal}
|
onCancel={onCloseModal}
|
||||||
onOk={onDepUpgrade}
|
onOk={(event, { values }) => onDepUpgrade(values)}
|
||||||
>
|
/>
|
||||||
<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>
|
|
||||||
</List.Item>
|
</List.Item>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -447,111 +434,114 @@ interface DependencyModalProps {
|
|||||||
templateBaseDependencies: Record<string, any>;
|
templateBaseDependencies: Record<string, any>;
|
||||||
onBizDependencyAdd: Function;
|
onBizDependencyAdd: Function;
|
||||||
onBaseDependencyAdd: Function;
|
onBaseDependencyAdd: Function;
|
||||||
|
record?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddDependencyModal({
|
function AddDependencyModal({
|
||||||
templateBaseDependencies,
|
templateBaseDependencies,
|
||||||
onBizDependencyAdd,
|
onBizDependencyAdd,
|
||||||
onBaseDependencyAdd,
|
onBaseDependencyAdd,
|
||||||
}: DependencyModalProps) {
|
record,
|
||||||
|
...rest
|
||||||
|
}: DependencyModalProps & ModalProps) {
|
||||||
// 依赖类型
|
// 依赖类型
|
||||||
const [type, setType] = useState<DependencyItemType>(DependencyItemType.其他依赖);
|
|
||||||
const [open, { on, off }] = useBoolean();
|
const [open, { on, off }] = useBoolean();
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const sandbox = useSandboxQuery();
|
const sandbox = useSandboxQuery();
|
||||||
|
const hasUpgraded = !!workspace.tangoConfigJson?.getValue('packages');
|
||||||
|
|
||||||
const onFinishCallback = (name?: string) => {
|
const onFinishCallback = (name?: string) => {
|
||||||
name && message.success(`${name} 添加成功`);
|
name && message.success(`${name} 添加成功`);
|
||||||
// FIXME: 修复沙箱添加组件后 HMR 失效的 bug,添加完刷新
|
// FIXME: 修复沙箱添加组件后 HMR 失效的 bug,添加完刷新
|
||||||
sandbox.reload();
|
sandbox.reload();
|
||||||
off();
|
off();
|
||||||
form.resetFields();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOk = () => {
|
const onOk: DependencyConfigModalProps['onOk'] = (event, { values }) => {
|
||||||
form.validateFields().then((values) => {
|
const { type, name, version, umd, library, resources, designerResources } = values;
|
||||||
const { name, version } = values;
|
|
||||||
// 根据是否有 packages 字段,判断是否已升级为新的规范
|
|
||||||
const hasUpgraded = !!workspace.tangoConfigJson?.getValue('packages');
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DependencyItemType.其他依赖: {
|
case DependencyItemType.其他依赖: {
|
||||||
workspace.updateDependency(name, version);
|
workspace.updateDependency(
|
||||||
break;
|
name,
|
||||||
}
|
version,
|
||||||
// case DependencyItemType.业务组件: {
|
hasUpgraded && umd
|
||||||
// const targetBizInfo = (bizList as any)?.find(
|
? {
|
||||||
// (biz: { packageName: string }) => biz.packageName === name,
|
package: {
|
||||||
// );
|
type: 'dependency',
|
||||||
// onBizDependencyAdd &&
|
...record?.package,
|
||||||
// onBizDependencyAdd({
|
version,
|
||||||
// ...values,
|
library,
|
||||||
// detail: targetBizInfo,
|
resources,
|
||||||
// });
|
designerResources,
|
||||||
// break;
|
},
|
||||||
// }
|
}
|
||||||
case DependencyItemType.基础包: {
|
: {},
|
||||||
const basePackage = templateBaseDependencies[name];
|
);
|
||||||
// 添加 package.json
|
break;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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) {
|
if (!hasUpgraded) {
|
||||||
// type 变更触发
|
// 更新 tango.config.json baseDependencies
|
||||||
form.resetFields(['name', 'version']);
|
workspace.tangoConfigJson.setValue('baseDependencies', (targetValue) => {
|
||||||
setType(depType);
|
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();
|
off();
|
||||||
form.resetFields(['name', 'version']);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px="l" py="m">
|
<Box px="l" py="m">
|
||||||
@@ -559,31 +549,155 @@ function AddDependencyModal({
|
|||||||
<PlusOutlined />
|
<PlusOutlined />
|
||||||
添加依赖
|
添加依赖
|
||||||
</Button>
|
</Button>
|
||||||
<Modal
|
<DependencyConfigModal
|
||||||
width={560}
|
|
||||||
open={open}
|
open={open}
|
||||||
title="添加依赖"
|
title={record ? `修改依赖:${record.name}` : '添加依赖'}
|
||||||
onOk={onOk}
|
onOk={onOk}
|
||||||
onCancel={onCloseModal}
|
onCancel={onCloseModal}
|
||||||
destroyOnClose
|
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={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
initialValues={{ type: DependencyItemType.业务组件 }}
|
initialValues={{ type }}
|
||||||
onValuesChange={onValuesChange}
|
onValuesChange={onValuesChange}
|
||||||
>
|
>
|
||||||
<Form.Item label="依赖类型" name="type">
|
{!record && (
|
||||||
<Radio.Group value={type}>
|
<>
|
||||||
{/* <Radio.Button value={DependencyItemType.业务组件}>业务组件</Radio.Button> */}
|
<Form.Item label="依赖类型" name="type" rules={[{ required: true }]}>
|
||||||
<Radio.Button value={DependencyItemType.基础包}>基础包</Radio.Button>
|
<Radio.Group>
|
||||||
<Radio.Button value={DependencyItemType.其他依赖}>其他依赖</Radio.Button>
|
{/* <Radio.Button value={DependencyItemType.业务组件}>业务组件</Radio.Button> */}
|
||||||
</Radio.Group>
|
<Radio.Button value={DependencyItemType.基础包}>基础包</Radio.Button>
|
||||||
</Form.Item>
|
<Radio.Button value={DependencyItemType.其他依赖}>其他依赖</Radio.Button>
|
||||||
<Form.Item label="npm包名" name="name" rules={[{ required: true }]}>
|
</Radio.Group>
|
||||||
<Input placeholder="输入依赖名" />
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item label="npm 包名" name="name" rules={[{ required: true }]}>
|
||||||
|
<Input placeholder="输入依赖名" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="版本号"
|
label="版本号"
|
||||||
name="version"
|
name="version"
|
||||||
@@ -591,8 +705,43 @@ function AddDependencyModal({
|
|||||||
>
|
>
|
||||||
<Input placeholder="输入版本号" />
|
<Input placeholder="输入版本号" />
|
||||||
</Form.Item>
|
</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>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Box>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { Box, Text, css, HTMLCoralProps } from 'coral-system';
|
import { Box, Text, css, HTMLCoralProps } from 'coral-system';
|
||||||
import {
|
|
||||||
ClusterOutlined,
|
|
||||||
FunctionOutlined,
|
|
||||||
ApiOutlined,
|
|
||||||
AppstoreOutlined,
|
|
||||||
BuildOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { Badge } from 'antd';
|
import { Badge } from 'antd';
|
||||||
import { ReactComponentProps } from '@music163/tango-helpers';
|
import { ReactComponentProps } from '@music163/tango-helpers';
|
||||||
import { observer, useDesigner } from '@music163/tango-context';
|
import { observer, useDesigner } from '@music163/tango-context';
|
||||||
import { ResizableBox } from './resizable-box';
|
import { ResizableBox } from './resizable-box';
|
||||||
import { OutlinePanel } from './outline-panel';
|
import { getWidget } from '../widgets';
|
||||||
import { DependencyPanel } from './dependency-panel';
|
|
||||||
import { VariablePanel } from './variable-panel';
|
|
||||||
import { DataSourcePanel } from './datasource-panel';
|
|
||||||
|
|
||||||
const sidebarStyle = css`
|
const sidebarStyle = css`
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -51,7 +41,6 @@ export interface SidebarProps extends ReactComponentProps {
|
|||||||
* 底部附加内容
|
* 底部附加内容
|
||||||
*/
|
*/
|
||||||
footer?: React.ReactNode;
|
footer?: React.ReactNode;
|
||||||
builtinPanelMap?: Record<string, SidebarPanelItemProps>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarPanelItemProps
|
export interface SidebarPanelItemProps
|
||||||
@@ -79,54 +68,21 @@ export interface SidebarPanelItemProps
|
|||||||
* 面板宽度
|
* 面板宽度
|
||||||
*/
|
*/
|
||||||
width?: number;
|
width?: number;
|
||||||
|
/**
|
||||||
|
* 如果 key 匹配到内置组件的话,传递给子节点的属性
|
||||||
|
*/
|
||||||
|
widgetProps?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const INTERNAL_SIDEBAR_PANEL_MAP: Record<string, SidebarPanelItemProps> = {
|
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) {
|
||||||
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) {
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
const ret: Record<React.Key, SidebarPanelItemProps> = {};
|
const ret: Record<React.Key, SidebarPanelItemProps> = {};
|
||||||
React.Children.forEach(children, (child) => {
|
React.Children.forEach(children, (child) => {
|
||||||
if (child && React.isValidElement(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] = {
|
ret[child.key] = {
|
||||||
...builtinPanelMap[child.key],
|
children: fallbackNode,
|
||||||
...child.props,
|
...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 <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,23 @@ import { FileAddOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
|||||||
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||||
import { EditableVariableTree, EditableVariableTreeProps } from '../components';
|
import { EditableVariableTree, EditableVariableTreeProps } from '../components';
|
||||||
|
|
||||||
|
const defaultStoreTemplate = `
|
||||||
|
import { defineStore } from '@music163/tango-boot';
|
||||||
|
export default defineStore({
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
export interface VariablePanelProps extends EditableVariableTreeProps {
|
export interface VariablePanelProps extends EditableVariableTreeProps {
|
||||||
wrapperHeight?: number | string;
|
wrapperHeight?: number | string;
|
||||||
|
newStoreTemplate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VariablePanel = observer(
|
export const VariablePanel = observer(
|
||||||
({ wrapperHeight = '100%', ...restProps }: VariablePanelProps) => {
|
({
|
||||||
|
wrapperHeight = '100%',
|
||||||
|
newStoreTemplate = defaultStoreTemplate,
|
||||||
|
...restProps
|
||||||
|
}: VariablePanelProps) => {
|
||||||
const [isAdd, { on, off }] = useBoolean();
|
const [isAdd, { on, off }] = useBoolean();
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const { storeVariables } = useWorkspaceData();
|
const { storeVariables } = useWorkspaceData();
|
||||||
@@ -42,7 +53,7 @@ export const VariablePanel = observer(
|
|||||||
existNames={storeNames}
|
existNames={storeNames}
|
||||||
onCancel={off}
|
onCancel={off}
|
||||||
onSubmit={(values) => {
|
onSubmit={(values) => {
|
||||||
workspace.addStoreModule(values.name, emptyStoreTemplate);
|
workspace.addStoreModule(values.name, newStoreTemplate);
|
||||||
off();
|
off();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -119,9 +130,3 @@ function AddStoreForm({ existNames = [], onCancel, onSubmit }: AddStoreFormProps
|
|||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyStoreTemplate = `
|
|
||||||
import { defineStore } from '@music163/tango-boot';
|
|
||||||
export default defineStore({
|
|
||||||
});
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ISelectedItemData, isString, noop } from '@music163/tango-helpers';
|
|||||||
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
|
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
|
||||||
import { IconButton } from '@music163/tango-ui';
|
import { IconButton } from '@music163/tango-ui';
|
||||||
import { getDragGhostElement } from '../helpers';
|
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 boundingOffset = 100; // 检测选中模块的工具栏是否溢出的偏移值
|
||||||
|
|
||||||
const internalSelectionToolsMap = {
|
|
||||||
source: <ViewSourceAction />,
|
|
||||||
copy: <CopyNodeAction />,
|
|
||||||
delete: <DeleteNodeAction />,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INTERNAL_SELECTION_TOOLS = ['source', 'create', 'copy', 'delete'];
|
|
||||||
|
|
||||||
export interface SelectionToolsProps {
|
export interface SelectionToolsProps {
|
||||||
/**
|
/**
|
||||||
* 动作列表,内置的动作列表有 source-定位到源码, block-创建区块, copy-复制节点, delete-删除节点
|
* 动作列表,内置的动作列表有 source-定位到源码, block-创建区块, copy-复制节点, delete-删除节点
|
||||||
*/
|
*/
|
||||||
actions?: Array<string | React.ReactElement>;
|
actions?: Array<string | React.ReactElement>;
|
||||||
builtinActionMap?: Record<string, React.ReactElement>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectionTools = observer(
|
export const SelectionTools = observer(
|
||||||
({
|
({ actions: actionsProp = ['viewSource', 'copyNode', 'deleteNode'] }: SelectionToolsProps) => {
|
||||||
actions: actionsProp = INTERNAL_SELECTION_TOOLS,
|
|
||||||
builtinActionMap = internalSelectionToolsMap,
|
|
||||||
}: SelectionToolsProps) => {
|
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const selectSource = workspace.selectSource;
|
const selectSource = workspace.selectSource;
|
||||||
const actions = actionsProp.map((item) => {
|
const actions = actionsProp.map((item) => {
|
||||||
if (isString(item)) {
|
if (isString(item)) {
|
||||||
return builtinActionMap[item]
|
const widget = getWidget(['selectionMenu', item].join('.'));
|
||||||
? React.cloneElement(builtinActionMap[item], { key: item })
|
return widget ? React.createElement(widget, { key: item }) : null;
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, HTMLCoralProps } from 'coral-system';
|
import { css, Box, HTMLCoralProps } from 'coral-system';
|
||||||
import { css } from 'styled-components';
|
|
||||||
import { observer, useDesigner } from '@music163/tango-context';
|
import { observer, useDesigner } from '@music163/tango-context';
|
||||||
|
|
||||||
export interface SimulatorProps {
|
export interface SimulatorProps {
|
||||||
|
|||||||
@@ -10,21 +10,14 @@ import { DraggingMask } from './mask';
|
|||||||
|
|
||||||
export interface ViewportProps extends HTMLCoralProps<'div'> {
|
export interface ViewportProps extends HTMLCoralProps<'div'> {
|
||||||
selectionTools?: SelectionToolsProps['actions'];
|
selectionTools?: SelectionToolsProps['actions'];
|
||||||
builtinActionMap?: SelectionToolsProps['builtinActionMap'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Viewport({
|
export function Viewport({ selectionTools, children, className, ...rest }: ViewportProps) {
|
||||||
selectionTools,
|
|
||||||
builtinActionMap,
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: ViewportProps) {
|
|
||||||
return (
|
return (
|
||||||
<Box position="relative" height="100%" className={cx('Viewport', className)} {...rest}>
|
<Box position="relative" height="100%" className={cx('Viewport', className)} {...rest}>
|
||||||
<ViewportBody>{children}</ViewportBody>
|
<ViewportBody>{children}</ViewportBody>
|
||||||
<div className="AuxTools">
|
<div className="AuxTools">
|
||||||
<SelectionTools actions={selectionTools} builtinActionMap={builtinActionMap} />
|
<SelectionTools actions={selectionTools} />
|
||||||
<InsertionPrompt />
|
<InsertionPrompt />
|
||||||
<DraggingMask />
|
<DraggingMask />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export const ModeSwitchTool = observer(() => {
|
|||||||
shape="ghost"
|
shape="ghost"
|
||||||
selected={designer.activeView === 'code'}
|
selected={designer.activeView === 'code'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
designer.setActiveView('code');
|
designer.setActiveView('code'); // 切换到源码视图
|
||||||
|
designer.setActiveSidebarPanel(''); // 关闭左侧面板
|
||||||
}}
|
}}
|
||||||
tip="源码视图"
|
tip="源码视图"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ export const PreviewTool = observer(() => {
|
|||||||
shape="ghost"
|
shape="ghost"
|
||||||
selected={designer.isPreview}
|
selected={designer.isPreview}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
designer.toggleIsPreview(!designer.isPreview);
|
const nextIsPreview = !designer.isPreview;
|
||||||
|
designer.toggleIsPreview(nextIsPreview);
|
||||||
|
if (nextIsPreview) {
|
||||||
|
designer.setActiveView('design');
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
tip="预览"
|
tip="预览"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,26 +1,13 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Box, Group } from 'coral-system';
|
import { Box, Group } from 'coral-system';
|
||||||
import { ReactComponentProps } from '@music163/tango-helpers';
|
import { ReactComponentProps } from '@music163/tango-helpers';
|
||||||
import { HistoryTool } from './history';
|
import { getWidget } from '../widgets';
|
||||||
import { ModeSwitchTool } from './mode-switch';
|
|
||||||
import { PreviewTool } from './preview';
|
|
||||||
import { RouteSwitchTool } from './route-switch';
|
|
||||||
import { TogglePanelTool } from './toggle-panel';
|
|
||||||
|
|
||||||
export interface ToolbarProps {
|
export interface ToolbarProps {
|
||||||
children?: React.ReactElement | React.ReactElement[];
|
children?: React.ReactElement | React.ReactElement[];
|
||||||
builtinToolMap?: Record<string, React.ReactNode>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const builtinToolsMap = {
|
export function Toolbar({ children }: ToolbarProps) {
|
||||||
history: <HistoryTool />,
|
|
||||||
modeSwitch: <ModeSwitchTool />,
|
|
||||||
preview: <PreviewTool />,
|
|
||||||
routeSwitch: <RouteSwitchTool />,
|
|
||||||
togglePanel: <TogglePanelTool />,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarProps) {
|
|
||||||
const [leftTools, centerTools, rightTools] = useMemo(() => {
|
const [leftTools, centerTools, rightTools] = useMemo(() => {
|
||||||
const left: React.ReactNode[] = [];
|
const left: React.ReactNode[] = [];
|
||||||
const center: React.ReactNode[] = [];
|
const center: React.ReactNode[] = [];
|
||||||
@@ -28,7 +15,15 @@ export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarP
|
|||||||
|
|
||||||
let prevPlacement: string;
|
let prevPlacement: string;
|
||||||
React.Children.forEach(children, (child: React.ReactElement, index) => {
|
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) {
|
if (!node) {
|
||||||
node = child; // separator
|
node = child; // separator
|
||||||
}
|
}
|
||||||
@@ -75,9 +70,13 @@ export function Toolbar({ children, builtinToolMap = builtinToolsMap }: ToolbarP
|
|||||||
export interface ToolbarItemProps extends ReactComponentProps {
|
export interface ToolbarItemProps extends ReactComponentProps {
|
||||||
placement?: 'left' | 'center' | 'right';
|
placement?: 'left' | 'center' | 'right';
|
||||||
children?: React.ReactElement;
|
children?: React.ReactElement;
|
||||||
|
/**
|
||||||
|
* 如果 key 匹配到内置组件的话,传递给子节点的属性
|
||||||
|
*/
|
||||||
|
widgetProps?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolbarItem({ placement, children }: ToolbarItemProps) {
|
function ToolbarItem({ placement, widgetProps, children }: ToolbarItemProps) {
|
||||||
return <div>{children}</div>;
|
return <div>{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { getValue, setValue } from '@music163/tango-helpers';
|
||||||
|
import {
|
||||||
|
HistoryTool,
|
||||||
|
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.history', HistoryTool);
|
||||||
|
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,16 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.1.4](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.3...@music163/tango-helpers@0.1.4) (2023-10-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- enhance expSetter ([#43](https://github.com/netease/tango/issues/43)) ([5ebbb42](https://github.com/netease/tango/commit/5ebbb428fb3fb786d330ab01959028443338d315))
|
||||||
|
|
||||||
|
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.2...@music163/tango-helpers@0.1.3) (2023-09-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @music163/tango-helpers
|
||||||
|
|
||||||
## 0.1.2 (2023-09-06)
|
## 0.1.2 (2023-09-06)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@music163/tango-helpers",
|
"name": "@music163/tango-helpers",
|
||||||
"version": "0.1.2",
|
"version": "0.1.4",
|
||||||
"description": "Shared types, helpers, and hooks of tango-apps",
|
"description": "Shared types, helpers, and hooks of tango-apps",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"shared",
|
"shared",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"prepublishOnly": "yarn build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16.8.0"
|
"react": ">= 16.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hoist-non-react-statics": "^3.3.2",
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function parseDndTrackId(str: string) {
|
|||||||
return [parts[0], str];
|
return [parts[0], str];
|
||||||
}
|
}
|
||||||
|
|
||||||
type DndIdParsedType = {
|
interface DndIdParsedType {
|
||||||
/**
|
/**
|
||||||
* 完整的 ID
|
* 完整的 ID
|
||||||
*/
|
*/
|
||||||
@@ -93,7 +93,7 @@ type DndIdParsedType = {
|
|||||||
* 序号
|
* 序号
|
||||||
*/
|
*/
|
||||||
index?: string;
|
index?: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 dnd id
|
* 解析 dnd id
|
||||||
@@ -178,7 +178,7 @@ export function isValidObjectString(str: string) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const templatePattern = /^\{(.+)\}$/s;
|
const templatePattern = /^{(.+)}$/s;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断给定字符串是否为变量字符串
|
* 判断给定字符串是否为变量字符串
|
||||||
@@ -191,15 +191,30 @@ const templatePattern = /^\{(.+)\}$/s;
|
|||||||
*/
|
*/
|
||||||
export function isVariableString(str: string) {
|
export function isVariableString(str: string) {
|
||||||
// 先检查是否是简单的对象
|
// 先检查是否是简单的对象
|
||||||
// TODO: 是不是可以忽略这个检查
|
|
||||||
if (code2object(str)) {
|
if (code2object(str)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 排除简单对象后,在用正则匹配
|
// 排除简单对象后,再用正则匹配
|
||||||
return templatePattern.test(str);
|
return templatePattern.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给输入代码加上花括号
|
||||||
|
* @example foo -> {foo}
|
||||||
|
* @example "hello" => {"hello"}
|
||||||
|
* @example () => {} => {() => {}}
|
||||||
|
*
|
||||||
|
* @param code 输入代码
|
||||||
|
* @returns 加上花括号后的代码
|
||||||
|
*/
|
||||||
|
export function wrapCode(code: string) {
|
||||||
|
if (isVariableString(code)) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
return `{${code}}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否为简单字符串,非变量字符串
|
* 是否为简单字符串,非变量字符串
|
||||||
* @param str
|
* @param str
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ describe('string', () => {
|
|||||||
expect(isVariableString('{"hello"}')).toBeTruthy();
|
expect(isVariableString('{"hello"}')).toBeTruthy();
|
||||||
expect(isVariableString('{ foo: "bar" }')).toBeFalsy();
|
expect(isVariableString('{ foo: "bar" }')).toBeFalsy();
|
||||||
expect(isVariableString('{{ foo: "bar" }}')).toBeTruthy();
|
expect(isVariableString('{{ foo: "bar" }}')).toBeTruthy();
|
||||||
|
expect(isVariableString('{() => {}}')).toBeTruthy();
|
||||||
|
expect(isVariableString('{() => { console.log(); }}')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getVariableContent', () => {
|
it('getVariableContent', () => {
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.1.7](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.6...@music163/tango-sandbox@0.1.7) (2023-10-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @music163/tango-sandbox
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [0.1.4](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.3...@music163/tango-sandbox@0.1.4) (2023-09-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @music163/tango-sandbox
|
||||||
|
|
||||||
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.2...@music163/tango-sandbox@0.1.3) (2023-09-13)
|
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.2...@music163/tango-sandbox@0.1.3) (2023-09-13)
|
||||||
|
|
||||||
**Note:** Version bump only for package @music163/tango-sandbox
|
**Note:** Version bump only for package @music163/tango-sandbox
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@music163/tango-sandbox",
|
"name": "@music163/tango-sandbox",
|
||||||
"version": "0.1.3",
|
"version": "0.1.7",
|
||||||
"description": "sandbox of tango apps",
|
"description": "sandbox of tango apps",
|
||||||
"author": "wwsun <ww.sun@outlook.com>",
|
"author": "wwsun <ww.sun@outlook.com>",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
@@ -24,13 +24,13 @@
|
|||||||
"prepublishOnly": "yarn build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16.8.0",
|
"react": ">= 16.8",
|
||||||
"react-dom": ">= 16.8.0"
|
"styled-components": ">= 4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.8.0",
|
"@ant-design/icons": "^4.8.0",
|
||||||
"@music163/tango-core": "^0.1.3",
|
"@music163/tango-core": "^0.2.2",
|
||||||
"@music163/tango-helpers": "^0.1.2",
|
"@music163/tango-helpers": "^0.1.4",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"lodash.isequal": "4.5.0",
|
"lodash.isequal": "4.5.0",
|
||||||
"react-frame-component": "^5.2.4"
|
"react-frame-component": "^5.2.4"
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.2.6](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.5...@music163/tango-setting-form@0.2.6) (2023-10-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- enhance expSetter ([#43](https://github.com/netease/tango/issues/43)) ([5ebbb42](https://github.com/netease/tango/commit/5ebbb428fb3fb786d330ab01959028443338d315))
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
## [0.2.1](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.0...@music163/tango-setting-form@0.2.1) (2023-09-18)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- remove useless codes ([813eb83](https://github.com/netease/tango/commit/813eb83ebb9ced891636f2653f25b6281386c18d))
|
||||||
|
|
||||||
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.1.3...@music163/tango-setting-form@0.2.0) (2023-09-13)
|
# [0.2.0](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.1.3...@music163/tango-setting-form@0.2.0) (2023-09-13)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@music163/tango-setting-form",
|
"name": "@music163/tango-setting-form",
|
||||||
"version": "0.2.0",
|
"version": "0.2.6",
|
||||||
"description": "setting form of tango-apps",
|
"description": "setting form of tango-apps",
|
||||||
"author": "wwsun <ww.sun@outlook.com>",
|
"author": "wwsun <ww.sun@outlook.com>",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
@@ -24,13 +24,12 @@
|
|||||||
"prepublishOnly": "yarn build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16.8.0"
|
"react": ">= 16.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.8.0",
|
"@ant-design/icons": "^4.8.0",
|
||||||
"@music163/tango-core": "^0.1.3",
|
"@music163/tango-helpers": "^0.1.4",
|
||||||
"@music163/tango-helpers": "^0.1.2",
|
"@music163/tango-ui": "^0.1.7",
|
||||||
"@music163/tango-ui": "^0.1.3",
|
|
||||||
"antd": "^4.24.2",
|
"antd": "^4.24.2",
|
||||||
"coral-system": "^1.0.5",
|
"coral-system": "^1.0.5",
|
||||||
"mobx": "6.9.0",
|
"mobx": "6.9.0",
|
||||||
|
|||||||
@@ -10,48 +10,11 @@ export const useFormModel = () => {
|
|||||||
return useContext(FormModelContext);
|
return useContext(FormModelContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: 删掉不需要的
|
|
||||||
export interface FormVariableContextType {
|
export interface FormVariableContextType {
|
||||||
/**
|
/**
|
||||||
* 是否允许表单项切换到表达式设置器
|
* 是否允许表单项切换到表达式设置器
|
||||||
*/
|
*/
|
||||||
disableSwitchExpressionSetter?: boolean;
|
disableSwitchExpressionSetter?: boolean;
|
||||||
// /**
|
|
||||||
// * 探测输入上下文的对象
|
|
||||||
// */
|
|
||||||
// evaluateContext?: object;
|
|
||||||
// /**
|
|
||||||
// * 表单动作发生时的回调
|
|
||||||
// */
|
|
||||||
// onAction?: (fn: string, args: unknown[]) => void;
|
|
||||||
// /**
|
|
||||||
// * 状态变量列表
|
|
||||||
// */
|
|
||||||
// modelVariables?: any[];
|
|
||||||
// /**
|
|
||||||
// * 动作变量列表
|
|
||||||
// */
|
|
||||||
// actionVariables?: any[];
|
|
||||||
// /**
|
|
||||||
// * 表达式变量列表(含状态变量和动作变量)
|
|
||||||
// */
|
|
||||||
// expressionVariables?: any[];
|
|
||||||
// /**
|
|
||||||
// * 路由选项
|
|
||||||
// */
|
|
||||||
// routeOptions?: any[];
|
|
||||||
// /**
|
|
||||||
// * 可选弹窗列表
|
|
||||||
// */
|
|
||||||
// modalOptions?: any[];
|
|
||||||
// /**
|
|
||||||
// * 关联的表单域列表
|
|
||||||
// */
|
|
||||||
// formFieldsOptions?: any[];
|
|
||||||
// /**
|
|
||||||
// * 远程服务
|
|
||||||
// */
|
|
||||||
// remoteServices?: TangoRemoteServicesType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormVariableContext = createContext<FormVariableContextType>(null);
|
const FormVariableContext = createContext<FormVariableContextType>(null);
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import {
|
|||||||
clone,
|
clone,
|
||||||
ComponentPropType,
|
ComponentPropType,
|
||||||
isVariableString,
|
isVariableString,
|
||||||
logger,
|
|
||||||
SetterOnChangeDetailType,
|
SetterOnChangeDetailType,
|
||||||
useBoolean,
|
useBoolean,
|
||||||
} from '@music163/tango-helpers';
|
} from '@music163/tango-helpers';
|
||||||
import { IconFont, ToggleButton } from '@music163/tango-ui';
|
import { IconFont, ToggleButton } from '@music163/tango-ui';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import { InputProps, Tooltip } from 'antd';
|
import { InputProps, Tooltip } from 'antd';
|
||||||
import { INTERNAL_SETTERS } from './setters';
|
|
||||||
import { useFormModel, useFormVariable } from './context';
|
import { useFormModel, useFormVariable } from './context';
|
||||||
import { FormControl } from './form-ui';
|
import { FormControl } from './form-ui';
|
||||||
|
|
||||||
@@ -50,10 +48,6 @@ export interface IFormItemCreateOptions {
|
|||||||
* 是否禁用变量设置器
|
* 是否禁用变量设置器
|
||||||
*/
|
*/
|
||||||
disableVariableSetter?: boolean;
|
disableVariableSetter?: boolean;
|
||||||
/**
|
|
||||||
* 子表单是否默认折叠
|
|
||||||
*/
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* 默认的表单值校验逻辑
|
* 默认的表单值校验逻辑
|
||||||
*/
|
*/
|
||||||
@@ -141,7 +135,6 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
|||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
visible={getVisible(model)}
|
visible={getVisible(model)}
|
||||||
defaultCollapsed={options.defaultCollapsed}
|
|
||||||
label={title}
|
label={title}
|
||||||
note={name}
|
note={name}
|
||||||
tip={tip}
|
tip={tip}
|
||||||
@@ -155,7 +148,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
|||||||
size="s"
|
size="s"
|
||||||
shape="outline"
|
shape="outline"
|
||||||
type="primary"
|
type="primary"
|
||||||
tooltip="切换为 JS 表达式"
|
tooltip={isVariable ? '关闭 JS 表达式' : '使用 JS 表达式'}
|
||||||
tooltipPlacement="left"
|
tooltipPlacement="left"
|
||||||
selected={isVariable}
|
selected={isVariable}
|
||||||
onClick={() => toggleIsVariable()}
|
onClick={() => toggleIsVariable()}
|
||||||
@@ -194,8 +187,6 @@ export function register(config: IFormItemCreateOptions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
INTERNAL_SETTERS.forEach(register);
|
|
||||||
|
|
||||||
const iconStyle = {
|
const iconStyle = {
|
||||||
color: 'red',
|
color: 'red',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ export interface IFormModel {
|
|||||||
onChange: (name: string, value: any, field?: Field) => void;
|
onChange: (name: string, value: any, field?: Field) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormModelOptionsType = {
|
export interface FormModelOptionsType {
|
||||||
onChange?: IFormModel['onChange'];
|
onChange?: IFormModel['onChange'];
|
||||||
};
|
}
|
||||||
|
|
||||||
export class FormModel implements IFormModel {
|
export class FormModel implements IFormModel {
|
||||||
state = {};
|
state = {};
|
||||||
@@ -141,21 +141,21 @@ class SubModel implements IFormModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldCreateOptionsType = {
|
interface FieldCreateOptionsType {
|
||||||
parent: IFormModel;
|
parent: IFormModel;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
type FieldConfigType = {
|
interface FieldConfigType {
|
||||||
validate?: (value: any, field: Field, trigger: string) => string | Promise<any>;
|
validate?: (value: any, field: Field, trigger: string) => string | Promise<any>;
|
||||||
};
|
}
|
||||||
|
|
||||||
type FieldStateType = {
|
interface FieldStateType {
|
||||||
error?: any;
|
error?: any;
|
||||||
validating?: boolean;
|
validating?: boolean;
|
||||||
cancelValidation?: () => void;
|
cancelValidation?: () => void;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
}
|
||||||
|
|
||||||
type FieldValidateTriggerType = '*' | 'blur' | 'change';
|
type FieldValidateTriggerType = '*' | 'blur' | 'change';
|
||||||
|
|
||||||
@@ -233,11 +233,12 @@ export class Field {
|
|||||||
this.state.validating = false;
|
this.state.validating = false;
|
||||||
this.state.error = error;
|
this.state.error = error;
|
||||||
|
|
||||||
this.parent?.onChange?.(this.name, this.value, this);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 校验错误,不触发 onChange
|
||||||
|
this.parent?.onChange?.(this.name, this.value, this);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,73 +1,40 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { css } from 'styled-components';
|
import { css, Box, HTMLCoralProps, Text } from 'coral-system';
|
||||||
import { Checkbox, Tooltip } from 'antd';
|
import { Checkbox, Tooltip } from 'antd';
|
||||||
import { Box, HTMLCoralProps, Text } from 'coral-system';
|
|
||||||
import { CollapsePanel } from '@music163/tango-ui';
|
import { CollapsePanel } from '@music163/tango-ui';
|
||||||
import { isNil, isString } from '@music163/tango-helpers';
|
import { isNil, isString } from '@music163/tango-helpers';
|
||||||
|
|
||||||
export interface FormControlProps extends Omit<FormLabelProps, 'type'> {
|
export interface FormControlProps extends Omit<FormLabelProps, 'type'> {
|
||||||
/**
|
|
||||||
* @deprecated 废弃 subForm 使用 FormObject 代替
|
|
||||||
*/
|
|
||||||
type?: 'formItem' | 'subForm';
|
|
||||||
/**
|
|
||||||
* 默认是否折叠,仅适用于 subForm 类型
|
|
||||||
*/
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
extra?: React.ReactNode;
|
extra?: React.ReactNode;
|
||||||
error?: string;
|
error?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormControl(props: FormControlProps) {
|
export function FormControl({
|
||||||
const {
|
visible,
|
||||||
type,
|
label,
|
||||||
defaultCollapsed = true,
|
note,
|
||||||
visible,
|
tip,
|
||||||
label,
|
docs,
|
||||||
note,
|
extra,
|
||||||
tip,
|
error,
|
||||||
docs,
|
children,
|
||||||
extra,
|
...rest
|
||||||
error,
|
}: FormControlProps) {
|
||||||
children,
|
|
||||||
...rest
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
if (type === 'formItem') {
|
|
||||||
return (
|
|
||||||
<Box className="FormControl" display={visible ? 'block' : 'none'} {...rest}>
|
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb="s">
|
|
||||||
<FormLabel label={label} note={note} tip={tip} docs={docs} />
|
|
||||||
{extra}
|
|
||||||
</Box>
|
|
||||||
<Box>{children}</Box>
|
|
||||||
{!!error && (
|
|
||||||
<Box mt="m" color="red">
|
|
||||||
{error}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// else type === 'subForm'
|
|
||||||
return (
|
return (
|
||||||
<CollapsePanel
|
<Box className="FormControl" display={visible ? 'block' : 'none'} {...rest}>
|
||||||
title={<FormLabel label={label} note={note} tip={tip} docs={docs} />}
|
<Box display="flex" justifyContent="space-between" alignItems="center" mb="s">
|
||||||
extra={
|
<FormLabel label={label} note={note} tip={tip} docs={docs} />
|
||||||
<Box flex="1" textAlign="right">
|
{extra}
|
||||||
{extra}
|
</Box>
|
||||||
|
<Box>{children}</Box>
|
||||||
|
{!!error && (
|
||||||
|
<Box mt="m" color="red">
|
||||||
|
{error}
|
||||||
</Box>
|
</Box>
|
||||||
}
|
)}
|
||||||
defaultCollapsed={defaultCollapsed}
|
</Box>
|
||||||
borderTop="solid"
|
|
||||||
borderTopColor="line.normal"
|
|
||||||
>
|
|
||||||
<Box bg="#f9f9f9">{children}</Box>
|
|
||||||
<Box color="red">{error}</Box>
|
|
||||||
</CollapsePanel>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
import { register } from './form-item';
|
||||||
|
import { ExpressionSetter } from './setter';
|
||||||
|
|
||||||
|
// 预注册基础 Setter
|
||||||
|
register({
|
||||||
|
name: 'expressionSetter',
|
||||||
|
component: ExpressionSetter,
|
||||||
|
});
|
||||||
|
|
||||||
export * from './form';
|
export * from './form';
|
||||||
export * from './form-item';
|
export * from './form-item';
|
||||||
export * from './form-object';
|
export * from './form-object';
|
||||||
export * from './form-model';
|
export * from './form-model';
|
||||||
export * from './context';
|
export * from './context';
|
||||||
export * from './setters';
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormItemComponentProps } from './form-item';
|
||||||
|
|
||||||
|
// FIXME: 内置 ExpressionSetter,默认全部 fallback 到此 setter
|
||||||
|
export function ExpressionSetter(props: FormItemComponentProps<string>) {
|
||||||
|
return <div>exp setter</div>;
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { TextAreaSetter, TextSetter } from './text-setter';
|
|
||||||
import { ChoiceSetter } from './choice-setter';
|
|
||||||
import { NumberSetter, SliderSetter } from './number-setter';
|
|
||||||
import { BoolSetter } from './bool-setter';
|
|
||||||
import { IFormItemCreateOptions } from '../form-item';
|
|
||||||
import { ExpressionSetter } from './exp-setter';
|
|
||||||
|
|
||||||
export const INTERNAL_SETTERS: IFormItemCreateOptions[] = [
|
|
||||||
{
|
|
||||||
name: 'boolSetter',
|
|
||||||
component: BoolSetter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'choiceSetter',
|
|
||||||
component: ChoiceSetter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'expSetter',
|
|
||||||
alias: ['expressionSetter'],
|
|
||||||
component: ExpressionSetter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'numberSetter',
|
|
||||||
component: NumberSetter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'textSetter',
|
|
||||||
component: TextSetter,
|
|
||||||
},
|
|
||||||
{ name: 'textAreaSetter', component: TextAreaSetter },
|
|
||||||
{
|
|
||||||
name: 'sliderSetter',
|
|
||||||
component: SliderSetter,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export * from './bool-setter';
|
|
||||||
export * from './choice-setter';
|
|
||||||
export * from './number-setter';
|
|
||||||
export * from './text-setter';
|
|
||||||
@@ -3,6 +3,28 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.1.7](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.6...@music163/tango-ui@0.1.7) (2023-10-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- enhance expSetter ([#43](https://github.com/netease/tango/issues/43)) ([5ebbb42](https://github.com/netease/tango/commit/5ebbb428fb3fb786d330ab01959028443338d315))
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.2...@music163/tango-ui@0.1.3) (2023-09-13)
|
## [0.1.3](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.2...@music163/tango-ui@0.1.3) (2023-09-13)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@music163/tango-ui",
|
"name": "@music163/tango-ui",
|
||||||
"version": "0.1.3",
|
"version": "0.1.7",
|
||||||
"description": "ui widgets of tango",
|
"description": "ui widgets of tango",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
@@ -29,20 +29,23 @@
|
|||||||
"prepublishOnly": "yarn build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16.8.0"
|
"react": ">= 16.8",
|
||||||
|
"styled-components": ">= 4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.8.0",
|
"@ant-design/icons": "^4.8.0",
|
||||||
"@codemirror/autocomplete": "^6.7.1",
|
"@codemirror/autocomplete": "^6.10.2",
|
||||||
"@codemirror/lang-javascript": "^6.1.2",
|
"@codemirror/lang-javascript": "^6.2.1",
|
||||||
"@codemirror/search": "^6.2.3",
|
"@codemirror/lint": "^6.4.2",
|
||||||
"@music163/tango-helpers": "^0.1.2",
|
"@codemirror/search": "^6.5.4",
|
||||||
|
"@music163/tango-helpers": "^0.1.4",
|
||||||
"@uiw/react-codemirror": "^4.19.3",
|
"@uiw/react-codemirror": "^4.19.3",
|
||||||
"antd": "^4.24.2",
|
"antd": "^4.24.2",
|
||||||
"antd-easy-form": "^1.1.3",
|
"antd-easy-form": "^1.1.3",
|
||||||
"coral-system": "^1.0.5",
|
"coral-system": "^1.0.5",
|
||||||
|
"eslint-linter-browserify": "^8.51.0",
|
||||||
"react-json-view": "^1.21.3",
|
"react-json-view": "^1.21.3",
|
||||||
"react-monaco-editor-lite": "^1.2.0"
|
"react-monaco-editor-lite": "^1.2.3"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ export interface CopyClipboardProps {
|
|||||||
* 拷贝成功后到回调
|
* 拷贝成功后到回调
|
||||||
*/
|
*/
|
||||||
onCopy?: () => void;
|
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) {
|
export function CopyClipboard({ onCopy, text, children }: CopyClipboardProps) {
|
||||||
@@ -37,7 +39,7 @@ export function CopyClipboard({ onCopy, text, children }: CopyClipboardProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (typeof children === 'function') {
|
if (typeof children === 'function') {
|
||||||
return React.cloneElement(children(copied), {
|
return React.cloneElement(children({ copied, onClick }), {
|
||||||
onClick,
|
onClick,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { css } from 'styled-components';
|
import { css, Box } from 'coral-system';
|
||||||
import { Box } from 'coral-system';
|
|
||||||
import { FolderOpenOutlined, FolderOutlined, FileOutlined } from '@ant-design/icons';
|
import { FolderOpenOutlined, FolderOutlined, FileOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { Box, HTMLCoralProps } from 'coral-system';
|
||||||
import CodeMirror, { ReactCodeMirrorProps } from '@uiw/react-codemirror';
|
import CodeMirror, { ReactCodeMirrorProps } from '@uiw/react-codemirror';
|
||||||
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
import { javascript, javascriptLanguage, esLint } from '@codemirror/lang-javascript';
|
||||||
import { CompletionContext } from '@codemirror/autocomplete';
|
import { CompletionContext } from '@codemirror/autocomplete';
|
||||||
import { syntaxTree } from '@codemirror/language';
|
import { syntaxTree } from '@codemirror/language';
|
||||||
|
import { linter, lintGutter } from '@codemirror/lint';
|
||||||
|
import * as eslint from 'eslint-linter-browserify';
|
||||||
import { getValue } from '@music163/tango-helpers';
|
import { getValue } from '@music163/tango-helpers';
|
||||||
import { Box, HTMLCoralProps } from 'coral-system';
|
|
||||||
|
|
||||||
const completePropertyAfter = ['PropertyName', '.', '?.'];
|
const completePropertyAfter = ['PropertyName', '.', '?.'];
|
||||||
const dontCompleteIn = [
|
const dontCompleteIn = [
|
||||||
@@ -15,6 +17,17 @@ const dontCompleteIn = [
|
|||||||
'PropertyDefinition',
|
'PropertyDefinition',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const eslintConfig = {
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
|
||||||
function completeProperties(from: number, object: Object) {
|
function completeProperties(from: number, object: Object) {
|
||||||
const options = [];
|
const options = [];
|
||||||
for (const name in object) {
|
for (const name in object) {
|
||||||
@@ -116,6 +129,10 @@ export interface InputCodeProps extends ReactCodeMirrorProps {
|
|||||||
* 自定义补全的选项列表
|
* 自定义补全的选项列表
|
||||||
*/
|
*/
|
||||||
autoCompleteOptions?: string[];
|
autoCompleteOptions?: string[];
|
||||||
|
/**
|
||||||
|
* 启用 ESLint
|
||||||
|
*/
|
||||||
|
enableESLint?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputCode({
|
export function InputCode({
|
||||||
@@ -126,6 +143,7 @@ export function InputCode({
|
|||||||
autoCompleteOptions,
|
autoCompleteOptions,
|
||||||
showLineNumbers,
|
showLineNumbers,
|
||||||
showFoldGutter,
|
showFoldGutter,
|
||||||
|
enableESLint = false,
|
||||||
...rest
|
...rest
|
||||||
}: InputCodeProps) {
|
}: InputCodeProps) {
|
||||||
const globalJavaScriptCompletions = useMemo(() => {
|
const globalJavaScriptCompletions = useMemo(() => {
|
||||||
@@ -136,15 +154,16 @@ export function InputCode({
|
|||||||
});
|
});
|
||||||
}, [autoCompleteContext, autoCompleteOptions]);
|
}, [autoCompleteContext, autoCompleteOptions]);
|
||||||
const { rootStyle, codeSetup } = useInputCode({ shape, status, showLineNumbers, showFoldGutter });
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="InputCode" display="flex" alignItems="center" overflow="hidden" {...rootStyle}>
|
<Box className="InputCode" display="flex" alignItems="center" overflow="hidden" {...rootStyle}>
|
||||||
<Box flex="1" overflow="auto">
|
<Box flex="1" overflow="auto">
|
||||||
<CodeMirror
|
<CodeMirror extensions={extensions} basicSetup={codeSetup} {...rest} />
|
||||||
extensions={[javascript({ jsx: true }), globalJavaScriptCompletions]}
|
|
||||||
basicSetup={codeSetup}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
{suffix}
|
{suffix}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -166,7 +185,6 @@ function useInputCode({
|
|||||||
border: 'solid',
|
border: 'solid',
|
||||||
borderColor: status === 'error' ? 'error.60' : 'line.normal',
|
borderColor: status === 'error' ? 'error.60' : 'line.normal',
|
||||||
borderRadius: 's',
|
borderRadius: 's',
|
||||||
px: 'm',
|
|
||||||
};
|
};
|
||||||
lineNumbers = false;
|
lineNumbers = false;
|
||||||
foldGutter = false;
|
foldGutter = false;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { Box, coral, Text } from 'coral-system';
|
import { css, Box, coral, Text } from 'coral-system';
|
||||||
import { css } from 'styled-components';
|
|
||||||
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { noop } from '@music163/tango-helpers';
|
import { noop } from '@music163/tango-helpers';
|
||||||
import { Popconfirm } from 'antd';
|
import { Popconfirm } from 'antd';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { css } from 'styled-components';
|
import { css, Box, Text, HTMLCoralProps } from 'coral-system';
|
||||||
import { Box, Text, HTMLCoralProps } from 'coral-system';
|
|
||||||
import { merge } from '@music163/tango-helpers';
|
import { merge } from '@music163/tango-helpers';
|
||||||
|
|
||||||
export interface PanelProps extends Omit<HTMLCoralProps<'div'>, 'title'> {
|
export interface PanelProps extends Omit<HTMLCoralProps<'div'>, 'title'> {
|
||||||
|
|||||||