Compare commits

...

16 Commits

Author SHA1 Message Date
wwsun 77f4145adf chore(release): publish
- @music163/tango-context@0.2.4
 - @music163/tango-core@0.2.3
 - @music163/tango-designer@0.5.2
 - @music163/tango-helpers@0.1.5
 - @music163/tango-sandbox@0.1.8
 - @music163/tango-setting-form@0.2.7
 - @music163/tango-ui@0.1.8
2023-11-02 15:28:40 +08:00
wwsun 11f166a2a6 fix: update style of components panel 2023-11-02 15:26:13 +08:00
wwsun 0452fdb7f1 chore: fix types 2023-11-02 15:17:22 +08:00
wwsun 0068d3426e docs: update 2023-11-02 11:25:22 +08:00
wwsun c5840f29b7 fix: limit outline tree node icon 2023-11-02 11:25:22 +08:00
wwsun 963bfe1ab8 fix(core): logging drop action 2023-11-02 11:21:02 +08:00
wwsun ae806aee35 fix(core): create store without entry file 2023-11-02 11:21:02 +08:00
BoBoooooo b29e23599b fix: outline-panel tree expand bug & add props (#52)
* fix: outline-panel tree expand bug

* feat: collpase-panel add showBottomBorder prop

* feat: outline-panel add props

* fix: rename onPageNodeSelect to onSelect
2023-11-02 11:10:39 +08:00
wwsun f53ce94c6e fix: refactor workspace onFilesChange 2023-10-31 17:24:18 +08:00
BoBoooooo d775a5003a fix: add onFilesChange to workspace (#50)
* feat: workspace add onFileChange event callback

* fix: rename onFileChange to onFilesChange

* revert: sandbox ts def
2023-10-31 17:05:00 +08:00
wwsun 46d5f59b4c fix: update types 2023-10-31 16:54:47 +08:00
wwsun 0c0d95fe58 chore: up 2023-10-31 16:50:18 +08:00
BoBoooooo 9dd11f7a6d fix: designer model add defaultActiveSidebarPanel (#51)
* feat: designer add component support click mode & support set defaultActivePanel

* revert: remove defaultAddComponentType

* revert: remove addComponentType ts def
2023-10-31 16:49:14 +08:00
BoBoooooo fd2fa943b0 fix: resizable-box support horizontal and vertical dragging (#48) 2023-10-31 15:44:03 +08:00
wwsun 70fc6f8030 fix: optional context config 2023-10-25 11:24:18 +08:00
Jiyu Shao 4f7233bd6c chore: fix tsconfig paths (#44) 2023-10-25 11:22:01 +08:00
34 changed files with 4607 additions and 4575 deletions
+3 -6
View File
@@ -5,7 +5,7 @@
<h1 align="center">Tango LowCode Builder</h1>
<div align="center">
A source code based low-code builder, developed by NetEase Cloud Music Develop Team.
A source code based low-code builder.
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/NetEase/tango/blob/main/LICENSE)
[![NPM version](https://img.shields.io/npm/v/@music163/tango-designer.svg?style=flat-square)](http://npmjs.org/package/@music163/tango-designer)
@@ -79,6 +79,7 @@ yarn start
Join NetEase Tango Community to share your ideas, suggestions, or questions and connect with other users and contributors.
- Discord: <https://discord.gg/B6hkGTe4Rz>
- [Usage Trends](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 🤝 Contributing
@@ -89,13 +90,9 @@ Please read the [github contribution guide](https://docs.github.com/en/get-start
- Commit and push your code
- Open a Pull Request
## 📈 Usage Trends
[Usage Trends of Tango Packages](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 💗 Acknowledgments
Thanks to the NetEase Cloud Music Front-end team, Public Technology team, Live Broadcasting Technology team, and all the colleagues who participated in the Tango project.
Thanks to the NetEase Cloud Music Front-end team, Public Technology team, Live Broadcasting Technology team, and all the developers who participated in the Tango project.
Thank you to CodeSandbox for providing the [Sandpack](https://sandpack.codesandbox.io/) project, which provides powerful online code execution capabilities for Tango.
+3 -6
View File
@@ -5,7 +5,7 @@
<h1 align="center">Tango 低代码设计器</h1>
<div align="center">
一个源码驱动的低代码设计器,来自网易云音乐技术团队
一个源码驱动的低代码设计器框架
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/NetEase/tango/blob/main/LICENSE)
[![NPM version](https://img.shields.io/npm/v/@music163/tango-designer.svg?style=flat-square)](http://npmjs.org/package/@music163/tango-designer)
@@ -79,6 +79,7 @@ yarn start
参与 NetEase Tango 的社区,以分享您的想法、建议或问题,并与其他用户和贡献者建立联系。
- Discord: <https://discord.gg/B6hkGTe4Rz>
- [使用趋势](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 🤝 参与共建
@@ -90,13 +91,9 @@ yarn start
- 合并修改 git rebase master
- 发起 Pull Request
## 📈 使用量趋势
[Tango 使用量趋势](https://npm-compare.com/@music163/tango-helpers,@music163/tango-context,@music163/tango-core,@music163/tango-setting-form,@music163/tango-sandbox,@music163/tango-ui,@music163/tango-designer)
## 💗 致谢
感谢网易云音乐公共技术团队,大前端团队,直播技术团队,以及所有参与过 Tango 项目的同学们
感谢网易云音乐公共技术团队,大前端团队,直播技术团队,以及所有参与过 Tango 项目的开发者
感谢 CodeSandbox 提供的 [Sandpack](https://sandpack.codesandbox.io/) 项目,为 Tango 提供了强大的基于浏览器的代码构建与执行能力。
+4
View File
@@ -29,11 +29,15 @@ import {
const workspace = new Workspace({
entry: '/src/index.js',
files: sampleFiles,
onFilesChange: (filenames) => {
console.log('onFilesChange', filenames, workspace.files);
},
});
// 2. 引擎初始化
const engine = createEngine({
workspace,
defaultActiveSidebarPanel: 'outline',
});
// @ts-ignore
+20 -23
View File
@@ -25,45 +25,42 @@
"prepare": "husky install"
},
"devDependencies": {
"@babel/preset-env": "^7.19.3",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/preset-env": "^7.23.2",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.2",
"@commitlint/cli": "^17.5.0",
"@commitlint/config-conventional": "^17.3.0",
"@types/jest": "^29.2.4",
"@types/lodash-es": "^4.17.8",
"@types/lodash.get": "^4.4.7",
"@types/lodash.isequal": "^4.5.6",
"@types/lodash.set": "^4.3.7",
"@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4",
"@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^6.4.1",
"@typescript-eslint/parser": "^6.4.1",
"@types/jest": "^29.5.7",
"@types/lodash-es": "^4.17.10",
"@types/lodash.get": "^4.4.8",
"@types/lodash.isequal": "^4.5.7",
"@types/lodash.set": "^4.3.8",
"@types/react": "^18.2.34",
"@types/react-dom": "^18.2.14",
"@types/styled-components": "^5.1.29",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"conventional-changelog-cli": "^2.2.2",
"copyfiles": "^2.4.1",
"eslint": "^8.40.0",
"eslint": "^8.52.0",
"eslint-config-ali": "^14.0.2",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.28.1",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"http-server": "^14.1.0",
"husky": "^8.0.3",
"jest": "^29.1.1",
"jest-environment-jsdom": "^29.1.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"lerna": "^6.6.1",
"lint-staged": "^14.0.1",
"prettier": "^2.8.7",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"styled-components": "^5.3.6",
"typedoc": "^0.25.0",
"typescript": "^5.0.4"
},
"resolutions": {
"@yarnpkg/parsers": "3.0.0-rc.48.1"
"typedoc": "^0.25.3",
"typescript": "^5.2.2"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.4](https://github.com/netease/tango/compare/@music163/tango-context@0.2.3...@music163/tango-context@0.2.4) (2023-11-02)
### Bug Fixes
- optional context config ([70fc6f8](https://github.com/netease/tango/commit/70fc6f8030202f5d340281c0424752044ed0fb83))
## [0.2.3](https://github.com/netease/tango/compare/@music163/tango-context@0.2.2...@music163/tango-context@0.2.3) (2023-10-23)
**Note:** Version bump only for package @music163/tango-context
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-context",
"version": "0.2.3",
"version": "0.2.4",
"description": "react context for tango-apps",
"keywords": [
"react",
@@ -31,8 +31,8 @@
"react": ">= 16.8"
},
"dependencies": {
"@music163/tango-core": "^0.2.2",
"@music163/tango-helpers": "^0.1.4",
"@music163/tango-core": "^0.2.3",
"@music163/tango-helpers": "^0.1.5",
"mobx-react-lite": "4.0.2"
},
"publishConfig": {
+3 -3
View File
@@ -16,9 +16,9 @@ export interface ITangoEngineContext {
/**
* 自定义配置数据
*/
config: {
customActionVariables: CustomVariableData[];
customExpressionVariables: CustomVariableData[];
config?: {
customActionVariables?: CustomVariableData[];
customExpressionVariables?: CustomVariableData[];
};
}
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.3](https://github.com/netease/tango/compare/@music163/tango-core@0.2.2...@music163/tango-core@0.2.3) (2023-11-02)
### Bug Fixes
- add onFilesChange to workspace ([#50](https://github.com/netease/tango/issues/50)) ([d775a50](https://github.com/netease/tango/commit/d775a5003ab1a14f801d3b38f7187ea15ef7d74d))
- **core:** create store without entry file ([ae806ae](https://github.com/netease/tango/commit/ae806aee3583ef5f957836e15d715035c4556867))
- **core:** logging drop action ([963bfe1](https://github.com/netease/tango/commit/963bfe1ab80fa4114637963534b2891a27538120))
- designer model add defaultActiveSidebarPanel ([#51](https://github.com/netease/tango/issues/51)) ([9dd11f7](https://github.com/netease/tango/commit/9dd11f7a6d5807c80a63c84c1ad0df5bc1ce9558))
- refactor workspace onFilesChange ([f53ce94](https://github.com/netease/tango/commit/f53ce94c6ed777d45ba6137188bfbc4566e03942))
- update types ([46d5f59](https://github.com/netease/tango/commit/46d5f59b4cd4fa71ed247f5e470e66c8c9f6d4a0))
## [0.2.2](https://github.com/netease/tango/compare/@music163/tango-core@0.2.1...@music163/tango-core@0.2.2) (2023-10-23)
### Bug Fixes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-core",
"version": "0.2.2",
"version": "0.2.3",
"description": "tango core",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -24,13 +24,13 @@
"prepublishOnly": "yarn build"
},
"dependencies": {
"@babel/generator": "^7.22.15",
"@babel/parser": "^7.22.15",
"@babel/traverse": "^7.22.15",
"@babel/types": "^7.22.15",
"@music163/tango-helpers": "^0.1.4",
"@types/babel__generator": "^7.6.4",
"@types/babel__traverse": "^7.18.3",
"@babel/generator": "^7.23.0",
"@babel/parser": "^7.23.0",
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@music163/tango-helpers": "^0.1.5",
"@types/babel__generator": "^7.6.6",
"@types/babel__traverse": "^7.20.3",
"mobx": "6.9.0",
"path-browserify": "^1.0.1"
},
+6
View File
@@ -10,6 +10,10 @@ interface ICreateEngineOptions {
* 默认的模拟器模式
*/
defaultSimulatorMode?: SimulatorNameType;
/**
* 默认激活的侧边栏
*/
defaultActiveSidebarPanel?: string;
}
/**
@@ -20,12 +24,14 @@ interface ICreateEngineOptions {
export function createEngine({
workspace,
defaultSimulatorMode = 'desktop',
defaultActiveSidebarPanel = '',
}: ICreateEngineOptions) {
const engine = new Engine({
workspace,
designer: new Designer({
workspace,
simulator: defaultSimulatorMode,
activeSidebarPanel: defaultActiveSidebarPanel,
}),
});
-1
View File
@@ -33,7 +33,6 @@ export function inferFileType(filename: string): FileType {
return FileType.JsonViewModule;
}
// FIXME: 暂时写死,后续要支持多 services 文件解析
if (/\/services\/.+\.js$/.test(filename)) {
return FileType.ServiceModule;
}
+36 -18
View File
@@ -3,22 +3,26 @@ import { IWorkspace } from './interfaces';
export type SimulatorNameType = 'desktop' | 'phone';
type SimulatorType = {
export type DesignerViewType = 'design' | 'code';
interface ISimulatorType {
name: SimulatorNameType;
width: number;
height: number;
};
}
type ViewportBoundingType = {
interface IViewportBounding {
width: number;
height: number;
};
}
export type DesignerViewType = 'design' | 'code';
interface IDesignerOptions {
workspace: IWorkspace;
simulator?: SimulatorNameType | ISimulatorType;
activeSidebarPanel?: string;
}
type DesignerOptionsType = { workspace: IWorkspace; simulator?: SimulatorNameType | SimulatorType };
const simulatorTypes: Record<string, SimulatorType> = {
const ISimulatorTypes: Record<string, ISimulatorType> = {
desktop: {
name: 'desktop',
width: 1366,
@@ -35,12 +39,12 @@ export class Designer {
/**
* 当前的沙箱模拟器类型
*/
_simulator: SimulatorType = simulatorTypes.desktop;
_simulator: ISimulatorType = ISimulatorTypes.desktop;
/**
* 当前的视图尺寸
*/
_viewport: ViewportBoundingType = {
_viewport: IViewportBounding = {
width: 1366,
height: 800,
};
@@ -70,9 +74,14 @@ export class Designer {
*/
_isPreview = false;
/**
* 默认展开的侧边栏
*/
defaultActiveSidebarPanel?: string;
private readonly workspace: IWorkspace;
get simulator(): SimulatorType {
get simulator(): ISimulatorType {
return toJS(this._simulator);
}
@@ -100,11 +109,21 @@ export class Designer {
return this._showRightPanel;
}
constructor(options: DesignerOptionsType) {
constructor(options: IDesignerOptions) {
this.workspace = options.workspace;
if (options.simulator) {
this.setSimulator(options.simulator);
const { simulator, activeSidebarPanel: defaultActiveSidebarPanel } = options;
// 默认设计器模式
if (simulator) {
this.setSimulator(simulator);
}
// 默认展开的侧边栏
if (defaultActiveSidebarPanel) {
this.setActiveSidebarPanel(defaultActiveSidebarPanel);
}
makeObservable(this, {
_simulator: observable,
_viewport: observable,
@@ -131,15 +150,15 @@ export class Designer {
});
}
setSimulator(value: SimulatorType | SimulatorNameType) {
setSimulator(value: ISimulatorType | SimulatorNameType) {
if (typeof value === 'string') {
this._simulator = simulatorTypes[value];
this._simulator = ISimulatorTypes[value];
} else {
this._simulator = value;
}
}
setViewport(value: ViewportBoundingType) {
setViewport(value: IViewportBounding) {
this._viewport = value;
}
@@ -154,7 +173,6 @@ export class Designer {
this._activeSidebarPanel = '';
}
}
closeSidebarPanel() {
this._activeSidebarPanel = '';
}
+9 -7
View File
@@ -157,13 +157,7 @@ export interface IWorkspace {
setComponentPrototypes: (prototypes: Record<string, ComponentPrototypeType>) => void;
getPrototype: (name: string | ComponentPrototypeType) => ComponentPrototypeType;
/**
* 查询节点
* @param id 节点 ID
* @param module 节点所在的模块名
* @returns 返回节点对象
*/
getNode: (id: string, module?: string) => IViewNode;
onFilesChange?: (filenames: string[]) => void;
getFile: (filename: string) => TangoFile;
listFiles: () => Record<string, string>;
@@ -174,6 +168,14 @@ export interface IWorkspace {
renameFile: (oldFilename: string, newFilename: string) => void;
renameFolder: (oldFoldername: string, newFoldername: string) => void;
/**
* 查询节点
* @param id 节点 ID
* @param module 节点所在的模块名
* @returns 返回节点对象
*/
getNode: (id: string, module?: string) => IViewNode;
removeSelectedNode: () => void;
cloneSelectedNode: () => void;
copySelectedNode: () => void;
+28 -12
View File
@@ -55,6 +55,10 @@ export interface IWorkspaceOptions {
* 组件描述列表
*/
prototypes?: Record<string, ComponentPrototypeType>;
/**
* 工作区文件变更事件
*/
onFilesChange?: (filenames: string[]) => void;
}
/**
@@ -125,6 +129,11 @@ export class Workspace extends EventTarget implements IWorkspace {
*/
appJson: TangoJsonFile;
/**
* 代码变更回调
*/
onFilesChange?: (filenames: string[]) => void;
/**
* 绑定事件
* TODO: 是否需要自己来管理 listeners,并及时进行 gc
@@ -220,6 +229,7 @@ export class Workspace extends EventTarget implements IWorkspace {
this.activeViewFile = '';
this.files = new Map();
this.isReady = false;
this.onFilesChange = options?.onFilesChange;
if (options?.files) {
this.addFiles(options.files);
@@ -598,9 +608,12 @@ export class Workspace extends EventTarget implements IWorkspace {
* @param code 模型代码
*/
addStoreModule(name: string, code: string) {
const filename = getFilepath(name, '/src/stores', '.js');
const filename = `/src/stores/${name}.js`;
this.addFile(filename, code);
this.storeEntryModule?.addStore(name).update();
if (!this.storeEntryModule) {
this.addFile('/src/stores/index.js', '');
}
this.storeEntryModule.addStore(name).update();
}
/**
@@ -1094,13 +1107,6 @@ export class Workspace extends EventTarget implements IWorkspace {
}
this.selectSource.clear();
this.history.push({
message: HistoryMessage.DropNode,
data: {
[targetFile.filename]: targetFile.code,
},
});
}
targetFile.update();
@@ -1109,6 +1115,15 @@ export class Workspace extends EventTarget implements IWorkspace {
}
dragSource.clear();
if (isValidOperation) {
this.history.push({
message: HistoryMessage.DropNode,
data: {
[targetFile.filename]: targetFile.code,
},
});
}
}
/**
@@ -1124,6 +1139,7 @@ export class Workspace extends EventTarget implements IWorkspace {
},
}),
);
this.onFilesChange?.(filenames);
}
/**
@@ -1185,11 +1201,11 @@ export class Workspace extends EventTarget implements IWorkspace {
if (this.files.has(sourceFilePath)) {
// 来源是文件
const file = this.files.get(sourceFilePath);
this.addFile(targetFilePath + '.js', file.cleanCode, file.type);
} else if (this.files.has(sourceFilePath + '/index.js')) {
this.addFile(`${targetFilePath}.js`, file.cleanCode, file.type);
} else if (this.files.has(`${sourceFilePath}/index.js`)) {
// 来源是目录
Array.from(this.files.keys()).forEach((key) => {
if (key.startsWith(sourceFilePath + '/')) {
if (key.startsWith(`${sourceFilePath}/`)) {
const sourceFile = this.getFile(key);
this.addFile(
targetFilePath + key.slice(sourceFilePath.length),
+9
View File
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.5.2](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.1...@music163/tango-designer@0.5.2) (2023-11-02)
### Bug Fixes
- limit outline tree node icon ([c5840f2](https://github.com/netease/tango/commit/c5840f29b724966930e239fe00ee13b2fb882503))
- outline-panel tree expand bug & add props ([#52](https://github.com/netease/tango/issues/52)) ([b29e235](https://github.com/netease/tango/commit/b29e23599befbd60845053544648b10976a65824))
- resizable-box support horizontal and vertical dragging ([#48](https://github.com/netease/tango/issues/48)) ([fd2fa94](https://github.com/netease/tango/commit/fd2fa943b05e9f1fa57574e703307725c5aed5d8))
- update style of components panel ([11f166a](https://github.com/netease/tango/commit/11f166a2a67313200922a514a8219f34cfafd8ab))
## [0.5.1](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.0...@music163/tango-designer@0.5.1) (2023-10-23)
### Bug Fixes
+11 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-designer",
"version": "0.5.1",
"version": "0.5.2",
"description": "lowcode designer",
"keywords": [
"react"
@@ -32,13 +32,13 @@
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/request": "^0.1.0",
"@music163/tango-context": "^0.2.3",
"@music163/tango-core": "^0.2.2",
"@music163/tango-helpers": "^0.1.4",
"@music163/tango-sandbox": "^0.1.7",
"@music163/tango-setting-form": "^0.2.6",
"@music163/tango-ui": "^0.1.7",
"@music163/request": "^0.1.2",
"@music163/tango-context": "^0.2.4",
"@music163/tango-core": "^0.2.3",
"@music163/tango-helpers": "^0.1.5",
"@music163/tango-sandbox": "^0.1.8",
"@music163/tango-setting-form": "^0.2.7",
"@music163/tango-ui": "^0.1.8",
"antd": "^4.24.2",
"cash-dom": "^8.1.2",
"classnames": "^2.3.2",
@@ -53,9 +53,9 @@
"semver": "^7.3.8"
},
"devDependencies": {
"@types/color": "^3.0.4",
"@types/react-color": "^3.0.6",
"@types/react-resizable": "^3.0.4"
"@types/color": "^3.0.5",
"@types/react-color": "^3.0.9",
"@types/react-resizable": "^3.0.6"
},
"publishConfig": {
"access": "public",
@@ -74,14 +74,6 @@ export function useFlatMenuData<T>(menuData: T) {
];
}
const StyledSpin = styled(Spin)`
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
margin-left: 25px;
`;
export const ComponentsPanel = observer(
({
menuData = emptyMenuData,
@@ -101,8 +93,6 @@ export const ComponentsPanel = observer(
[workspace.bizComps, workspace.blocks, getBizCompName],
);
if (loading) return <StyledSpin tip="正在加载物料列表..." spinning />;
const tabs = Object.keys(menuData).map((key) => ({
key,
label: localeMap[key],
@@ -135,7 +125,9 @@ export const ComponentsPanel = observer(
<Box px="l" py="m" position="sticky" top="0" zIndex={1} bg="white">
<Search placeholder="搜索物料" onChange={setKeyword} />
</Box>
{!keyword ? contentNode : <MaterialList data={allList} filterKeyword={keyword} />}
<Spin spinning={loading} tip="正在加载物料列表...">
{!keyword ? contentNode : <MaterialList data={allList} filterKeyword={keyword} />}
</Spin>
</Box>
);
},
+2 -1
View File
@@ -4,4 +4,5 @@ export * from './components-panel';
export * from './dependency-panel';
export * from './history-panel';
export * from './variable-panel';
export * from './sidebar';
export * from './sidebar';
export * from './resizable-box';
@@ -1,14 +1,34 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Space, Tree } from 'antd';
import { Dropdown, Space, Tree } from 'antd';
import { Box, css } from 'coral-system';
import { IconFont } from '@music163/tango-ui';
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
import { EyeOutlined, EyeInvisibleOutlined, EllipsisOutlined } from '@ant-design/icons';
import { observer, useWorkspace } from '@music163/tango-context';
import { DropMethod, ITangoViewNodeData } from '@music163/tango-core';
import { parseDndId } from '@music163/tango-helpers';
import { noop, parseDndId } from '@music163/tango-helpers';
import { useSandboxQuery } from '../../context';
import { buildQueryBySlotId } from '../../helpers';
export interface ComponentsTreeProps {
/**
* 显示/隐藏组件按钮,默认显示
*/
showToggleVisibleIcon?: boolean;
/**
* 页面节点选中回调
*/
onSelect?: (nodeKey: string) => void;
/**
* 节点树更多功能菜单
*/
actionItems?: ActionItem[];
}
interface ActionItem {
item: React.ReactNode;
key: string;
}
const outlineStyle = css`
.ant-tree-title {
color: rgb(85, 85, 85);
@@ -32,6 +52,10 @@ const outlineStyle = css`
.ant-tree-switcher-noop {
display: none;
}
.material-icon {
width: 1em;
height: 1em;
}
`;
const filedNames = {
@@ -51,126 +75,159 @@ const getNodeKeys = (data: ITangoViewNodeData[]) => {
return ids;
};
const OutlineTreeNode: React.FC<any> = observer(({ node }: { node: ITangoViewNodeData }) => {
const workspace = useWorkspace();
const sandboxQuery = useSandboxQuery();
const [visible, setVisible] = useState(true);
const nodeLabel = useMemo(() => {
const { component, index } = parseDndId(node.id);
return `${component}:${index}`;
}, [node.id]);
const componentPrototype = workspace.componentPrototypes.get(node.component);
const icon = componentPrototype?.icon || 'icon-placeholder';
const OutlineTreeNode: React.FC<{ node: ITangoViewNodeData } & ComponentsTreeProps> = observer(
({ node, showToggleVisibleIcon, actionItems }) => {
const workspace = useWorkspace();
const sandboxQuery = useSandboxQuery();
const [visible, setVisible] = useState(true);
const nodeLabel = useMemo(() => {
const { component, index } = parseDndId(node.id);
return `${component}:${index}`;
}, [node.id]);
const componentPrototype = workspace.componentPrototypes.get(node.component);
const icon = componentPrototype?.icon || 'icon-placeholder';
const iconRender = icon.startsWith('icon-') ? (
<IconFont className="material-icon" type={icon} />
) : (
<img src={icon} alt={componentPrototype.name} />
);
const iconRender = icon.startsWith('icon-') ? (
<IconFont className="material-icon" type={icon} />
) : (
<img className="material-icon" src={icon} alt={componentPrototype.name} />
);
const toggleVisible = (nodeId: string) => {
sandboxQuery.getElement(buildQueryBySlotId(nodeId)).style.visibility = visible
? 'hidden'
: 'visible';
setVisible(!visible);
};
const toggleVisible = (nodeId: string) => {
sandboxQuery.getElement(buildQueryBySlotId(nodeId)).style.visibility = visible
? 'hidden'
: 'visible';
setVisible(!visible);
};
const VisibleIcon = visible ? EyeOutlined : EyeInvisibleOutlined;
return (
<Space>
{iconRender} {nodeLabel}
{/* 切换显示 */}
<VisibleIcon
className="material-icon"
onClick={(e) => {
e.stopPropagation();
toggleVisible(node.id);
}}
/>
</Space>
);
});
export const ComponentsTree = observer(() => {
const workspace = useWorkspace();
const sandboxQuery = useSandboxQuery();
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>(
workspace.selectSource.selected.map((item) => item.id),
);
const file = workspace.activeViewModule;
const nodesTree = file.nodesTree as ITangoViewNodeData[];
useEffect(() => {
setSelectedKeys(workspace.selectSource.selected.map((item) => item.id));
}, [workspace.selectSource.selected]);
if (!nodesTree.length) {
const VisibleIcon = visible ? EyeOutlined : EyeInvisibleOutlined;
return (
<Box>
<code>{workspace.activeViewFile}</code>
<Space>
{iconRender} {nodeLabel}
{/* 切换显示 */}
{showToggleVisibleIcon && (
<VisibleIcon
className="material-icon"
onClick={(e) => {
e.stopPropagation();
toggleVisible(node.id);
}}
/>
)}
{/* 更多 ... 功能菜单 */}
{actionItems?.length > 0 && (
<Dropdown
menu={{
items: actionItems.map((_) => ({
key: _.key,
label: _.item,
})),
}}
trigger={['click']}
>
<EllipsisOutlined />
</Dropdown>
)}
</Space>
);
},
);
export const ComponentsTree: React.FC<ComponentsTreeProps> = observer(
({ showToggleVisibleIcon = true, onSelect = noop, actionItems }) => {
const workspace = useWorkspace();
const sandboxQuery = useSandboxQuery();
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>(
workspace.selectSource.selected.map((item) => item.id),
);
const file = workspace.activeViewModule;
const nodesTree = (file?.nodesTree ?? []) as ITangoViewNodeData[];
const [expandedKeys, setExpandedKeys] = useState(getNodeKeys(nodesTree));
useEffect(() => {
setSelectedKeys(workspace.selectSource.selected.map((item) => item.id));
}, [workspace.selectSource.selected]);
if (!nodesTree.length) {
return (
<Box>
<code>{workspace.activeViewFile}</code>
</Box>
);
}
useEffect(() => {
setExpandedKeys(getNodeKeys(nodesTree));
// nodeTree 更新后重新计算expandKeys
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodesTree?.[0]?.id]);
return (
<Box css={outlineStyle}>
<Tree
selectedKeys={selectedKeys as string[]}
fieldNames={filedNames}
treeData={nodesTree as any[]}
onSelect={(keys) => {
const slotKey = keys?.[0] as string;
const data = sandboxQuery.getDraggableParentsData(buildQueryBySlotId(slotKey), true);
if (data && data.id) {
workspace.selectSource.select({
id: data.id,
name: data.name,
bounding: data.bounding,
parents: data.parents,
});
}
// export selected
onSelect(slotKey);
setSelectedKeys(keys);
}}
blockNode
expandedKeys={expandedKeys}
onExpand={(keys) => setExpandedKeys(keys as string[])}
draggable
onDragStart={(data) => {
const prototype = workspace.componentPrototypes.get(data.node.component);
if (!prototype) {
return;
}
const { canDrag } = prototype.rules || {};
if (canDrag && !canDrag()) {
return;
}
workspace.dragSource.set({
id: data.node.id,
name: data.node.component,
});
}}
onDrop={(data) => {
const dropKey = data.node.key as string;
let method;
if (data.dropToGap) {
// 插入节点的后面
method = DropMethod.InsertAfter;
} else {
// 作为第一个子节点
method = DropMethod.InsertFirstChild;
}
workspace.dragSource.dropTarget.set(
{
id: dropKey,
},
method,
);
workspace.dropNode();
}}
titleRender={(node) => (
<OutlineTreeNode
actionItems={actionItems}
showToggleVisibleIcon={showToggleVisibleIcon}
node={node}
/>
)}
/>
</Box>
);
}
return (
<Box css={outlineStyle}>
<Tree
selectedKeys={selectedKeys}
fieldNames={filedNames}
treeData={nodesTree as any[]}
onSelect={(keys) => {
const data = sandboxQuery.getDraggableParentsData(
buildQueryBySlotId(keys[0] as string),
true,
);
if (data && data.id) {
workspace.selectSource.select({
id: data.id,
name: data.name,
bounding: data.bounding,
parents: data.parents,
});
}
setSelectedKeys(keys);
}}
blockNode
autoExpandParent
expandedKeys={getNodeKeys(nodesTree)}
draggable
onDragStart={(data) => {
const prototype = workspace.componentPrototypes.get(data.node.component);
if (!prototype) {
return;
}
const { canDrag } = prototype.rules || {};
if (canDrag && !canDrag()) {
return;
}
workspace.dragSource.set({
id: data.node.id,
name: data.node.component,
});
}}
onDrop={(data) => {
const dropKey = data.node.key as string;
let method;
if (data.dropToGap) {
// 插入节点的后面
method = DropMethod.InsertAfter;
} else {
// 作为第一个子节点
method = DropMethod.InsertFirstChild;
}
workspace.dragSource.dropTarget.set(
{
id: dropKey,
},
method,
);
workspace.dropNode();
}}
titleRender={(node) => <OutlineTreeNode node={node} />}
/>
</Box>
);
});
},
);
@@ -2,20 +2,26 @@ import React from 'react';
import { Box } from 'coral-system';
import { CollapsePanel } from '@music163/tango-ui';
import { StateTree } from './state-tree';
import { ComponentsTree } from './components-tree';
import { ComponentsTree, ComponentsTreeProps } from './components-tree';
export interface OutlineViewProps {
export interface OutlineViewProps extends ComponentsTreeProps {
/**
* 展示状态视图
*/
showStateView?: boolean;
}
export function OutlinePanel({ showStateView = true }: OutlineViewProps) {
export function OutlinePanel({ showStateView = true, ...treeProps }: OutlineViewProps) {
return (
<Box height="100%" position="relative">
<CollapsePanel title="页面结构" stickyHeader maxHeight="90%" overflowY="auto">
<ComponentsTree />
<CollapsePanel
title="页面结构"
maxHeight={showStateView ? '90%' : '100%'}
collapsed={!showStateView ? false : undefined}
overflowY="auto"
showBottomBorder={showStateView}
>
<ComponentsTree {...treeProps} />
</CollapsePanel>
{showStateView && (
<CollapsePanel title="页面状态" stickyHeader maxHeight="90%" overflowY="auto">
+71 -18
View File
@@ -1,45 +1,98 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import cx from 'classnames';
import { Box, HTMLCoralProps, css } from 'coral-system';
import { Resizable } from 'react-resizable';
import { Resizable, ResizeHandle } from 'react-resizable';
const resizeHandleStyle = css`
export interface ResizableBoxProps extends HTMLCoralProps<'div'> {
width?: number;
height?: number;
resizeHandlePosition?: 'left' | 'right' | 'top' | 'bottom';
axis?: 'x' | 'y';
}
const resizeHandleStyle = (axis: ResizableBoxProps['axis'], barStyle: HTMLCoralProps<'div'>) => css`
position: absolute;
top: 0;
z-index: 999;
width: 4px;
height: 100%;
cursor: col-resize;
cursor: ${axis === 'x' ? 'col-resize' : 'row-resize'};
${barStyle &&
Object.keys(barStyle)
.map((key) => `${key}: ${barStyle[key]};`)
.join('')}
&:hover,
&:active {
background-color: var(--tango-colors-brand);
}
`;
export interface ResizableBoxProps extends HTMLCoralProps<'div'> {
width?: number;
height?: number;
resizeHandlePosition?: 'left' | 'right';
}
const getResizeHandles = (
resizeHandlePosition: ResizableBoxProps['resizeHandlePosition'],
axis: ResizableBoxProps['axis'],
) => {
const handles: ResizeHandle[] = [];
switch (axis) {
case 'x':
handles.push(resizeHandlePosition === 'right' ? 'e' : 'w');
break;
case 'y':
handles.push(resizeHandlePosition === 'bottom' ? 's' : 'n');
break;
default:
break;
}
return handles;
};
export function ResizableBox({
resizeHandlePosition = 'right',
width: widthProp,
height,
height: heightProp,
children,
className,
style,
axis = 'x',
}: ResizableBoxProps) {
const [width, setWidth] = useState(widthProp);
const barStyle = resizeHandlePosition === 'right' ? { right: '-4px' } : { left: '-4px' };
const [height, setHeight] = useState(heightProp);
const barStyle: HTMLCoralProps<'div'> = {
...(axis === 'x'
? {
height: '100%',
width: '4px',
top: '0',
[resizeHandlePosition === 'right' ? 'right' : 'left']: '-4px',
}
: {
height: '4px',
width: '100%',
left: '0',
[resizeHandlePosition === 'bottom' ? 'bottom' : 'top']: '0',
}),
};
useEffect(() => {
setWidth(widthProp);
}, [widthProp]);
useEffect(() => {
setHeight(heightProp);
}, [heightProp]);
return (
<Resizable
axis="x"
axis={axis}
width={width}
resizeHandles={getResizeHandles(resizeHandlePosition, axis)}
height={height}
minConstraints={[150, 150]}
maxConstraints={[window.innerWidth * 0.6, window.innerHeight * 0.8]}
onResize={(e, { size }) => {
setWidth(size.width);
if (axis === 'x') {
setWidth(size.width);
}
if (axis === 'y') {
setHeight(size.height);
}
}}
onResizeStart={() => {
document.body.style.pointerEvents = 'none';
@@ -49,7 +102,7 @@ export function ResizableBox({
document.body.style.pointerEvents = 'auto';
document.body.style.userSelect = 'auto';
}}
handle={<Box className="ResizeHandle" css={resizeHandleStyle} {...barStyle} />}
handle={<Box className="ResizeHandle" css={resizeHandleStyle(axis, barStyle)} />}
>
<div
className={cx('ResizableBox', className)}
+1 -1
View File
@@ -76,7 +76,7 @@ export interface SidebarPanelItemProps
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) {
const items = useMemo(() => {
const ret: Record<React.Key, SidebarPanelItemProps> = {};
const ret: Record<string, SidebarPanelItemProps> = {};
React.Children.forEach(children, (child) => {
if (child && React.isValidElement(child)) {
const widget = getWidget(['sidebar', child.key].join('.'));
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.5](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.4...@music163/tango-helpers@0.1.5) (2023-11-02)
**Note:** Version bump only for package @music163/tango-helpers
## [0.1.4](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.3...@music163/tango-helpers@0.1.4) (2023-10-23)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-helpers",
"version": "0.1.4",
"version": "0.1.5",
"description": "Shared types, helpers, and hooks of tango-apps",
"keywords": [
"shared",
+1 -1
View File
@@ -72,7 +72,7 @@ export function withDnd(options: IWithDndOptions<any>) {
);
});
hoistNonReactStatic(Component, BaseComponent);
hoistNonReactStatic(Component, BaseComponent as any);
Component.displayName = wrapDisplayName(displayName, 'withDnd');
return Component;
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.7...@music163/tango-sandbox@0.1.8) (2023-11-02)
**Note:** Version bump only for package @music163/tango-sandbox
## [0.1.7](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.6...@music163/tango-sandbox@0.1.7) (2023-10-23)
**Note:** Version bump only for package @music163/tango-sandbox
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-sandbox",
"version": "0.1.7",
"version": "0.1.8",
"description": "sandbox of tango apps",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -29,14 +29,14 @@
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/tango-core": "^0.2.2",
"@music163/tango-helpers": "^0.1.4",
"@music163/tango-core": "^0.2.3",
"@music163/tango-helpers": "^0.1.5",
"crypto-js": "^4.1.1",
"lodash.isequal": "4.5.0",
"react-frame-component": "^5.2.4"
},
"devDependencies": {
"@types/crypto-js": "^4.1.1"
"@types/crypto-js": "^4.1.3"
},
"publishConfig": {
"access": "public",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.7](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.6...@music163/tango-setting-form@0.2.7) (2023-11-02)
**Note:** Version bump only for package @music163/tango-setting-form
## [0.2.6](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.5...@music163/tango-setting-form@0.2.6) (2023-10-23)
### Bug Fixes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-setting-form",
"version": "0.2.6",
"version": "0.2.7",
"description": "setting form of tango-apps",
"author": "wwsun <ww.sun@outlook.com>",
"homepage": "",
@@ -28,8 +28,8 @@
},
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@music163/tango-helpers": "^0.1.4",
"@music163/tango-ui": "^0.1.7",
"@music163/tango-helpers": "^0.1.5",
"@music163/tango-ui": "^0.1.8",
"antd": "^4.24.2",
"coral-system": "^1.0.5",
"mobx": "6.9.0",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.7...@music163/tango-ui@0.1.8) (2023-11-02)
### Bug Fixes
- outline-panel tree expand bug & add props ([#52](https://github.com/netease/tango/issues/52)) ([b29e235](https://github.com/netease/tango/commit/b29e23599befbd60845053544648b10976a65824))
## [0.1.7](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.6...@music163/tango-ui@0.1.7) (2023-10-23)
### Bug Fixes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@music163/tango-ui",
"version": "0.1.7",
"version": "0.1.8",
"description": "ui widgets of tango",
"keywords": [
"react",
@@ -38,8 +38,8 @@
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lint": "^6.4.2",
"@codemirror/search": "^6.5.4",
"@music163/tango-helpers": "^0.1.4",
"@uiw/react-codemirror": "^4.19.3",
"@music163/tango-helpers": "^0.1.5",
"@uiw/react-codemirror": "^4.21.20",
"antd": "^4.24.2",
"antd-easy-form": "^1.1.3",
"coral-system": "^1.0.5",
+13 -1
View File
@@ -28,6 +28,10 @@ export interface CollapsePanelProps extends Omit<HTMLCoralProps<'div'>, 'title'>
* 折叠状态变化时的回调
*/
onCollapse?: (collapse: boolean) => void;
/**
* 是否显示底部边框线
*/
showBottomBorder?: boolean;
}
const headerStyle = css`
@@ -49,6 +53,7 @@ export function CollapsePanel(props: CollapsePanelProps) {
defaultCollapsed = false,
onCollapse,
stickyHeader,
showBottomBorder = true,
...rest
} = props;
const [collapsed, setCollapsed] = useControllableState({
@@ -70,8 +75,15 @@ export function CollapsePanel(props: CollapsePanelProps) {
}
: {};
const CollapsePanelBorderStyle = showBottomBorder
? {
borderBottom: 'solid',
borderBottomColor: 'line2',
}
: {};
return (
<Box className="CollapsePanel" borderBottom="solid" borderBottomColor="line2" {...rest}>
<Box className="CollapsePanel" {...CollapsePanelBorderStyle} {...rest}>
<Box
display="flex"
alignItems="center"
+3 -2
View File
@@ -10,9 +10,10 @@
"@music163/tango-helpers": ["packages/helpers/src/index.ts"],
"@music163/tango-core": ["packages/core/src/index.ts"],
"@music163/tango-context": ["packages/context/src/index.ts"],
"@music163/tango-setting-form": ["packages/setting-form/src/index.ts"],
"@music163/tango-ui": ["packages/ui/src/index.ts"],
"@music163/tango": ["packages/designer/src/index.ts"]
"@music163/tango-sandbox": ["packages/sandbox/src/index.ts"],
"@music163/tango-setting-form": ["packages/setting-form/src/index.ts"],
"@music163/tango-designer": ["packages/designer/src/index.ts"]
}
}
}
+4136 -4306
View File
File diff suppressed because it is too large Load Diff