Compare commits

..

3 Commits

Author SHA1 Message Date
Wells 8ac0954d65 fix: update state tree data 2024-06-26 16:48:52 +08:00
Wells 0cb71a8634 fix: check if prototype exists in selection tool 2024-06-25 17:45:34 +08:00
Wells a7759494de fix: update toggleButton arrow style 2024-06-21 19:40:19 +08:00
90 changed files with 408 additions and 1627 deletions
-1
View File
@@ -9,4 +9,3 @@ coverage/
.umi-production/
.log
.eslintcache
*.pem
+5 -22
View File
@@ -35,13 +35,13 @@ You can view the detailed usage guide through the following links:
You can use tango to build lowcode builders, for example:
| Preview | Description |
| ---------------------------------------------------------------------------------------------- | ----------------------- |
| Preview | Description |
|---------|-------------------------|
| ![image](https://github.com/NetEase/tango/assets/6984035/56a71741-4536-4336-803a-d81d51128a76) | Internal App Builder |
| ![dashboard app](public/dashboard-builder.png) | Dashboard App Builder |
| ![image](https://github.com/NetEase/tango/assets/6984035/7f817e59-f7aa-4476-bc29-ac3e33fadbf7) | Dashboard App Builder |
| ![image](https://github.com/NetEase/tango/assets/6984035/790d88e1-d716-4f99-868a-31dda25e9fb1) | Mobile App Builder |
| ![rn app](public/rn-builder.png) | ReactNative App Builder |
| ![mail app](public/mail-builder.png) | Email Builder |
| ![rn app](public/rn-builder.png) | ReactNative App Builder |
| ![mail app](public/mail-builder.png) | Email Builder |
## 🌐 Compatibility
@@ -70,23 +70,6 @@ yarn
yarn start
```
### Local Https certificate
If you need to use https in the local development environment, you can use the following command to generate a certificate:
```bash
brew install mkcert
# install the local certificate
mkcert -install
# enter the playground app directory
cd apps/playground
# generate a certificate
mkcert local.netease.com # or change to your own domain name
```
## 💬 Community
Join NetEase Tango Community to share your ideas, suggestions, or questions and connect with other users and contributors.
+8 -24
View File
@@ -35,13 +35,14 @@
你可以使用 Tango 快速构建多种类型的基于源码的低代码搭建工具,例如:
| 预览图 | 说明 |
| ---------------------------------------------------------------------------------------------- | -------------------- |
| ![image](https://github.com/NetEase/tango/assets/6984035/56a71741-4536-4336-803a-d81d51128a76) | 中后台系统搭建 |
| ![dashboard app](public/dashboard-builder.png) | 仪表盘应用搭建 |
| ![image](https://github.com/NetEase/tango/assets/6984035/790d88e1-d716-4f99-868a-31dda25e9fb1) | H5活动页面搭建 |
| ![rn app](public/rn-builder.png) | ReactNative 应用搭建 |
| ![mail app](public/mail-builder.png) | 营销类邮件搭建 |
| 预览图 | 说明 |
|---------|-------------------------|
| ![image](https://github.com/NetEase/tango/assets/6984035/56a71741-4536-4336-803a-d81d51128a76) | 中后台系统搭建 |
| ![image](https://github.com/NetEase/tango/assets/6984035/7f817e59-f7aa-4476-bc29-ac3e33fadbf7) | 仪表盘应用搭建 |
| ![image](https://github.com/NetEase/tango/assets/6984035/790d88e1-d716-4f99-868a-31dda25e9fb1) | H5活动页面搭建 |
| ![rn app](public/rn-builder.png) | ReactNative 应用搭建 |
| ![mail app](public/mail-builder.png) | 营销类邮件搭建 |
## 🌐 兼容环境
@@ -70,23 +71,6 @@ yarn
yarn start
```
### 本地 https 证书
如果需要在本地开发环境中使用 https,可以使用以下命令生成证书:
```bash
brew install mkcert
# 将 mkcert 添加到本地根 CA,仅在本地生效
mkcert -install
# 进入 playground 应用目录
cd apps/playground
# 为网站生成一个由 mkcert 签名的证书
mkcert local.netease.com
```
## 💬 社区讨论
参与 NetEase Tango 的社区,以分享您的想法、建议或问题,并与其他用户和贡献者建立联系。
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4sAqDE3nhtSWD
YqV4mlglnzIs2AioZr2uxfj/OKrpE1ZML/dflJyc5NGUpOZWV+W1vRc70wWVNFxB
Si8nrYrrseoID7Ehxxe6+J1m/JsEbhZWHwV3M1iGW7JD7VhujaDpSLg67wMTMpSW
3BUS+0NT+MNfCisp+u/ls4fXPCTgXiWcjMULm95oLlTeYwurPHfxwn9f9hh85I5I
K3dI7w8MsQN7EFNrJz3UfrNLpmARMtSUBMwBYqrCitoUxRT0q7KYCru7kHxGPB6A
SQrFpSpOWT79vp8GBSp9g3axhRsK+aEgzQ0QGrFPHBwBP/kWgmdB1URc/K7STSC9
xyMNZOM7AgMBAAECggEATbyDYDKVbaRymr+tyHqmuYctdmSkGEXOdz8LFFoEzj/+
ZekWpVuHJB7H/FoAPb2XiYyCuAKVafz1C+IuwPOx8o0bZbM9KmuDmIZGsm+GzDGO
I0fBZC/vhfYYrbC8NSRV43thWCT8VVtH4cvW7vtllnWxvlIoYu4lhbiwZ68AEpO1
8PlJsOhnagfkFpG8LehSDs9if5L+Ig0geQEcUY3pGUM9LKYsRoxFv6Vh8wQku1XD
YOxLvLWDPqJ75m816t50RARe3e6eDJ+ukzDCoDjmLP8uhf1sk1FcL5aOgUNU3VOi
P6sIH5uzuD0MA3FXx2mItYLOc44r4uaFJZiRUMEXGQKBgQDCjUYblE4rh0nKFkqx
AhgfcRgpgLbP42OwFtD0u20RXdZAEJDRDQ43NdOgjFp2B50k75LBPIoFXYUZdzKj
l7S2FH5O9Cc6hEgZGI4hCiYd+nQVI1mxv820YnzZJN4oooxyL+B5RlnW5cDT7LJN
11Q5PRewkM+XpfbE3lKUjTNcDwKBgQDzBS+j8T9GJSzV2xgA+mpGHe1RjDq6yQtk
Rl5GWpflW4Nr3/58BbmCWNA8WP0NOWutJhrkltwLCqJC8OZX/WHnmcmq2k+UAfQQ
z98p+xcMWOTcl5XIbveuhYoZb2udt9WVJLcpRVxVhEWcBrtHsrpScZXsKYf/TaZ1
JtCjPU5KFQKBgGXj/UqmYkYzxXZ8NEP998pHvcLGsXew/F2VpXv2yQlmXrYQtvd1
YfOSUjJsL1hPZoKYogBoB1UGBJYwsimxYyhVFU2eWwkvjF7wWEd/fDUJHVCQwgHw
lPga+I303BDmCK4o2uRI7vY4P8P92+gelPKhR03mmYIvnky+rvsz9CkLAoGAVerl
du6Z0jDecxUsnVvrKrL17jjHorXlYyRFvFXXEe2SvxbIIAzeEylXJZ7IiF5epS6t
n1d+oCC4UTZeGYmpjXofhFn+fHNcWH1FhtAQy/q5nwuwltY0luz7cBamU3Jk/n+m
id7N0CYdfhYbLDQSD4e822ureyV2zkBUzwGjpikCgYEAoCVbhkxVrQHRmxLC2jDx
pM94W1ij9TqblfGg3OMpPXjpTdlknatWjBw7JkoyxYiYJq5Gi2p4QV6HHUvS1osj
BB+4A99/PD9A0ICoJglz8SXlGe/895zwHwpKT9f0zjizQrEd2KCPf75URa+t2FGY
B2GkXsbChRWVrey5+uMzUD8=
-----END PRIVATE KEY-----
+25
View File
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEQjCCAqqgAwIBAgIRAIeLJ2jwt4MSKycHFmFXL6YwDQYJKoZIhvcNAQELBQAw
fTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSkwJwYDVQQLDCB3d3N1
bkBzdW4tbWJwLmxvY2FsIChTVU4gV2Vpd2VpKTEwMC4GA1UEAwwnbWtjZXJ0IHd3
c3VuQHN1bi1tYnAubG9jYWwgKFNVTiBXZWl3ZWkpMB4XDTIyMDcyODAxNTIxMFoX
DTI0MTAyODAxNTIxMFowVDEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNl
cnRpZmljYXRlMSkwJwYDVQQLDCB3d3N1bkBzdW4tbWJwLmxvY2FsIChTVU4gV2Vp
d2VpKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALiwCoMTeeG1JYNi
pXiaWCWfMizYCKhmva7F+P84qukTVkwv91+UnJzk0ZSk5lZX5bW9FzvTBZU0XEFK
Lyetiuux6ggPsSHHF7r4nWb8mwRuFlYfBXczWIZbskPtWG6NoOlIuDrvAxMylJbc
FRL7Q1P4w18KKyn67+Wzh9c8JOBeJZyMxQub3mguVN5jC6s8d/HCf1/2GHzkjkgr
d0jvDwyxA3sQU2snPdR+s0umYBEy1JQEzAFiqsKK2hTFFPSrspgKu7uQfEY8HoBJ
CsWlKk5ZPv2+nwYFKn2DdrGFGwr5oSDNDRAasU8cHAE/+RaCZ0HVRFz8rtJNIL3H
Iw1k4zsCAwEAAaNmMGQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUF
BwMBMB8GA1UdIwQYMBaAFDILZR+SW32Ug0HkAKrH1KT2a1ttMBwGA1UdEQQVMBOC
EWxvY2FsLm5ldGVhc2UuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQCEdeV2Likl4MUt
wWeN/X3tAR3L7mkl7e4f0CQ4v2lOs8cekOP7p3ZFk5nSyVbxda3usPB0OtbVTibw
qZ1n/TMRb5epHroXIMleqngP82zFZNg+WVyHm/ZAml8gVD0VZjYM1Tb+U9+SrgGw
lvZndbuUXa3h/d0irPH90KsdEeAR1SqwzAzvLO2JgvBuimHRceQWcTiiGWDZOyii
tux4U+pEzh0Dia40uFozFAoYZPDUpZ+gsiQ3MR3HOrAu2OR0a+CfelDXZNLR3C4z
cBKr53ajAbHstnpxKJh9IfeKph44PrbDo6HMe1XPprl7idoEesX3Ma3SBgm4lmId
cfEOUWNIYOgtOT5bflXNNyzXOgYVjpLnjwg4EOASO/XIjhvqdjIyCu7HJMIcZ9oo
Aob2exeluv5LVwic1UJQDl2McDBIgJ8R32oEN4skCKIBSbWlxWyjnxL5I5WkJF4y
vlSx8bKAjVr8map0C3rWISuZdgheJkF96qjwhVSxOv++p6srq6M=
-----END CERTIFICATE-----
+5 -5
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Group, Text } from 'coral-system';
import { Box, Group } from 'coral-system';
import { Avatar, Space, Switch } from 'antd';
import { BranchesOutlined, MenuOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { registerSetter } from '@music163/tango-designer';
@@ -66,12 +66,12 @@ export function Logo() {
export function ProjectDetail() {
return (
<Box display="flex" alignItems="center" columnGap="l">
<Text className="ProjectName" fontSize="18px" fontWeight="bold" truncated>
<Box className="ProjectName" fontSize="18px" fontWeight="bold">
community-test
</Text>
<Text className="BranchName" as="code" fontSize="14px" truncated>
</Box>
<Box className="BranchName" as="code" fontSize="14px">
<BranchesOutlined /> feature/list
</Text>
</Box>
</Box>
);
}
+1 -1
View File
@@ -166,7 +166,7 @@ import { OutButton } from "../components";
class App extends React.Component {
render() {
return (
<Page title={tango.stores.app.title} subTitle={<><Button>hello</Button></>}>
<Page title={tango.stores.app.title} subTitle={111}>
<Section tid="section0" />
<Section tid="section1" title="Section Title">
your input: <Input tid="input1" defaultValue="hello" />
@@ -132,9 +132,6 @@ export const nativeDomPrototypes = () => {
title: '样式',
group: 'style',
setter: 'expressionSetter',
setterProps: {
expressionType: 'cssObject',
},
},
{
name: 'className',
+8 -8
View File
@@ -113,8 +113,8 @@ export default function App() {
actions={
<Box px="l">
<Toolbar>
<Toolbar.Item key="routeSwitch" placement="left" activeViews={['design']} />
<Toolbar.Item key="addPage" placement="left" activeViews={['design']}>
<Toolbar.Item key="routeSwitch" placement="left" />
<Toolbar.Item key="addPage" placement="left">
<Action
tooltip="添加页面"
shape="outline"
@@ -122,10 +122,10 @@ export default function App() {
onClick={() => setShowNewPageModal(true)}
/>
</Toolbar.Item>
<Toolbar.Item key="history" placement="left" activeViews={['design']} />
<Toolbar.Item key="preview" placement="left" activeViews={['design']} />
<Toolbar.Item key="togglePanel" placement="right" activeViews={['design']} />
<Toolbar.Item key="history" placement="left" />
<Toolbar.Item key="preview" placement="left" />
<Toolbar.Item key="modeSwitch" placement="right" />
<Toolbar.Item key="togglePanel" placement="right" />
<Toolbar.Separator />
<Toolbar.Item placement="right">
<Space>
@@ -187,9 +187,6 @@ export default function App() {
/>
</Sidebar>
<WorkspacePanel>
<WorkspaceView mode="code">
<CodeEditor />
</WorkspaceView>
<WorkspaceView mode="design">
<Sandbox
onMessage={(e) => {
@@ -211,6 +208,9 @@ export default function App() {
navigatorExtra={<Button size="small">hello world</Button>}
/>
</WorkspaceView>
<WorkspaceView mode="code">
<CodeEditor />
</WorkspaceView>
</WorkspacePanel>
<SettingPanel />
</DesignerPanel>
@@ -69,14 +69,6 @@ const prototypeHasBasicProps: IComponentPrototype = {
title: 'codeSetter',
setter: 'codeSetter',
},
{
name: 'style',
title: 'codeSetter(cssObject)',
setter: 'codeSetter',
setterProps: {
expressionType: 'cssObject',
},
},
{
name: 'text',
title: 'textSetter',
@@ -276,9 +268,6 @@ export function InitValues() {
name: 'style',
title: 'codeSetter',
setter: 'codeSetter',
setterProps: {
expressionType: 'cssObject',
},
},
{
name: 'object',
+1 -5
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { Action, InputCode, InputStyleCode } from '@music163/tango-ui';
import { Action, InputCode } from '@music163/tango-ui';
import { BlockOutlined } from '@ant-design/icons';
export default {
@@ -28,10 +28,6 @@ export function Basic() {
);
}
export function CSS() {
return <InputStyleCode enableESLint suffix={<Action icon={<BlockOutlined />} size="small" />} />;
}
const code = `
function foo() {
console.log("test");
+4 -4
View File
@@ -15,10 +15,10 @@
"test:watch": "jest --watch",
"eslint": "eslint packages/**/src/*.{ts,tsx} --cache",
"publish": "lerna publish",
"ver": "lerna version --no-private --conventional-commits",
"release": "yarn eslint && yarn build && yarn ver && lerna publish from-git",
"release:npm": "yarn eslint && yarn build && yarn ver && lerna publish from-package",
"release:beta": "yarn eslint && yarn build && yarn ver && lerna publish from-package --dist-tag beta",
"ver": "lerna version --no-private",
"release": "yarn eslint && yarn build && lerna version --no-private --conventional-commits && lerna publish from-git",
"release:npm": "yarn eslint && yarn build && yarn run ver && lerna publish from-git",
"release:beta": "yarn eslint && yarn build && yarn run ver && lerna publish from-package --dist-tag beta",
"up": "yarn upgrade-interactive --latest",
"prepare": "husky install"
},
-16
View File
@@ -3,22 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.8](https://github.com/netease/tango/compare/@music163/tango-context@1.1.7...@music163/tango-context@1.1.8) (2024-08-06)
**Note:** Version bump only for package @music163/tango-context
## [1.1.7](https://github.com/netease/tango/compare/@music163/tango-context@1.1.6...@music163/tango-context@1.1.7) (2024-08-05)
**Note:** Version bump only for package @music163/tango-context
## [1.1.6](https://github.com/netease/tango/compare/@music163/tango-context@1.1.5...@music163/tango-context@1.1.6) (2024-08-01)
**Note:** Version bump only for package @music163/tango-context
## [1.1.5](https://github.com/netease/tango/compare/@music163/tango-context@1.1.4...@music163/tango-context@1.1.5) (2024-07-12)
**Note:** Version bump only for package @music163/tango-context
## [1.1.4](https://github.com/netease/tango/compare/@music163/tango-context@1.1.3...@music163/tango-context@1.1.4) (2024-06-20)
**Note:** Version bump only for package @music163/tango-context
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-context",
"version": "1.1.8",
"version": "1.1.4",
"description": "react context for tango-apps",
"keywords": [
"react",
@@ -31,8 +31,8 @@
"react": ">= 16.8"
},
"dependencies": {
"@music163/tango-core": "^1.4.2",
"@music163/tango-helpers": "^1.2.2",
"@music163/tango-core": "^1.3.2",
"@music163/tango-helpers": "^1.2.0",
"mobx-react-lite": "4.0.7"
},
"publishConfig": {
-26
View File
@@ -3,32 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.2](https://github.com/netease/tango/compare/@music163/tango-core@1.4.1...@music163/tango-core@1.4.2) (2024-08-06)
### Bug Fixes
- view module \_nodesTree set init value ([#194](https://github.com/netease/tango/issues/194)) ([269e304](https://github.com/netease/tango/commit/269e304d685c4fcc80635b9d04220cda333b80c4))
## [1.4.1](https://github.com/netease/tango/compare/@music163/tango-core@1.4.0...@music163/tango-core@1.4.1) (2024-08-05)
**Note:** Version bump only for package @music163/tango-core
# [1.4.0](https://github.com/netease/tango/compare/@music163/tango-core@1.3.3...@music163/tango-core@1.4.0) (2024-08-01)
### Bug Fixes
- update types ([67d602a](https://github.com/netease/tango/commit/67d602a3ec6b7f74156bb78fda6f3b4bc2676e55))
### Features
- add isError and errorMessage to File & add isAstSynced to Module ([#190](https://github.com/netease/tango/issues/190)) ([8dd289c](https://github.com/netease/tango/commit/8dd289cd7daba628e54dc6b8929f22a1e2245160))
## [1.3.3](https://github.com/netease/tango/compare/@music163/tango-core@1.3.2...@music163/tango-core@1.3.3) (2024-07-12)
### Bug Fixes
- update designer config ([#186](https://github.com/netease/tango/issues/186)) ([85c053b](https://github.com/netease/tango/commit/85c053b3db6d652b41c9873dba1366315174833f))
## [1.3.2](https://github.com/netease/tango/compare/@music163/tango-core@1.3.1...@music163/tango-core@1.3.2) (2024-06-20)
### Bug Fixes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-core",
"version": "1.4.2",
"version": "1.3.2",
"description": "tango core",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -28,7 +28,7 @@
"@babel/parser": "^7.23.5",
"@babel/traverse": "^7.23.5",
"@babel/types": "^7.23.5",
"@music163/tango-helpers": "^1.2.2",
"@music163/tango-helpers": "^1.2.0",
"@types/babel__generator": "^7.6.7",
"@types/babel__traverse": "^7.20.4",
"mobx": "6.12.3",
+1 -1
View File
@@ -35,7 +35,7 @@ export function isTangoVariable(name: string) {
return /^tango\??\.(stores|services)\??\./.test(name) && name.split('.').length > 2;
}
const templatePattern = /^{(.+)}$/;
const templatePattern = /^{(.+)}$/s;
/**
* 判断给定字符串是否被表达式容器`{expCode}`包裹
+16 -4
View File
@@ -3,7 +3,7 @@
*/
import generator, { GeneratorOptions } from '@babel/generator';
import * as t from '@babel/types';
import { Dict, logger, wrapCode } from '@music163/tango-helpers';
import { logger, wrapCode } from '@music163/tango-helpers';
import { formatCode } from '../string';
const defaultGeneratorOptions: GeneratorOptions = {
@@ -25,7 +25,19 @@ export function ast2code(ast: t.Node, options: GeneratorOptions = defaultGenerat
return code;
}
const bracketPattern = /^\(.+\)$/;
const bracketPattern = /^\(.+\)$/s;
/**
* 是否被 () 包裹
*
* @example ({ foo: 'foo' }) -> true
* @example { foo: 'foo' } -> false
*
* @param str 目标字符串
*/
function isWrappingWithBrackets(str: string) {
return bracketPattern.test(str);
}
/**
* 将表达式生成为块级代码
@@ -42,7 +54,7 @@ export function expression2code(node: t.Expression) {
const isWrappingExpression = t.isObjectExpression(node) || t.isFunctionExpression(node);
if (isWrappingExpression && bracketPattern.test(ret)) {
if (isWrappingExpression && isWrappingWithBrackets(ret)) {
// 如果是对象,输出包含 ({}),则去掉首尾的括号
ret = ret.slice(1, -1);
}
@@ -183,7 +195,7 @@ export function node2value(node: t.Node, isWrapCode = true): any {
);
if (isSimpleObject) {
// simple object: { key1, key2, key3 }
ret = node.properties.reduce<Dict>((prev, propertyNode) => {
ret = node.properties.reduce((prev, propertyNode) => {
if (propertyNode.type === 'ObjectProperty') {
const key = keyNode2value(propertyNode.key);
const value = node2value(propertyNode.value, isWrapCode);
+6 -2
View File
@@ -68,7 +68,11 @@ export function isValidExpressionCode(code: string) {
* @returns
*/
export function code2ast(code: string): t.File {
return parse(code, babelParserConfig);
try {
return parse(code, babelParserConfig);
} catch (err) {
logger.error('[code2ast failed!]', err);
}
}
/**
@@ -171,7 +175,7 @@ export function value2node(
* 将 js 普通对象解析为 t.Node
*/
export function object2node(
obj: Dict,
obj: object,
getValueNode: (value: any, key?: string) => t.Expression = value2node,
) {
if (!isPlainObject(obj)) {
+2 -2
View File
@@ -1009,7 +1009,7 @@ export function serviceConfig2Node(payload: object) {
});
}
export function updateServiceConfigToServiceFile(ast: t.File, config: Dict<Dict>) {
export function updateServiceConfigToServiceFile(ast: t.File, config: Dict<object>) {
traverse(ast, {
CallExpression(path) {
const calleeName = keyNode2value(path.node.callee) as string;
@@ -1017,7 +1017,7 @@ export function updateServiceConfigToServiceFile(ast: t.File, config: Dict<Dict>
const configNode = path.node.arguments[0];
if (t.isObjectExpression(configNode)) {
const newPropertiesNodeMap = Object.keys(config).reduce<Dict>((properties, key) => {
const newPropertiesNodeMap = Object.keys(config).reduce((properties, key) => {
const serviceConfig = config[key];
const property = t.objectProperty(t.identifier(key), serviceConfig2Node(serviceConfig));
properties[key] = property;
+1 -1
View File
@@ -11,7 +11,7 @@ export function namesToImportDeclarations(
names: string[],
nameMap: Dict<IImportSpecifierSourceData>,
) {
const map: Dict = {};
const map = {};
names.forEach((name) => {
const mod = nameMap[name];
if (mod) {
+2 -2
View File
@@ -1,9 +1,9 @@
import { Dict, parseDndId, uuid } from '@music163/tango-helpers';
import { parseDndId, uuid } from '@music163/tango-helpers';
// { id: 1, component, props: { id: 1 }, children: [ { id: 2, component } ] }
export function deepCloneNode(obj: any, component?: string): any {
function deepCloneObject() {
const target: Dict = {};
const target = {};
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
target[key] = deepCloneNode(obj[key], component);
+1 -1
View File
@@ -13,7 +13,7 @@ export class TangoComponentsEntryModule extends TangoModule {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.update(props.code, true, false);
this.update(props.code, false, false);
makeObservable(this, {
_code: observable,
_cleanCode: observable,
-7
View File
@@ -239,13 +239,6 @@ export class Designer {
setActiveView(view: DesignerViewType) {
this._activeView = view;
if (view === 'dual') {
this.setActiveSidebarPanel('');
this.toggleRightPanel(false);
this.toggleIsPreview(true);
} else if (view === 'design') {
this.toggleIsPreview(false);
}
}
setActiveSidebarPanel(panel: string) {
-11
View File
@@ -24,16 +24,6 @@ export class TangoFile {
*/
lastModified: number;
/**
* 文件解析是否出错
*/
isError: boolean;
/**
* 文件解析错误消息
*/
errorMessage: string;
_code: string;
_cleanCode: string;
@@ -50,7 +40,6 @@ export class TangoFile {
this.filename = props.filename;
this.type = props.type;
this.lastModified = Date.now();
this.isError = false;
// 这里主要是为了解决 umi ts 编译错误的问题,@see https://github.com/umijs/umi/issues/7594
if (isSyncCode) {
+2 -18
View File
@@ -10,7 +10,6 @@ import {
IPageConfigData,
IImportSpecifierSourceData,
IImportSpecifierData,
IFileError,
} from '../types';
import { TangoFile, TangoJsonFile } from './file';
import { TangoRouteModule } from './route-module';
@@ -193,18 +192,7 @@ export interface IWorkspace {
renameFile: (oldFilename: string, newFilename: string) => void;
renameFolder: (oldFoldername: string, newFoldername: string) => void;
/**
* 更新文件
* @param filename 文件名
* @param code 代码
* @param isSyncAst 是否同步 ast
*/
updateFile: (filename: string, code: string, isSyncAst?: boolean) => void;
/**
* 检查并同步文件的 ast
*/
syncFiles: () => void;
updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void;
listFiles: () => Record<string, string>;
getFile: (filename: string) => TangoFile;
@@ -310,12 +298,8 @@ export interface IWorkspace {
get bizComps(): string[];
get baseComps(): string[];
get localComps(): string[];
get fileErrors(): IFileError[];
/**
* 是否是有效的项目
* - 包含 tango.config.json
* - 包含视图模块
* - 没有文件错误
* 是否是合法的项目
*/
get isValid(): boolean;
}
+18 -60
View File
@@ -21,11 +21,6 @@ import { IWorkspace } from './interfaces';
export class TangoModule extends TangoFile {
ast: t.File;
/**
* ast 是否与 code 保持同步
*/
isAstSynced: boolean;
/**
* 导入的依赖列表
*/
@@ -38,53 +33,23 @@ export class TangoModule extends TangoFile {
/**
* 基于最新的 ast 进行同步
* @param code 如果传入 code,则基于 code 进行同步
* @param isSyncAst 是否同步 ast
* @param isRefreshWorkspace 是否刷新 workspace
* @param isFormatCode 是否格式化代码
* @param refreshWorkspace 是否刷新 workspace
*/
update(code?: string, isSyncAst = true, isRefreshWorkspace = true) {
update(code?: string, isFormatCode = true, refreshWorkspace = true) {
this.lastModified = Date.now();
try {
if (isNil(code)) {
this._syncByAst();
} else {
this._syncByCode(code, isSyncAst);
}
if (isSyncAst) {
this._analysisAst();
}
this.isAstSynced = isSyncAst;
this.isError = false;
this.errorMessage = undefined;
this.workspace.onFilesChange([this.filename]);
if (isRefreshWorkspace) {
this.workspace.refresh([this.filename]);
}
} catch (err: any) {
this.isError = true;
this.errorMessage = err.message;
if (isNil(code)) {
this._syncByAst();
} else {
this._syncByCode(code, isFormatCode);
}
}
/**
* 基于当前的代码重新生成 ast
*/
updateAst() {
if (!this.isAstSynced) {
try {
this.ast = code2ast(this._code);
this._analysisAst();
this.isAstSynced = true;
this.isError = false;
this.errorMessage = undefined;
} catch (err: any) {
this.isAstSynced = false;
this.isError = true;
this.errorMessage = err.message;
}
this._analysisAst();
this.workspace.onFilesChange([this.filename]);
if (refreshWorkspace) {
this.workspace.refresh([this.filename]);
}
}
@@ -110,26 +75,22 @@ export class TangoModule extends TangoFile {
/**
* 基于输入的源码进行同步
* @param code 源码
* @param isSyncAst 是否同步 ast
* @param isFormatCode 是否格式化代码
* @returns
*/
_syncByCode(code: string, isSyncAst = true) {
_syncByCode(code: string, isFormatCode = true) {
if (code === this._code) {
return;
}
// 提前格式化代码
try {
if (isFormatCode) {
code = formatCode(code);
} catch (err) {
// err ignored, format code failed
}
this._code = code;
this._cleanCode = code;
if (isSyncAst) {
this.ast = code2ast(code);
}
this.ast = code2ast(code);
}
_analysisAst() {
@@ -144,17 +105,14 @@ export class TangoModule extends TangoFile {
export class TangoJsModule extends TangoModule {
constructor(workspace: IWorkspace, props: IFileConfig) {
super(workspace, props, false);
this.update(props.code, true, false);
this.update(props.code, false, false);
makeObservable(this, {
_code: observable,
_cleanCode: observable,
isError: observable,
errorMessage: observable,
code: computed,
cleanCode: computed,
update: action,
updateAst: action,
});
}
}
+1 -5
View File
@@ -1,5 +1,4 @@
import { action, computed, makeObservable, observable, toJS } from 'mobx';
import { logger } from '@music163/tango-helpers';
import {
traverseRouteFile,
addRouteToRouteFile,
@@ -28,13 +27,10 @@ export class TangoRouteModule extends TangoModule {
_routes: observable,
_code: observable,
_cleanCode: observable,
isError: observable,
errorMessage: observable,
routes: computed,
code: computed,
cleanCode: computed,
update: action,
updateAst: action,
});
}
@@ -78,7 +74,7 @@ export class TangoRouteModule extends TangoModule {
*/
removeRoute(route: string) {
if (route === '/') {
logger.warn('index route should not be removed!');
console.warn('index route should not be removed!');
return;
}
const record = this.getRouteByRoutePath(route);
@@ -49,14 +49,11 @@ export class TangoServiceModule extends TangoModule {
_baseConfig: observable,
_code: observable,
_cleanCode: observable,
isError: observable,
errorMessage: observable,
serviceFunctions: computed,
baseConfig: computed,
cleanCode: computed,
code: computed,
update: action,
updateAst: action,
});
}
-3
View File
@@ -31,13 +31,10 @@ export class TangoStoreEntryModule extends TangoModule {
_stores: observable,
_code: observable,
_cleanCode: observable,
isError: observable,
errorMessage: observable,
stores: computed,
code: computed,
cleanCode: computed,
update: action,
updateAst: action,
});
}
+2 -6
View File
@@ -84,7 +84,7 @@ function nodeListToTreeData(list: ITangoViewNodeData[]) {
*/
export class TangoViewModule extends TangoModule implements IViewFile {
// 解析为树结构的 jsxNodes 数组
_nodesTree: ITangoViewNodeData[] = [];
_nodesTree: ITangoViewNodeData[];
/**
* 通过导入组件名查找组件来自的包
*/
@@ -138,14 +138,10 @@ export class TangoViewModule extends TangoModule implements IViewFile {
_code: observable,
_cleanCode: observable,
isError: observable,
errorMessage: observable,
code: computed,
cleanCode: computed,
update: action,
updateAst: action,
});
}
@@ -324,7 +320,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
updateNodeAttributes(nodeId: string, config: Record<string, any>, relatedImports?: string[]) {
if (relatedImports && relatedImports.length) {
// 导入依赖的组件
const newImportData = relatedImports.reduce<Dict<IImportSpecifierData[]>>((prev, name) => {
const newImportData = relatedImports.reduce((prev, name) => {
const proto = this.workspace.getPrototype(name);
const { source, specifiers } = prototype2importDeclarationData(proto, this.filename);
const existSpecifiers: IImportSpecifierData[] = prev[source];
+15 -36
View File
@@ -25,10 +25,10 @@ import {
import { DropMethod } from './drop-target';
import { HistoryMessage, TangoHistory } from './history';
import { TangoNode } from './node';
import { TangoJsModule, TangoModule } from './module';
import { TangoJsModule } from './module';
import { TangoFile, TangoJsonFile, TangoLessFile } from './file';
import { IWorkspace } from './interfaces';
import { IFileConfig, FileType, ITangoConfigPackages, IPageConfigData, IFileError } from '../types';
import { IFileConfig, FileType, ITangoConfigPackages, IPageConfigData } from '../types';
import { SelectSource } from './select-source';
import { DragSource } from './drag-source';
import { TangoRouteModule } from './route-module';
@@ -151,8 +151,11 @@ export class Workspace extends EventTarget implements IWorkspace {
*/
private copyTempNodes: TangoNode[];
/**
* 是否是合法的项目
*/
get isValid() {
return !!this.tangoConfigJson && !!this.activeViewModule && this.fileErrors.length === 0;
return !!this.tangoConfigJson && !!this.activeViewModule;
}
/**
@@ -220,19 +223,6 @@ export class Workspace extends EventTarget implements IWorkspace {
return Object.keys(this.componentsEntryModule?.exportList || {});
}
get fileErrors() {
const errors: IFileError[] = [];
this.files.forEach((file) => {
if (file.isError) {
errors.push({
filename: file.filename,
message: file.errorMessage,
});
}
});
return errors;
}
constructor(options?: IWorkspaceOptions) {
super();
this.history = new TangoHistory(this);
@@ -266,8 +256,6 @@ export class Workspace extends EventTarget implements IWorkspace {
activeViewFile: observable,
pages: computed,
bizComps: computed,
fileErrors: computed,
isValid: computed,
setActiveRoute: action,
setActiveFile: action,
addFile: action,
@@ -451,16 +439,10 @@ export class Workspace extends EventTarget implements IWorkspace {
);
}
updateFile(filename: string, code: string, isSyncAst = true) {
updateFile(filename: string, code: string, shouldFormatCode = false) {
const file = this.getFile(filename);
if (file instanceof TangoModule) {
file.update(code, isSyncAst);
} else {
file.update(code);
}
const shouldFormat = this.projectConfig?.designerConfig?.autoFormatCode;
if (shouldFormat && file instanceof TangoViewModule) {
file.update(code);
if (shouldFormatCode && file instanceof TangoViewModule) {
file.removeUnusedImportSpecifiers().update();
}
this.history.push({
@@ -471,15 +453,12 @@ export class Workspace extends EventTarget implements IWorkspace {
});
}
syncFiles() {
this.files.forEach((file) => {
if (file instanceof TangoModule) {
file.updateAst();
}
});
}
/**
* 删除工作区的文件
* @param filename
*/
removeFile(filename: string) {
// TODO: refactor visitFile to share this logic
if (this.files.get(filename)) {
// 如果是文件,直接删除
this.files.delete(filename);
@@ -535,7 +514,7 @@ export class Workspace extends EventTarget implements IWorkspace {
* @returns { [filename]: fileCode }
*/
listFiles() {
const ret: Dict<string> = {};
const ret = {};
this.files.forEach((file) => {
ret[file.filename] = file.cleanCode;
});
-5
View File
@@ -57,11 +57,6 @@ export interface IFileConfig {
type?: FileType;
}
export interface IFileError {
filename: string;
message: string;
}
/**
* 视图节点数据类型
*/
-42
View File
@@ -3,48 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.4](https://github.com/netease/tango/compare/@music163/tango-designer@1.4.3...@music163/tango-designer@1.4.4) (2024-08-16)
### Bug Fixes
- CodeInput 支持 jsx css 代码补全 ([#198](https://github.com/netease/tango/issues/198)) ([6de5488](https://github.com/netease/tango/commit/6de54884b50d683c74d695a8c13184dc0ce8d3c4))
## [1.4.3](https://github.com/netease/tango/compare/@music163/tango-designer@1.4.2...@music163/tango-designer@1.4.3) (2024-08-09)
### Bug Fixes
- file errors overlay support click to error file & optimize ui ([#197](https://github.com/netease/tango/issues/197)) ([ca56edd](https://github.com/netease/tango/commit/ca56edd9ae201dbeb84b03ad390a09085433a257))
- route matching without query string & toggle button wrapping ([#195](https://github.com/netease/tango/issues/195)) ([9394666](https://github.com/netease/tango/commit/939466648d40331800f15e886256a0789b7e7623))
- update mobile simulator ui ([#196](https://github.com/netease/tango/issues/196)) ([65149f1](https://github.com/netease/tango/commit/65149f1326eaf78e8a27222993c3cacc674cd378))
## [1.4.2](https://github.com/netease/tango/compare/@music163/tango-designer@1.4.1...@music163/tango-designer@1.4.2) (2024-08-06)
**Note:** Version bump only for package @music163/tango-designer
## [1.4.1](https://github.com/netease/tango/compare/@music163/tango-designer@1.4.0...@music163/tango-designer@1.4.1) (2024-08-05)
### Bug Fixes
- parse template code & update maxRow of TextAreaSetter ([#192](https://github.com/netease/tango/issues/192)) ([a1eea02](https://github.com/netease/tango/commit/a1eea02f9409db73f042c74d7903ad84258f25c2))
- show right panel when design view select node & optimize editor css ([#193](https://github.com/netease/tango/issues/193)) ([b83d965](https://github.com/netease/tango/commit/b83d965955c8d33ed8d37063482c7abe079e62b1))
# [1.4.0](https://github.com/netease/tango/compare/@music163/tango-designer@1.3.3...@music163/tango-designer@1.4.0) (2024-08-01)
### Bug Fixes
- update types ([67d602a](https://github.com/netease/tango/commit/67d602a3ec6b7f74156bb78fda6f3b4bc2676e55))
### Features
- add isError and errorMessage to File & add isAstSynced to Module ([#190](https://github.com/netease/tango/issues/190)) ([8dd289c](https://github.com/netease/tango/commit/8dd289cd7daba628e54dc6b8929f22a1e2245160))
## [1.3.3](https://github.com/netease/tango/compare/@music163/tango-designer@1.3.2...@music163/tango-designer@1.3.3) (2024-07-12)
### Bug Fixes
- selection bug without prototype & update ToggleButton style ([#180](https://github.com/netease/tango/issues/180)) ([1d0fb4f](https://github.com/netease/tango/commit/1d0fb4f274a5b767962c54583ab5863329d5bcc5))
- update designer config ([#186](https://github.com/netease/tango/issues/186)) ([85c053b](https://github.com/netease/tango/commit/85c053b3db6d652b41c9873dba1366315174833f))
## [1.3.2](https://github.com/netease/tango/compare/@music163/tango-designer@1.3.1...@music163/tango-designer@1.3.2) (2024-06-20)
### Bug Fixes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-designer",
"version": "1.4.4",
"version": "1.3.2",
"description": "lowcode designer",
"keywords": [
"react"
@@ -33,12 +33,12 @@
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/request": "^0.2.0",
"@music163/tango-context": "^1.1.8",
"@music163/tango-core": "^1.4.2",
"@music163/tango-helpers": "^1.2.2",
"@music163/tango-sandbox": "^1.0.11",
"@music163/tango-setting-form": "^1.2.13",
"@music163/tango-ui": "^1.4.3",
"@music163/tango-context": "^1.1.4",
"@music163/tango-core": "^1.3.2",
"@music163/tango-helpers": "^1.2.0",
"@music163/tango-sandbox": "^1.0.7",
"@music163/tango-setting-form": "^1.2.7",
"@music163/tango-ui": "^1.3.2",
"antd": "^4.24.2",
"cash-dom": "^8.1.2",
"classnames": "^2.5.1",
@@ -1,133 +0,0 @@
import React, { useState } from 'react';
import { Box, css, Text } from 'coral-system';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { CloseOutlined } from '@ant-design/icons';
const errorMessageStyle = css`
padding: 35px;
position: relative;
white-space: pre-wrap;
color: #f45755;
font-weight: bold;
height: 100%;
background-color: #171717;
border-radius: 8px;
.top-bar {
border-radius: 8px 8px 0 0;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 10px;
background-color: #f45755;
}
.WorkspaceErrorOverlayItem {
font-size: 18px;
padding-top: 20px;
padding-bottom: 20px;
.WorkspaceErrorOverlayItemFilename {
color: #48cacb;
text-decoration: underline;
letter-spacing: 1px;
&:hover {
color: #51b1ff;
}
}
}
`;
/**
* 文件错误提示浮层
*/
export const FileErrorsOverlay = observer(() => {
const [isVisible, setIsVisible] = useState<boolean>(true);
const workspace = useWorkspace();
const designer = useDesigner();
const handleClose = () => {
setIsVisible(false);
};
const handleOpenErrorFile = (fileName: string) => {
workspace.setActiveFile(fileName);
designer.setActiveView('code');
};
if (workspace.fileErrors.length === 0) {
return null;
}
if (!isVisible) {
return null;
}
return (
<Box
className="WorkspaceErrorOverlay"
position="absolute"
top="0"
left="0"
right="0"
height="100%"
bg="rgb(84, 84, 84)"
padding="30px"
color="red"
overflow="auto"
zIndex={1999}
>
<Box position="relative" p="4" boxShadow="0 0 10px rgba(0, 0, 0, 0.5)">
<CloseOutlined
style={{
position: 'absolute',
top: 24,
right: 12,
color: '#fff',
zIndex: 2,
}}
onClick={handleClose}
/>
<Box css={errorMessageStyle}>
<Box className="top-bar" />
<Box fontSize="18px" mb="l">
<Text letterSpacing=".5px" color="#c29ef5">
[plugin: @babel/parser]
</Text>
<Text marginLeft={'8px'} color="#f45755">
SyntaxError in {workspace.fileErrors.length} filesClick file to modify the error
code
</Text>
</Box>
<Box>
{workspace.fileErrors.map((fileError, index) => (
<Box
className="WorkspaceErrorOverlayItem"
key={fileError.filename}
borderBottom={index === workspace.fileErrors.length - 1 ? 'none' : 'dashed'}
borderBottomColor="line2"
py="l"
>
<Box
className="WorkspaceErrorOverlayItemFilename"
fontWeight="bold"
style={{ cursor: 'pointer' }}
onClick={() => handleOpenErrorFile(fileError.filename)}
>
{fileError.filename}
</Box>
<Box
as="code"
display="block"
color="#daa15e"
fontSize="16px"
fontWeight={'normal'}
>
{fileError.message}
</Box>
</Box>
))}
</Box>
</Box>
</Box>
</Box>
);
});
@@ -4,4 +4,3 @@ export * from './input-kv';
export * from './variable-tree';
export * from './variable-tree-modal';
export * from './components-popover';
export * from './file-errors-overlay';
@@ -1,11 +1,11 @@
import { Dict, isValidFunctionCode, url2serviceName } from '@music163/tango-helpers';
import { isValidFunctionCode, url2serviceName } from '@music163/tango-helpers';
import { InputCode } from '@music163/tango-ui';
import { Button, Dropdown, Form, FormProps, Input, Select, Space } from 'antd';
import { Box } from 'coral-system';
import React, { useState } from 'react';
interface AddServiceFormProps extends FormProps {
initialValues?: Dict;
initialValues?: object;
onSubmit: (values: object) => void;
onCancel: () => void;
serviceModules: object[];
+1 -3
View File
@@ -1,10 +1,8 @@
import { Dict } from '@music163/tango-helpers';
/**
* 快捷键
*/
export class Hotkey {
private readonly hotkeyMap: Dict = {};
private readonly hotkeyMap = {};
constructor(hotkeys: Record<string, Function>) {
Object.keys(hotkeys).forEach((hotkey) => {
-2
View File
@@ -137,8 +137,6 @@ export function useDnd({
const data = sandboxQuery.getDraggableParentsData(e.target as HTMLElement, true);
if (data && data.id) {
selectSource.select(data);
// 画布选中节点时,自动唤起 right-panel
designer.toggleRightPanel(true);
}
};
+109 -80
View File
@@ -2,6 +2,8 @@ import React, { useRef, useEffect, useCallback } from 'react';
import { Box } from 'coral-system';
import { MultiEditor, MultiEditorProps } from '@music163/tango-ui';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { isValidCode } from '@music163/tango-core';
import { Modal } from 'antd';
const ideConfig = {
// disableFileOps: {
@@ -19,89 +21,116 @@ const ideConfig = {
// disableSetting: true,
};
export type CodeEditorProps = Partial<MultiEditorProps>;
export interface CodeEditorProps extends Partial<MultiEditorProps> {
/**
* 是否自动清楚未使用的导入
*/
autoRemoveUnusedImports?: boolean;
}
export const CodeEditor = observer((props: CodeEditorProps) => {
const editorRef = useRef(null);
const workspace = useWorkspace();
const designer = useDesigner();
const files = workspace.listFiles();
const activeFile = workspace.activeFile;
export const CodeEditor = observer(
({ autoRemoveUnusedImports = true, ...rest }: CodeEditorProps) => {
const editorRef = useRef(null);
const workspace = useWorkspace();
const designer = useDesigner();
const files = workspace.listFiles();
const activeFile = workspace.activeFile;
let loc: any; // 记录视图代码的选中位置
const selectNode = workspace.selectSource.firstNode;
if (selectNode && activeFile === workspace.activeViewFile) {
loc = selectNode.loc;
}
let loc: any; // 记录视图代码的选中位置
const selectNode = workspace.selectSource.firstNode;
if (selectNode && activeFile === workspace.activeViewFile) {
loc = selectNode.loc;
}
useEffect(() => {
editorRef.current?.refresh(files, activeFile, loc);
}, [files, activeFile, loc]);
useEffect(() => {
editorRef.current?.refresh(files, activeFile, loc);
}, [files, activeFile, loc]);
const fileSave = useCallback(
(path: string, value: string) => {
workspace.updateFile(path, value, false);
},
[workspace],
);
const handleFileChange = useCallback(
(type: string, info: any) => {
switch (type) {
case 'addFile':
workspace.addFile(info.path, info.value);
break;
case 'deleteFile':
case 'deleteFolder':
workspace.removeFile(info.path);
break;
case 'renameFile':
workspace.renameFile(info.path, info.newpath);
workspace.setActiveFile(info.newpath);
break;
case 'renameFolder':
workspace.renameFolder(info.path, info.newpath);
break;
case 'addFolder':
default:
break;
}
},
[workspace],
);
const handlePathChange = useCallback(
(path: string) => {
workspace.setActiveFile(path);
},
[workspace],
);
const borderStyle =
designer.activeView === 'dual'
? {
borderLeft: 'solid 1px var(--tango-colors-line2)',
borderRight: 'solid 1px var(--tango-colors-line2)',
const fileSave = useCallback(
(path: string, value: string) => {
if (!isJsFile(path)) {
// 非 js 文件直接保存
workspace.updateFile(path, value, autoRemoveUnusedImports);
return;
}
: {};
return (
<Box display="flex" flexDirection="row" height="100%" bg="white" {...borderStyle}>
<MultiEditor
ref={editorRef}
options={{
fontSize: 14,
automaticLayout: true,
}}
ideConfig={ideConfig}
onFileSave={fileSave}
onPathChange={handlePathChange}
onFileChange={handleFileChange}
defaultPath={activeFile}
defaultTheme="GithubLightDefault"
defaultFiles={files}
{...props}
/>
</Box>
);
});
// js 文件需要先检查语法,只有语法正确才会保存
if (isValidCode(value)) {
workspace.updateFile(path, value, autoRemoveUnusedImports);
} else {
Modal.confirm({
title: '检测到代码中存在语法错误,暂时无法将代码同步给设计器,是否回退到安全代码?',
onOk: () => {
editorRef.current?.refresh(files, activeFile);
},
onCancel: () => {},
});
}
},
[workspace, autoRemoveUnusedImports, activeFile, files],
);
const handleFileChange = useCallback(
(type: string, info: any) => {
switch (type) {
case 'addFile':
workspace.addFile(info.path, info.value);
break;
case 'deleteFile':
case 'deleteFolder':
workspace.removeFile(info.path);
break;
case 'renameFile':
workspace.renameFile(info.path, info.newpath);
workspace.setActiveFile(info.newpath);
break;
case 'renameFolder':
workspace.renameFolder(info.path, info.newpath);
break;
case 'addFolder':
default:
break;
}
},
[workspace],
);
const handlePathChange = useCallback(
(path: string) => {
workspace.setActiveFile(path);
},
[workspace],
);
const borderStyle =
designer.activeView === 'dual'
? {
borderLeft: 'solid 1px var(--tango-colors-line2)',
}
: {};
return (
<Box display="flex" flexDirection="row" height="100%" bg="white" {...borderStyle}>
<MultiEditor
ref={editorRef}
options={{
fontSize: 14,
automaticLayout: true,
}}
ideConfig={ideConfig}
onFileSave={fileSave}
onPathChange={handlePathChange}
onFileChange={handleFileChange}
defaultPath={activeFile}
defaultTheme="GithubLightDefault"
defaultFiles={files}
{...rest}
/>
</Box>
);
},
);
function isJsFile(path: string) {
return /.jsx?$/.test(path);
}
+2 -2
View File
@@ -1,7 +1,7 @@
import React, { useImperativeHandle, ForwardedRef, useRef, useState, useEffect } from 'react';
import { Box } from 'coral-system';
import { CodeSandbox, CodeSandboxProps } from '@music163/tango-sandbox';
import { Dict, getValue, isFunction, logger, pick, setValue } from '@music163/tango-helpers';
import { getValue, isFunction, logger, pick, setValue } from '@music163/tango-helpers';
import { observer, useWorkspace, useDesigner } from '@music163/tango-context';
import { Simulator, Viewport } from '../simulator';
import { useSandboxQuery } from '../context';
@@ -88,7 +88,7 @@ function useSandbox({
onViewChange: (data) => onViewChange && onViewChange(data, getSandboxConfig()),
});
let files = Array.from(workspace.files.keys()).reduce<Dict>((prev, filename) => {
let files = Array.from(workspace.files.keys()).reduce((prev, filename) => {
let code = workspace.getFile(filename).code;
if (filename === '/tango.config.json') {
code = mergeTangoConfigJson(code, { isPreview, injectScript, formatter: configFormatter });
@@ -4,7 +4,7 @@ import { AutoComplete } from 'antd';
import { ActionSelect } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { Dict, wrapCode } from '@music163/tango-helpers';
import { wrapCode } from '@music163/tango-helpers';
import { ExpressionPopover } from './expression-setter';
import { value2code } from '@music163/tango-core';
@@ -141,7 +141,7 @@ export function EventSetter(props: EventSetterProps) {
);
}
const handlerMap: Dict = {
const handlerMap = {
[EventAction.OpenModal]: 'openModal',
[EventAction.CloseModal]: 'closeModal',
[EventAction.NavigateTo]: 'navigateTo',
@@ -4,15 +4,7 @@ import { Dropdown, Button } from 'antd';
import { isValidExpressionCode } from '@music163/tango-core';
import { getValue, IVariableTreeNode, noop } from '@music163/tango-helpers';
import { CloseCircleFilled, MenuOutlined } from '@ant-design/icons';
import {
Panel,
InputCode,
Action,
DragPanel,
PopOutOutlined,
InputCodeProps,
InputStyleCode,
} from '@music163/tango-ui';
import { Panel, InputCode, Action, DragPanel, PopOutOutlined } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { VariableTree } from '../components';
@@ -51,10 +43,6 @@ export interface ExpressionSetterProps extends FormItemComponentProps<string> {
autoCompleteOptions?: string[];
allowClear?: boolean;
showOptionsDropDown?: boolean;
/**
* 表达式类型指定为 css object
*/
expressionType?: 'cssObject';
}
export function ExpressionSetter(props: ExpressionSetterProps) {
@@ -63,13 +51,12 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
modalTitle,
modalTip,
autoCompleteOptions,
placeholder = '在这里输入代码',
placeholder = '在这里输入JS代码',
value: valueProp,
status,
allowClear = true,
newStoreTemplate,
showOptionsDropDown = true,
expressionType,
} = props;
// const codeValue = getCodeOfWrappedCode(valueProp);
const [inputValue, setInputValue] = useState(valueProp);
@@ -92,12 +79,10 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
const sandbox = useSandboxQuery();
const evaluateContext = sandbox.window;
const InputCodeComponent = expressionType === 'cssObject' ? InputStyleCode : InputCode;
return (
<Box className="ExpressionSetter">
{/* 同时支持下拉框展示 */}
<InputCodeComponent
<InputCode
placeholder={placeholder}
suffix={
<Box css={suffixStyle}>
@@ -136,7 +121,6 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
autoCompleteOptions={autoCompleteOptions}
newStoreTemplate={newStoreTemplate}
value={inputValue}
expressionType={expressionType}
onOk={(value) => {
change(value);
}}
@@ -157,7 +141,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
);
}
export interface ExpressionPopoverProps extends InputCodeProps {
export interface ExpressionPopoverProps {
title?: string;
subTitle?: string;
placeholder?: string;
@@ -170,10 +154,6 @@ export interface ExpressionPopoverProps extends InputCodeProps {
* 新建 store 的模板代码
*/
newStoreTemplate?: string;
/**
* 表达式类型指定为 css object
*/
expressionType?: 'cssObject';
children?: React.ReactNode;
}
@@ -188,12 +168,10 @@ export function ExpressionPopover({
autoCompleteOptions,
newStoreTemplate = CODE_TEMPLATES.newStoreTemplate,
children,
expressionType,
}: ExpressionPopoverProps) {
const [exp, setExp] = useState(value ?? defaultValue);
const [error, setError] = useState('');
const workspace = useWorkspace();
const InputCodeComponent = expressionType === 'cssObject' ? InputStyleCode : InputCode;
const selectNodePath = workspace.selectSource.selected[0]?.codeId;
@@ -204,7 +182,7 @@ export function ExpressionPopover({
}));
const sandbox = useSandboxQuery();
const evaluateContext: any = sandbox.window;
const evaluateContext = sandbox.window;
const handleExpInputChange = (val: string) => {
setExp(val?.trim());
@@ -229,7 +207,7 @@ export function ExpressionPopover({
body={
<Box display="flex" flexDirection="column">
<Box p="m">
<InputCodeComponent
<InputCode
shape="inset"
minHeight="56px"
maxHeight="160px"
@@ -275,7 +253,7 @@ export function ExpressionPopover({
height="100%"
showViewButton
dataSource={dataSource || expressionVariables}
appContext={evaluateContext['tango']}
appContext={sandbox?.window['tango']}
getStoreNames={() => Object.keys(workspace.storeModules)}
serviceModules={serviceModules}
getServiceData={(serviceKey) => {
@@ -3,11 +3,10 @@ import { css, Box } from 'coral-system';
import { Form, Input, Button, Popover } from 'antd';
import { DeleteOutlined, EditOutlined, HolderOutlined } from '@ant-design/icons';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { Dict } from '@music163/tango-helpers';
import { DragBox } from '../components';
function copyWithoutUndefined(source: Dict) {
const ret: Dict = {};
function copyWithoutUndefined(source: Record<string, any>) {
const ret = {};
for (const key in source) {
if (source[key] !== undefined) {
ret[key] = source[key];
@@ -60,7 +60,7 @@ export function ModelSetter({
}: FormItemComponentProps) {
const [inputValue, setInputValue] = useState(value);
const { modelVariables } = useWorkspaceData();
const evaluateContext: any = useSandboxQuery().window || {};
const evaluateContext = useSandboxQuery().window || {};
const workspace = useWorkspace();
const definedVariables = useMemo(() => {
const list: string[] = [];
@@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { ActionSelect, InputCode } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { Box } from 'coral-system';
import { Dict } from '@music163/tango-helpers';
interface IRenderOption {
label: string;
@@ -38,7 +37,7 @@ export function RenderSetter({
}, [value]);
const optionsMap = useMemo(() => {
return options.reduce<Dict>((prev, cur) => {
return options.reduce((prev, cur) => {
prev[cur.value] = cur;
return prev;
}, {});
@@ -1,17 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Box } from 'coral-system';
import { Input, Radio } from 'antd';
import { Dict, isValidUrl } from '@music163/tango-helpers';
import { isValidUrl } from '@music163/tango-helpers';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspaceData } from '@music163/tango-context';
import { PickerSetter } from './picker-setter';
const routerModeMap: Dict = {
link: '链接',
router: '路由',
both: '',
};
export function RouterSetter(props: FormItemComponentProps) {
const [type, setType] = useState<'input' | 'select'>(() => {
return isValidUrl(props.value) ? 'input' : 'select';
@@ -43,7 +37,7 @@ export function RouterSetter(props: FormItemComponentProps) {
)}
{type === 'input' && (
<Input
placeholder={`请输入任意${routerModeMap[displayType]}地址`}
placeholder={`请输入任意${{ link: '链接', router: '路由', both: '' }[displayType]}地址`}
{...props}
value={input}
onChange={(e) => setInput(e.target.value)}
+2 -2
View File
@@ -2,7 +2,7 @@ import React from 'react';
import { Box } from 'coral-system';
import { SettingForm, FormModel, SettingFormProps } from '@music163/tango-setting-form';
import { Panel } from '@music163/tango-ui';
import { clone, Dict, parseDndId } from '@music163/tango-helpers';
import { clone, parseDndId } from '@music163/tango-helpers';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { registerBuiltinSetters } from './setters';
@@ -34,7 +34,7 @@ export const SettingPanel = observer((props: SettingPanelProps) => {
}
const getSettingValue = (attributes: Record<string, string>) => {
const ret: Dict = {};
const ret = {};
const keys = Object.keys(attributes);
for (const key of keys) {
if (/^data-/.test(key)) {
@@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react';
import { Box, Grid, Text } from 'coral-system';
import styled, { css } from 'styled-components';
import {
Dict,
IComponentPrototype,
MenuDataType,
MenuValueType,
@@ -67,7 +66,7 @@ export interface ComponentsPanelProps {
layout?: 'grid' | 'line';
}
const localeMap: Dict = {
const localeMap = {
common: '基础组件',
atom: '原子组件',
snippet: '组合',
@@ -125,7 +124,7 @@ export const ComponentsPanel = observer(
const tabs = Object.keys(menuData).map((key) => ({
key,
label: localeMap[key],
children: <MaterialList data={(menuData as any)[key]} />,
children: <MaterialList data={menuData[key]} />,
}));
if (showBizComps) {
@@ -24,7 +24,7 @@ const DataSourceView = observer(() => {
<Box className="ServiceFunctionList" p="m">
<VariableTree
dataSource={serviceVariables}
appContext={(sandbox?.window as any)['tango']}
appContext={sandbox?.window['tango']}
serviceModules={serviceModules}
onRemoveService={(variableKey) => {
workspace.removeServiceFunction(variableKey);
@@ -4,7 +4,6 @@ import { ConfigGroup, ConfigItem } from '@music163/tango-ui';
import { Box, css } from 'coral-system';
import { Input, Switch, Form, Button, message, FormProps } from 'antd';
import { CloseCircleOutlined, CloseOutlined } from '@ant-design/icons';
import { Dict } from '@music163/tango-helpers';
export default observer(() => {
const workspace = useWorkspace();
@@ -64,7 +63,7 @@ function ProxyManage({
initialValues: initialValuesProp = {},
onSave,
}: {
initialValues?: Dict;
initialValues?: object;
onSave?: (values: any) => void;
}) {
const initialValues = useMemo(() => {
@@ -85,7 +84,7 @@ function ProxyManage({
{...proxyManageFormStyle}
initialValues={initialValues}
onFinish={(values) => {
const proxy = values.proxy.reduce((prev: any, { path, ...rest }: any) => {
const proxy = values.proxy.reduce((prev: object, { path, ...rest }: any) => {
prev[path] = rest;
return prev;
}, {});
@@ -1,12 +1,12 @@
import React, { useEffect, useReducer } from 'react';
import { observer } from '@music163/tango-context';
import { Dict, isFunction, isObject, pick } from '@music163/tango-helpers';
import { isFunction, isObject, pick } from '@music163/tango-helpers';
import { CollapsePanel, IconButton, JsonView } from '@music163/tango-ui';
import { ReloadOutlined } from '@ant-design/icons';
import { useSandboxQuery } from '../../context';
function processTango(obj: Dict = {}, index = 0) {
const ret: Dict = {};
function processTango(obj: any = {}, index = 0) {
const ret = {};
Object.keys(obj).forEach((key) => {
const val = obj[key];
if (index > 2) {
@@ -25,7 +25,7 @@ function processTango(obj: Dict = {}, index = 0) {
export const StateTree = observer(() => {
const sandboxQuery = useSandboxQuery();
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const tangoContext = pick((sandboxQuery.window as any)['tango'] || {}, [
const tangoContext = pick(sandboxQuery.window['tango'] || {}, [
'stores',
'pageStore',
'services',
@@ -19,7 +19,7 @@ const resizeHandleStyle = (axis: ResizableBoxProps['axis'], barStyle: HTMLCoralP
cursor: ${axis === 'x' ? 'col-resize' : 'row-resize'};
${barStyle &&
Object.keys(barStyle)
.map((key) => `${key}: ${(barStyle as any)[key]};`)
.map((key) => `${key}: ${barStyle[key]};`)
.join('')}
&:hover,
&:active {
+18 -28
View File
@@ -3,7 +3,7 @@ import cx from 'classnames';
import { Box, Text, css, HTMLCoralProps } from 'coral-system';
import { Badge } from 'antd';
import { ReactComponentProps } from '@music163/tango-helpers';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { observer, useDesigner } from '@music163/tango-context';
import { ResizableBox } from './resizable-box';
import { getWidget } from '../widgets';
@@ -19,22 +19,15 @@ const sidebarStyle = css`
list-style: none;
user-select: none;
transition: all 0.3s ease;
&.disabled {
pointer-events: none;
}
}
.SidebarPanelBarListItem {
color: var(--tango-colors-text2);
&.active {
color: var(--tango-colors-brand);
background-color: var(--tango-colors-custom-sidebarItemActiveBg, --tango-colors-fill2);
background-color: var(--tango-colors-custom-sidebarItemActiveBg);
}
&:hover {
background-color: var(--tango-colors-custom-sidebarItemHoverBg, --tango-colors-fill2);
background-color: var(--tango-colors-custom-sidebarItemHoverBg);
}
}
`;
@@ -83,7 +76,6 @@ export interface SidebarPanelItemProps
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 266, footer, children }: SidebarProps) {
const designer = useDesigner();
const workspace = useWorkspace();
const items = useMemo(() => {
const ret: Record<string, SidebarPanelItemProps> = {};
@@ -100,23 +92,10 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 266, footer, childre
return ret;
}, [children]);
if (designer.activeView !== 'design') {
return null;
}
return (
<Box display="flex" flexShrink={0} css={sidebarStyle} className="SidebarPanel">
<Box className="SidebarPanelBar" display="flex" flexDirection="column" position="relative">
<Box
as="ul"
flex="0"
p="0"
m="0"
textAlign="center"
className={cx('SidebarPanelBarList', {
disabled: workspace.fileErrors.length > 0,
})}
>
<Box as="ul" flex="0" p="0" m="0" textAlign="center" className="SidebarPanelBarList">
{Object.keys(items).map((key) => {
const item = items[key];
const isActive = key === designer.activeSidebarPanel;
@@ -133,6 +112,7 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 266, footer, childre
icon={item.icon}
label={item.label}
showBadge={item.showBadge}
isActive={isActive}
/>
</li>
);
@@ -203,6 +183,10 @@ BaseSidebarPanel.Item = function ({
export const Sidebar = observer(BaseSidebarPanel);
interface SidebarPanelBarItemProps {
/**
* 是否选中
*/
isActive?: boolean;
/**
* 侧边栏图标
*/
@@ -228,9 +212,15 @@ interface SidebarPanelBarItemProps {
};
}
function SidebarPanelBarItem({ icon: iconProp, label, showBadge }: SidebarPanelBarItemProps) {
function SidebarPanelBarItem({
isActive,
icon: iconProp,
label,
showBadge,
}: SidebarPanelBarItemProps) {
const color = isActive ? 'brand' : 'text.body';
let icon = (
<Text fontSize="24px" lineHeight={1}>
<Text fontSize="24px" lineHeight={1} color={color}>
{iconProp}
</Text>
);
@@ -251,7 +241,7 @@ function SidebarPanelBarItem({ icon: iconProp, label, showBadge }: SidebarPanelB
py="12px"
>
{icon}
<Text fontSize="12px" mt="s">
<Text fontSize="12px" mt="s" color={color}>
{label}
</Text>
</Box>
+12 -35
View File
@@ -25,45 +25,22 @@ function DesktopSimulator({ children }: HTMLCoralProps<'div'>) {
}
const mobileStyle = css`
--device-viewport-width: 360px;
--device-viewport-height: 640px;
position: relative;
margin: auto;
border: 16px black solid;
border-top-width: 60px;
border-bottom-width: 60px;
border-radius: 36px;
&::before {
content: '';
display: block;
width: 60px;
height: 5px;
position: absolute;
top: -30px;
left: 50%;
transform: translate(-50%, -50%);
background: #333;
border-radius: 10px;
}
&::after {
content: '';
display: block;
width: 35px;
height: 35px;
position: absolute;
left: 50%;
bottom: -65px;
transform: translate(-50%, -50%);
background: #333;
border-radius: 50%;
}
--device-viewport-width: 375px;
--device-viewport-height: 812px;
--device-frame-width: 13px;
background-image: url(https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/21897838692/b1c5/2665/56db/10558c1bb2030d55ceabbcc74431de99.png);
background-repeat: no-repeat;
background-size: 100% 100%;
box-shadow: 0 2px 8px rgb(2 8 20 / 10%), 0 8px 16px rgb(2 8 20 / 8%);
border-radius: 62px;
overflow: hidden;
.MobileSimulatorDeviceFrame {
border-radius: 50px;
width: var(--device-viewport-width);
height: var(--device-viewport-height);
background: white;
margin: var(--device-frame-width);
overflow: hidden;
}
`;
+2 -3
View File
@@ -5,12 +5,11 @@ import { observer, useWorkspace } from '@music163/tango-context';
export const HistoryTool = observer(() => {
const workspace = useWorkspace();
const disabled = !workspace.isValid;
return (
<Box display="flex" columnGap="m">
<ToggleButton
shape="ghost"
disabled={disabled || !workspace.history.couldBack || workspace.history.index === 0}
disabled={!workspace.history.couldBack || workspace.history.index === 0}
tooltip="撤销"
onClick={() => {
workspace.history.back();
@@ -21,7 +20,7 @@ export const HistoryTool = observer(() => {
</ToggleButton>
<ToggleButton
shape="ghost"
disabled={disabled || !workspace.history.couldForward}
disabled={!workspace.history.couldForward}
tooltip="重做"
onClick={() => {
workspace.history.forward();
+16 -3
View File
@@ -1,5 +1,6 @@
import React from 'react';
import React, { useCallback } from 'react';
import { Group } from 'coral-system';
import { Modal } from 'antd';
import { ToggleButton, CodeOutlined, DualOutlined } from '@music163/tango-ui';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { BorderOutlined } from '@ant-design/icons';
@@ -8,13 +9,23 @@ export const ModeSwitchTool = observer(() => {
const workspace = useWorkspace();
const designer = useDesigner();
const activeFileCheck = useCallback(() => {
if (workspace.activeFile !== workspace.activeViewFile) {
Modal.confirm({
title: '当前打开的文件与视图不匹配,是否切换到当前视图对应的文件?',
onOk: () => {
workspace.setActiveFile(workspace.activeViewFile);
},
});
}
}, [workspace]);
return (
<Group attached whiteSpace="nowrap">
<Group attached>
<ToggleButton
shape="ghost"
selected={designer.activeView === 'design'}
onClick={() => {
workspace.syncFiles(); // 保证 ast 与 code 是同步的
designer.setActiveView('design');
}}
tooltip="设计视图"
@@ -27,6 +38,7 @@ export const ModeSwitchTool = observer(() => {
onClick={() => {
designer.setActiveView('code'); // 切换到源码视图
designer.setActiveSidebarPanel(''); // 关闭左侧面板
activeFileCheck();
}}
tooltip="源码视图"
>
@@ -38,6 +50,7 @@ export const ModeSwitchTool = observer(() => {
onClick={() => {
designer.setActiveView('dual'); // 切换到双屏视图
designer.setActiveSidebarPanel(''); // 关闭左侧面板
activeFileCheck();
}}
tooltip="双屏视图"
>
+1 -3
View File
@@ -1,15 +1,13 @@
import React from 'react';
import { ToggleButton } from '@music163/tango-ui';
import { EyeOutlined } from '@ant-design/icons';
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
import { observer, useDesigner } from '@music163/tango-context';
export const PreviewTool = observer(() => {
const designer = useDesigner();
const workspace = useWorkspace();
return (
<ToggleButton
disabled={!workspace.isValid}
shape="ghost"
selected={designer.isPreview}
onClick={() => {
@@ -47,14 +47,11 @@ function PageSelect({ value, onSelect, onRemove, onUpdate, onCopy, options }: Pa
setVisible(false);
};
const pathname = value?.split?.('?')[0];
const current = optionMap[pathname];
const current = optionMap[value];
return (
<ToggleButton
shape="ghost"
minWidth={100}
maxWidth={200}
textAlign="left"
dropdownProps={{
open: visible,
onOpenChange: setVisible,
+16 -46
View File
@@ -1,8 +1,6 @@
import React, { useMemo } from 'react';
import { Box, Group } from 'coral-system';
import { ReactComponentProps } from '@music163/tango-helpers';
import { DesignerViewType } from '@music163/tango-core';
import { observer, useDesigner } from '@music163/tango-context';
import { getWidget } from '../widgets';
export interface ToolbarProps {
@@ -17,23 +15,25 @@ export function Toolbar({ children }: ToolbarProps) {
let prevPlacement: string;
React.Children.forEach(children, (child: React.ReactElement, index) => {
const childProps = child.props;
let fallbackChildren;
let fallbackNode;
if (child.key) {
const Widget = getWidget(['toolbar', child.key].join('.'));
if (Widget) {
fallbackChildren = <Widget {...childProps.widgetProps} />;
fallbackNode = React.createElement(Widget, child.props.widgetProps);
}
}
const key = child.key || index;
const node = React.cloneElement(child, {
key,
'data-key': key,
children: childProps.children || fallbackChildren,
});
const placement = childProps.placement || prevPlacement || 'center';
let node = child.props.children ?? fallbackNode ?? null;
if (!node) {
node = child; // separator
}
const key = child.key || index;
node = (
<div key={key} className="ToolbarPanelItem" data-key={key}>
{node}
</div>
);
const placement = child.props.placement || prevPlacement || 'center';
switch (placement) {
case 'left':
left.push(node);
@@ -68,47 +68,17 @@ export function Toolbar({ children }: ToolbarProps) {
}
export interface ToolbarItemProps extends ReactComponentProps {
/**
* 类型
*/
type?: 'divider';
placement?: 'left' | 'center' | 'right';
children?: React.ReactElement;
/**
* 如果 key 匹配到内置组件的话,传递给子节点的属性
*/
widgetProps?: object;
/**
* 工具箱展示的视图
*/
activeViews?: DesignerViewType[];
}
const ToolbarItem = observer(
({
activeViews = ['code', 'design', 'dual'],
type,
placement,
widgetProps,
children,
...rest
}: ToolbarItemProps) => {
const designer = useDesigner();
if (!activeViews.includes(designer.activeView)) {
return null;
}
if (type === 'divider') {
return <Separator />;
}
return (
<div className="ToolbarPanelItem" {...rest}>
{children}
</div>
);
},
);
function ToolbarItem({ placement, widgetProps, children }: ToolbarItemProps) {
return <div>{children}</div>;
}
function Separator() {
return <Box className="Separator" width={1} height={16} bg="colors.custom.toolbarDividerColor" />;
+1 -1
View File
@@ -30,7 +30,7 @@ import {
const widgets = {};
export function registerWidget(key: string, component: React.ComponentType<any>) {
if (!/^(toolbar|sidebar|selectionMenu|contextMenu)\.[a-zA-Z]+$/.test(key)) {
if (!/^(toolbar|sidebar|selectionMenu|contextMenu)\.[a-zA-z]+$/.test(key)) {
throw new Error(
`Invalid widget key(${key}), should start with toolbar, sidebar, contextMenu or selectionMenu`,
);
+7 -9
View File
@@ -1,37 +1,35 @@
import React from 'react';
import { Box, HTMLCoralProps } from 'coral-system';
import { Box } from 'coral-system';
import { observer, useDesigner } from '@music163/tango-context';
import { DesignerViewType } from '@music163/tango-core';
import cx from 'classnames';
import { ComponentsPopover, FileErrorsOverlay } from './components';
import { ComponentsPopover } from './components';
export interface WorkspaceViewProps extends HTMLCoralProps<'div'> {
export interface WorkspaceViewProps {
/**
* 视图面板模式,对应 Workspace 的模式
*/
mode?: DesignerViewType;
children: React.ReactNode;
}
export const WorkspaceView = observer((props: WorkspaceViewProps) => {
const { mode = 'design', children, className, ...rest } = props;
const { mode = 'design', children } = props;
const designer = useDesigner();
const display = mode !== designer.activeView ? 'none' : 'block';
// 移动端模式小屏幕适配,可能会溢出屏幕
// 云音乐移动端模式小屏幕适配,可能会溢出屏幕
const overflow =
designer.simulator.name === 'phone' ? 'auto' : designer.isPreview ? 'auto' : 'hidden';
return (
<Box
className={cx('ViewPanel', mode, className)}
className={`ViewPanel ${mode}`}
display={designer.activeView === 'dual' ? 'block' : display}
flex="1"
overflow={overflow}
position="relative"
{...rest}
>
{children}
{/* 添加组件弹层 */}
{display === 'block' && <ComponentsPopover type="inner" isControlled />}
{designer.activeView === 'design' && <FileErrorsOverlay />}
</Box>
);
});
-12
View File
@@ -3,18 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.2](https://github.com/netease/tango/compare/@music163/tango-helpers@1.2.1...@music163/tango-helpers@1.2.2) (2024-08-05)
### Bug Fixes
- parse template code & update maxRow of TextAreaSetter ([#192](https://github.com/netease/tango/issues/192)) ([a1eea02](https://github.com/netease/tango/commit/a1eea02f9409db73f042c74d7903ad84258f25c2))
## [1.2.1](https://github.com/netease/tango/compare/@music163/tango-helpers@1.2.0...@music163/tango-helpers@1.2.1) (2024-08-01)
### Bug Fixes
- update types ([67d602a](https://github.com/netease/tango/commit/67d602a3ec6b7f74156bb78fda6f3b4bc2676e55))
# [1.2.0](https://github.com/netease/tango/compare/@music163/tango-helpers@1.1.1...@music163/tango-helpers@1.2.0) (2024-06-03)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-helpers",
"version": "1.2.2",
"version": "1.2.0",
"description": "Shared types, helpers, and hooks of tango-apps",
"keywords": [
"shared",
+2 -2
View File
@@ -43,7 +43,7 @@ export function filterTreeData<T = unknown>(
childrenProp = 'children',
onlyLeaf = false,
) {
const reducer = (result: T[], current: any) => {
const reducer = (result: T[], current: T) => {
if (current[childrenProp]) {
const newChildren = current[childrenProp].reduce(reducer, []);
if (newChildren.length) {
@@ -70,7 +70,7 @@ export function mapTreeData<T = unknown>(
mapper: (node: T) => any,
childrenProp = 'children',
) {
return treeData.map((node: any) => {
return treeData.map((node) => {
const newNode = mapper(node);
if (node[childrenProp]) {
newNode[childrenProp] = mapTreeData(node[childrenProp], mapper, childrenProp);
+1 -1
View File
@@ -32,7 +32,7 @@ export function isValidFunctionCode(str: string) {
}
}
const templatePattern = /^{{([\s\S]+)}}$/;
const templatePattern = /^{{(.+)}}$/s;
/**
* 判断给定代码是否被双花括号包裹
+2 -3
View File
@@ -1,6 +1,5 @@
import get from 'lodash.get';
import set from 'lodash.set';
import { Dict } from '../types';
/**
* 合并 target 和 source 对象,source 对象的优先级更高,如存在重名,覆盖 target 中的 key
@@ -46,7 +45,7 @@ export function clone(obj: any, withUndefined = true) {
return obj;
}
const target: Dict = {};
const target = {};
for (const key in obj) {
if (withUndefined) {
target[key] = obj[key];
@@ -66,7 +65,7 @@ export function omit(obj: any, keys: string[]) {
}
export function pick(obj: any, keys: string[]) {
const target: Dict = {};
const target = {};
for (const key of keys) {
target[key] = obj[key];
}
+1 -1
View File
@@ -57,7 +57,7 @@ export function withDnd(options: IWithDndOptions<any>) {
const override = options.overrideProps || {};
const renderFooter = options.renderFooter || renderEmpty;
const Component = forwardRef<unknown, P & DraggableComponentProps>((props: any, refProp) => {
const Component = forwardRef<unknown, P & DraggableComponentProps>((props, refProp) => {
const dndProps = {
[SLOT.id]: props.tid, // id 作为唯一标记
[SLOT.dnd]: props[SLOT.dnd], // dnd 作为追踪标记
+2 -3
View File
@@ -1,14 +1,13 @@
import { warning } from '../helpers';
import { Dict } from '../types';
type ListStoreOptionsType<T extends Dict = Dict> = {
type ListStoreOptionsType<T = object> = {
data: T[];
keyProp?: string;
labelProp?: string;
childrenProp?: string;
};
export class ListStore<T extends Dict = Dict> {
export class ListStore<T = object> {
private nodeMap: Map<string, T>;
private keyProp: string;
private childrenProp: string;
@@ -1,13 +0,0 @@
import { isWrappedCode } from '../src/helpers';
describe('helpers/codeHelper', () => {
it('isWrappedCode', () => {
expect(isWrappedCode('{{[]}}')).toBeTruthy(); // true
expect(isWrappedCode('{{{}}}')).toBeTruthy(); // true
expect(isWrappedCode('{{this.foo}}')).toBeTruthy(); // true
expect(isWrappedCode('{{123}}')).toBeTruthy(); // true
expect(isWrappedCode('{{() => {}}}')).toBeTruthy(); // true
expect(isWrappedCode('{{<Button>button</Button>}}')).toBeTruthy(); // true
expect(isWrappedCode('{{<>\n<Button>hello</Button>\n</>}}')).toBeTruthy(); // true
});
});
-16
View File
@@ -3,22 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.11](https://github.com/netease/tango/compare/@music163/tango-sandbox@1.0.10...@music163/tango-sandbox@1.0.11) (2024-08-06)
**Note:** Version bump only for package @music163/tango-sandbox
## [1.0.10](https://github.com/netease/tango/compare/@music163/tango-sandbox@1.0.9...@music163/tango-sandbox@1.0.10) (2024-08-05)
**Note:** Version bump only for package @music163/tango-sandbox
## [1.0.9](https://github.com/netease/tango/compare/@music163/tango-sandbox@1.0.8...@music163/tango-sandbox@1.0.9) (2024-08-01)
**Note:** Version bump only for package @music163/tango-sandbox
## [1.0.8](https://github.com/netease/tango/compare/@music163/tango-sandbox@1.0.7...@music163/tango-sandbox@1.0.8) (2024-07-12)
**Note:** Version bump only for package @music163/tango-sandbox
## [1.0.7](https://github.com/netease/tango/compare/@music163/tango-sandbox@1.0.6...@music163/tango-sandbox@1.0.7) (2024-06-20)
**Note:** Version bump only for package @music163/tango-sandbox
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-sandbox",
"version": "1.0.11",
"version": "1.0.7",
"description": "sandbox of tango apps",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -29,8 +29,8 @@
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/tango-core": "^1.4.2",
"@music163/tango-helpers": "^1.2.2",
"@music163/tango-core": "^1.3.2",
"@music163/tango-helpers": "^1.2.0",
"crypto-js": "^4.1.1",
"lodash.isequal": "4.5.0",
"react-frame-component": "^5.2.4"
-30
View File
@@ -3,36 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.13](https://github.com/netease/tango/compare/@music163/tango-setting-form@1.2.12...@music163/tango-setting-form@1.2.13) (2024-08-16)
### Bug Fixes
- CodeInput 支持 jsx css 代码补全 ([#198](https://github.com/netease/tango/issues/198)) ([6de5488](https://github.com/netease/tango/commit/6de54884b50d683c74d695a8c13184dc0ce8d3c4))
## [1.2.12](https://github.com/netease/tango/compare/@music163/tango-setting-form@1.2.11...@music163/tango-setting-form@1.2.12) (2024-08-09)
**Note:** Version bump only for package @music163/tango-setting-form
## [1.2.11](https://github.com/netease/tango/compare/@music163/tango-setting-form@1.2.10...@music163/tango-setting-form@1.2.11) (2024-08-06)
**Note:** Version bump only for package @music163/tango-setting-form
## [1.2.10](https://github.com/netease/tango/compare/@music163/tango-setting-form@1.2.9...@music163/tango-setting-form@1.2.10) (2024-08-05)
### Bug Fixes
- parse template code & update maxRow of TextAreaSetter ([#192](https://github.com/netease/tango/issues/192)) ([a1eea02](https://github.com/netease/tango/commit/a1eea02f9409db73f042c74d7903ad84258f25c2))
## [1.2.9](https://github.com/netease/tango/compare/@music163/tango-setting-form@1.2.8...@music163/tango-setting-form@1.2.9) (2024-08-01)
### Bug Fixes
- update types ([67d602a](https://github.com/netease/tango/commit/67d602a3ec6b7f74156bb78fda6f3b4bc2676e55))
## [1.2.8](https://github.com/netease/tango/compare/@music163/tango-setting-form@1.2.7...@music163/tango-setting-form@1.2.8) (2024-07-12)
**Note:** Version bump only for package @music163/tango-setting-form
## [1.2.7](https://github.com/netease/tango/compare/@music163/tango-setting-form@1.2.6...@music163/tango-setting-form@1.2.7) (2024-06-20)
### Bug Fixes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-setting-form",
"version": "1.2.13",
"version": "1.2.7",
"description": "setting form of tango-apps",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -28,9 +28,9 @@
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/tango-core": "^1.4.2",
"@music163/tango-helpers": "^1.2.2",
"@music163/tango-ui": "^1.4.3",
"@music163/tango-core": "^1.3.2",
"@music163/tango-helpers": "^1.2.0",
"@music163/tango-ui": "^1.3.2",
"antd": "^4.24.2",
"coral-system": "^1.0.5",
"mobx": "6.12.3",
+1 -1
View File
@@ -246,7 +246,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
const CodeSetter = REGISTERED_FORM_ITEM_MAP['codeSetter']?.config?.component;
const setterNode = isCodeSetter ? (
<CodeSetter {...expProps} {...baseComponentProps} {...setterProps} />
<CodeSetter {...expProps} {...baseComponentProps} />
) : (
renderSetter({
...expProps,
+1 -2
View File
@@ -7,7 +7,6 @@ import {
noop,
filterTreeData,
mapTreeData,
Dict,
} from '@music163/tango-helpers';
import { Action, Search, Tabs } from '@music163/tango-ui';
import { SettingFormItem, register } from './form-item';
@@ -25,7 +24,7 @@ function normalizeComponentProps(
props: IComponentPrototype['props'],
groupOptions: IFormTabsGroupOption[],
) {
const groups: Record<string, IComponentProp[]> = groupOptions.reduce<Dict>((prev, cur) => {
const groups: Record<string, IComponentProp[]> = groupOptions.reduce((prev, cur) => {
prev[cur.value] = [];
return prev;
}, {});
@@ -34,7 +34,7 @@ export function TextSetter({
const autoSize = {
minRows: 2,
maxRows: 6,
maxRows: 5,
};
export function TextAreaSetter({
-33
View File
@@ -3,39 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.3](https://github.com/netease/tango/compare/@music163/tango-ui@1.4.2...@music163/tango-ui@1.4.3) (2024-08-16)
### Bug Fixes
- CodeInput 支持 jsx css 代码补全 ([#198](https://github.com/netease/tango/issues/198)) ([6de5488](https://github.com/netease/tango/commit/6de54884b50d683c74d695a8c13184dc0ce8d3c4))
## [1.4.2](https://github.com/netease/tango/compare/@music163/tango-ui@1.4.1...@music163/tango-ui@1.4.2) (2024-08-09)
### Bug Fixes
- route matching without query string & toggle button wrapping ([#195](https://github.com/netease/tango/issues/195)) ([9394666](https://github.com/netease/tango/commit/939466648d40331800f15e886256a0789b7e7623))
- update mobile simulator ui ([#196](https://github.com/netease/tango/issues/196)) ([65149f1](https://github.com/netease/tango/commit/65149f1326eaf78e8a27222993c3cacc674cd378))
## [1.4.1](https://github.com/netease/tango/compare/@music163/tango-ui@1.4.0...@music163/tango-ui@1.4.1) (2024-08-05)
**Note:** Version bump only for package @music163/tango-ui
# [1.4.0](https://github.com/netease/tango/compare/@music163/tango-ui@1.3.3...@music163/tango-ui@1.4.0) (2024-08-01)
### Bug Fixes
- update types ([67d602a](https://github.com/netease/tango/commit/67d602a3ec6b7f74156bb78fda6f3b4bc2676e55))
### Features
- add isError and errorMessage to File & add isAstSynced to Module ([#190](https://github.com/netease/tango/issues/190)) ([8dd289c](https://github.com/netease/tango/commit/8dd289cd7daba628e54dc6b8929f22a1e2245160))
## [1.3.3](https://github.com/netease/tango/compare/@music163/tango-ui@1.3.2...@music163/tango-ui@1.3.3) (2024-07-12)
### Bug Fixes
- selection bug without prototype & update ToggleButton style ([#180](https://github.com/netease/tango/issues/180)) ([1d0fb4f](https://github.com/netease/tango/commit/1d0fb4f274a5b767962c54583ab5863329d5bcc5))
## [1.3.2](https://github.com/netease/tango/compare/@music163/tango-ui@1.3.1...@music163/tango-ui@1.3.2) (2024-06-20)
### Bug Fixes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-ui",
"version": "1.4.3",
"version": "1.3.2",
"description": "ui widgets of tango",
"keywords": [
"react",
@@ -38,7 +38,7 @@
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lint": "^6.7.1",
"@codemirror/search": "^6.5.6",
"@music163/tango-helpers": "^1.2.2",
"@music163/tango-helpers": "^1.2.0",
"@uiw/react-codemirror": "^4.22.0",
"antd": "^4.24.2",
"classnames": "^2.5.1",
+2 -2
View File
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { Menu, Space } from 'antd';
import { Box, css } from 'coral-system';
import { Dict, isApplePlatform } from '@music163/tango-helpers';
import { isApplePlatform } from '@music163/tango-helpers';
const contextActionStyle = css`
display: flex;
@@ -46,7 +46,7 @@ export function ContextAction({
if (!hotkey) {
return null;
}
const keyMap: Dict = isApplePlatform()
const keyMap = isApplePlatform()
? {
command: '⌘',
meta: '⌘',
+1 -1
View File
@@ -3,7 +3,7 @@ import { createIcon } from './create-icon';
const DualOutlinedSvg = () => (
<svg
viewBox="0 0 1024 1024"
viewBox="0 0 1025 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="1em"
@@ -8,7 +8,6 @@ const PackageOutlinedSvg = () => (
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
fill="currentColor"
>
<path d="M915.2 284.8c0-3.2 0-3.2-3.2-6.4 0-3.2-3.2-3.2-6.4-6.4-9.6-12.8-22.4-25.6-38.4-35.2l-297.6-169.6c-35.2-22.4-80-22.4-118.4 0L313.6 147.2c-6.4 3.2-12.8 6.4-16 12.8L153.6 240c-12.8 9.6-25.6 22.4-38.4 35.2 0 3.2-3.2 3.2-3.2 6.4s0 3.2-3.2 6.4c-6.4 16-12.8 35.2-12.8 54.4v342.4c0 41.6 22.4 80 57.6 102.4l297.6 169.6c12.8 6.4 28.8 12.8 41.6 16 3.2 3.2 9.6 3.2 16 3.2s9.6-3.2 16-3.2c16-3.2 28.8-6.4 41.6-16l297.6-169.6c35.2-22.4 57.6-60.8 57.6-102.4V342.4c6.4-19.2 0-38.4-6.4-57.6z m-428.8-160c9.6-3.2 16-6.4 25.6-6.4 9.6 0 19.2 3.2 25.6 6.4L832 291.2l-128 73.6-316.8-182.4 99.2-57.6z m25.6 352L192 291.2l128-73.6 316.8 182.4-124.8 76.8zM185.6 729.6c-16-9.6-25.6-28.8-25.6-48V345.6l320 185.6V896l-294.4-166.4z m652.8 0L544 896V531.2l320-185.6v336c0 19.2-9.6 38.4-25.6 48z" />
</svg>
-1
View File
@@ -13,7 +13,6 @@ export * from './menu';
export * from './panel';
export * from './toggle-button';
export * from './input-code';
export * from './input-style-code';
export * from './input-list';
export * from './search';
export * from './json-view';
+1 -1
View File
@@ -174,7 +174,7 @@ export function InputCode({
);
}
export function useInputCode({
function useInputCode({
shape,
status,
showLineNumbers,
-99
View File
@@ -1,99 +0,0 @@
import React from 'react';
import { Box } from 'coral-system';
import CodeMirror from '@uiw/react-codemirror';
import { javascript, javascriptLanguage, esLint } from '@codemirror/lang-javascript';
import { CompletionContext } from '@codemirror/autocomplete';
import { syntaxTree } from '@codemirror/language';
import { linter, lintGutter } from '@codemirror/lint';
import * as eslint from 'eslint-linter-browserify';
import { jsxCSSproperties, jsxCSSvalues } from './lang/css-object';
import type { Completion } from '@codemirror/autocomplete';
import { InputCodeProps, useInputCode } from './input-code';
const eslintConfig = {
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
env: {
browser: true,
},
rules: {},
};
function completeProperties(from: number, cssOptions: Completion[] | readonly Completion[]) {
return {
from,
options: cssOptions,
validFor: /^[\w$]*$/,
};
}
/**
*
* @param scope 预测 CSS 的补全上下文
* @param customOptions 自定义补全选项列表
* @returns
*/
function buildAutoComplete() {
/**
* @see context https://codemirror.net/docs/ref/#autocomplete.CompletionContext
*/
return function (context: CompletionContext) {
const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1);
if (nodeBefore.name === 'VariableName') {
// match css property name
return completeProperties(nodeBefore.from, jsxCSSproperties());
}
// match css property value
else if (nodeBefore.name === 'String') {
return completeProperties(nodeBefore.from + 1, jsxCSSvalues);
}
return null;
};
}
export type InputStyleCodeProps = Omit<
InputCodeProps,
'autoCompleteContext' | 'autoCompleteOptions'
>;
export function InputStyleCode({
shape = 'solid',
suffix,
status,
showLineNumbers,
showFoldGutter,
enableESLint = false,
...rest
}: InputStyleCodeProps) {
const extensions = [
javascript({ jsx: true }),
javascriptLanguage.data.of({
autocomplete: buildAutoComplete(),
}),
];
if (enableESLint) {
extensions.push(lintGutter(), linter(esLint(new eslint.Linter(), eslintConfig)));
}
const { rootStyle, codeSetup } = useInputCode({
shape,
status,
showLineNumbers,
showFoldGutter,
});
return (
<Box className="InputCode" display="flex" alignItems="center" overflow="hidden" {...rootStyle}>
<Box flex="1" overflow="auto">
<CodeMirror
extensions={extensions}
basicSetup={codeSetup}
placeholder={'//输入 css 代码'}
{...rest}
/>
</Box>
{suffix}
</Box>
);
}
-602
View File
@@ -1,602 +0,0 @@
/**
* @file: jsx css completion file
* @description: fork from https://github.com/codemirror/lang-css/blob/main/src/complete.ts
*/
import type { Completion } from '@codemirror/autocomplete';
function toCamelCase(input: string): string {
return input.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
let _properties: readonly Completion[] | null = null;
// inject global css properties
export function jsxCSSproperties() {
if (!_properties && typeof document === 'object' && document.body) {
const { style } = document.body;
const names = [];
const seen = new Set();
for (let prop in style)
if (prop !== 'cssText' && prop !== 'cssFloat') {
if (typeof style[prop] === 'string') {
if (/[A-Z]/.test(prop)) prop = prop.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);
if (!seen.has(prop)) {
names.push(prop);
seen.add(prop);
}
}
}
_properties = names.sort().map((name) => ({ label: toCamelCase(name), type: 'variable' }));
}
return _properties || [];
}
export const jsxCSSvalues = [
'above',
'absolute',
'activeborder',
'additive',
'activecaption',
'after-white-space',
'ahead',
'alias',
'all',
'all-scroll',
'alphabetic',
'alternate',
'always',
'antialiased',
'appworkspace',
'asterisks',
'attr',
'auto',
'auto-flow',
'avoid',
'avoid-column',
'avoid-page',
'avoid-region',
'axis-pan',
'background',
'backwards',
'baseline',
'below',
'bidi-override',
'blink',
'block',
'block-axis',
'bold',
'bolder',
'border',
'border-box',
'both',
'bottom',
'break',
'break-all',
'break-word',
'bullets',
'button',
'button-bevel',
'buttonface',
'buttonhighlight',
'buttonshadow',
'buttontext',
'calc',
'capitalize',
'caps-lock-indicator',
'caption',
'captiontext',
'caret',
'cell',
'center',
'checkbox',
'circle',
'cjk-decimal',
'clear',
'clip',
'close-quote',
'col-resize',
'collapse',
'color',
'color-burn',
'color-dodge',
'column',
'column-reverse',
'compact',
'condensed',
'contain',
'content',
'contents',
'content-box',
'context-menu',
'continuous',
'copy',
'counter',
'counters',
'cover',
'crop',
'cross',
'crosshair',
'currentcolor',
'cursive',
'cyclic',
'darken',
'dashed',
'decimal',
'decimal-leading-zero',
'default',
'default-button',
'dense',
'destination-atop',
'destination-in',
'destination-out',
'destination-over',
'difference',
'disc',
'discard',
'disclosure-closed',
'disclosure-open',
'document',
'dot-dash',
'dot-dot-dash',
'dotted',
'double',
'down',
'e-resize',
'ease',
'ease-in',
'ease-in-out',
'ease-out',
'element',
'ellipse',
'ellipsis',
'embed',
'end',
'ethiopic-abegede-gez',
'ethiopic-halehame-aa-er',
'ethiopic-halehame-gez',
'ew-resize',
'exclusion',
'expanded',
'extends',
'extra-condensed',
'extra-expanded',
'fantasy',
'fast',
'fill',
'fill-box',
'fixed',
'flat',
'flex',
'flex-end',
'flex-start',
'footnotes',
'forwards',
'from',
'geometricPrecision',
'graytext',
'grid',
'groove',
'hand',
'hard-light',
'help',
'hidden',
'hide',
'higher',
'highlight',
'highlighttext',
'horizontal',
'hsl',
'hsla',
'hue',
'icon',
'ignore',
'inactiveborder',
'inactivecaption',
'inactivecaptiontext',
'infinite',
'infobackground',
'infotext',
'inherit',
'initial',
'inline',
'inline-axis',
'inline-block',
'inline-flex',
'inline-grid',
'inline-table',
'inset',
'inside',
'intrinsic',
'invert',
'italic',
'justify',
'keep-all',
'landscape',
'large',
'larger',
'left',
'level',
'lighter',
'lighten',
'line-through',
'linear',
'linear-gradient',
'lines',
'list-item',
'listbox',
'listitem',
'local',
'logical',
'loud',
'lower',
'lower-hexadecimal',
'lower-latin',
'lower-norwegian',
'lowercase',
'ltr',
'luminosity',
'manipulation',
'match',
'matrix',
'matrix3d',
'medium',
'menu',
'menutext',
'message-box',
'middle',
'min-intrinsic',
'mix',
'monospace',
'move',
'multiple',
'multiple_mask_images',
'multiply',
'n-resize',
'narrower',
'ne-resize',
'nesw-resize',
'no-close-quote',
'no-drop',
'no-open-quote',
'no-repeat',
'none',
'normal',
'not-allowed',
'nowrap',
'ns-resize',
'numbers',
'numeric',
'nw-resize',
'nwse-resize',
'oblique',
'opacity',
'open-quote',
'optimizeLegibility',
'optimizeSpeed',
'outset',
'outside',
'outside-shape',
'overlay',
'overline',
'padding',
'padding-box',
'painted',
'page',
'paused',
'perspective',
'pinch-zoom',
'plus-darker',
'plus-lighter',
'pointer',
'polygon',
'portrait',
'pre',
'pre-line',
'pre-wrap',
'preserve-3d',
'progress',
'push-button',
'radial-gradient',
'radio',
'read-only',
'read-write',
'read-write-plaintext-only',
'rectangle',
'region',
'relative',
'repeat',
'repeating-linear-gradient',
'repeating-radial-gradient',
'repeat-x',
'repeat-y',
'reset',
'reverse',
'rgb',
'rgba',
'ridge',
'right',
'rotate',
'rotate3d',
'rotateX',
'rotateY',
'rotateZ',
'round',
'row',
'row-resize',
'row-reverse',
'rtl',
'run-in',
'running',
's-resize',
'sans-serif',
'saturation',
'scale',
'scale3d',
'scaleX',
'scaleY',
'scaleZ',
'screen',
'scroll',
'scrollbar',
'scroll-position',
'se-resize',
'self-start',
'self-end',
'semi-condensed',
'semi-expanded',
'separate',
'serif',
'show',
'single',
'skew',
'skewX',
'skewY',
'skip-white-space',
'slide',
'slider-horizontal',
'slider-vertical',
'sliderthumb-horizontal',
'sliderthumb-vertical',
'slow',
'small',
'small-caps',
'small-caption',
'smaller',
'soft-light',
'solid',
'source-atop',
'source-in',
'source-out',
'source-over',
'space',
'space-around',
'space-between',
'space-evenly',
'spell-out',
'square',
'start',
'static',
'status-bar',
'stretch',
'stroke',
'stroke-box',
'sub',
'subpixel-antialiased',
'svg_masks',
'super',
'sw-resize',
'symbolic',
'symbols',
'system-ui',
'table',
'table-caption',
'table-cell',
'table-column',
'table-column-group',
'table-footer-group',
'table-header-group',
'table-row',
'table-row-group',
'text',
'text-bottom',
'text-top',
'textarea',
'textfield',
'thick',
'thin',
'threeddarkshadow',
'threedface',
'threedhighlight',
'threedlightshadow',
'threedshadow',
'to',
'top',
'transform',
'translate',
'translate3d',
'translateX',
'translateY',
'translateZ',
'transparent',
'ultra-condensed',
'ultra-expanded',
'underline',
'unidirectional-pan',
'unset',
'up',
'upper-latin',
'uppercase',
'url',
'var',
'vertical',
'vertical-text',
'view-box',
'visible',
'visibleFill',
'visiblePainted',
'visibleStroke',
'visual',
'w-resize',
'wait',
'wave',
'wider',
'window',
'windowframe',
'windowtext',
'words',
'wrap',
'wrap-reverse',
'x-large',
'x-small',
'xor',
'xx-large',
'xx-small',
]
.map((name) => ({ label: name, type: 'text' }))
.concat(
[
'aliceblue',
'antiquewhite',
'aqua',
'aquamarine',
'azure',
'beige',
'bisque',
'black',
'blanchedalmond',
'blue',
'blueviolet',
'brown',
'burlywood',
'cadetblue',
'chartreuse',
'chocolate',
'coral',
'cornflowerblue',
'cornsilk',
'crimson',
'cyan',
'darkblue',
'darkcyan',
'darkgoldenrod',
'darkgray',
'darkgreen',
'darkkhaki',
'darkmagenta',
'darkolivegreen',
'darkorange',
'darkorchid',
'darkred',
'darksalmon',
'darkseagreen',
'darkslateblue',
'darkslategray',
'darkturquoise',
'darkviolet',
'deeppink',
'deepskyblue',
'dimgray',
'dodgerblue',
'firebrick',
'floralwhite',
'forestgreen',
'fuchsia',
'gainsboro',
'ghostwhite',
'gold',
'goldenrod',
'gray',
'grey',
'green',
'greenyellow',
'honeydew',
'hotpink',
'indianred',
'indigo',
'ivory',
'khaki',
'lavender',
'lavenderblush',
'lawngreen',
'lemonchiffon',
'lightblue',
'lightcoral',
'lightcyan',
'lightgoldenrodyellow',
'lightgray',
'lightgreen',
'lightpink',
'lightsalmon',
'lightseagreen',
'lightskyblue',
'lightslategray',
'lightsteelblue',
'lightyellow',
'lime',
'limegreen',
'linen',
'magenta',
'maroon',
'mediumaquamarine',
'mediumblue',
'mediumorchid',
'mediumpurple',
'mediumseagreen',
'mediumslateblue',
'mediumspringgreen',
'mediumturquoise',
'mediumvioletred',
'midnightblue',
'mintcream',
'mistyrose',
'moccasin',
'navajowhite',
'navy',
'oldlace',
'olive',
'olivedrab',
'orange',
'orangered',
'orchid',
'palegoldenrod',
'palegreen',
'paleturquoise',
'palevioletred',
'papayawhip',
'peachpuff',
'peru',
'pink',
'plum',
'powderblue',
'purple',
'rebeccapurple',
'red',
'rosybrown',
'royalblue',
'saddlebrown',
'salmon',
'sandybrown',
'seagreen',
'seashell',
'sienna',
'silver',
'skyblue',
'slateblue',
'slategray',
'snow',
'springgreen',
'steelblue',
'tan',
'teal',
'thistle',
'tomato',
'turquoise',
'violet',
'wheat',
'white',
'whitesmoke',
'yellow',
'yellowgreen',
].map((name) => ({ label: name, type: 'text' })),
);
+1 -3
View File
@@ -12,7 +12,6 @@ const buttonStyle = css`
cursor: pointer;
color: var(--tango-colors-gray-50);
padding: 0 8px;
white-space: nowrap;
.anticon-down {
width: 10px;
@@ -202,8 +201,7 @@ export function ToggleButton(props: ToggleButtonProps) {
let btn = (
<Button css={buttonStyle} className={clazz} type="button" {...rest}>
{/* @ts-ignore textOverflow is available */}
<Box as="span" flex="1" overflow="hidden" textOverflow="ellipsis">
<Box as="span">
{icon && (
<Box as="span" className="buttonIcon" mr="s">
{icon}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

+3 -28
View File
@@ -17697,16 +17697,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -17819,7 +17810,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -17833,13 +17824,6 @@ strip-ansi@^3.0.1:
dependencies:
ansi-regex "^2.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz"
@@ -19376,7 +19360,7 @@ workerpool@^9.1.1:
resolved "https://registry.npmmirror.com/workerpool/-/workerpool-9.1.1.tgz#9ba4d534a79a5517c1e1b9d1014151516829be8d"
integrity sha512-EFoFTSEo9m4V4wNrwzVRjxnf/E/oBpOzcI/R5CIugJhl9RsCiq525rszo4AtqcjQQoqFdu2E3H82AnbtpaQHvg==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -19394,15 +19378,6 @@ wrap-ansi@^6.0.1:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz"