mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-19 20:08:05 +00:00
feat: 合入 trunk-vision 代码
This commit is contained in:
commit
ea6bc7aeb8
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ava my-package",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${workspaceFolder}/packages/material-parser/node_modules/.bin/ava",
|
||||
"runtimeArgs": ["debug", "--break", "${workspaceFolder}/packages/material-parser/test/antd.ts"]
|
||||
}
|
||||
]
|
||||
}
|
||||
1110
CHANGELOG.md
Normal file
1110
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
7
index.ts
Normal file
7
index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import renderer from './packages/react-simulator-renderer/src/renderer';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).SimulatorRenderer = renderer;
|
||||
}
|
||||
|
||||
export default renderer;
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"lerna": "2.11.0",
|
||||
"version": "independent",
|
||||
"version": "0.13.1-29",
|
||||
"npmClient": "tyarn",
|
||||
"registry": "http://registry.npm.alibaba-inc.com",
|
||||
"useWorkspaces": true,
|
||||
|
||||
@ -14,7 +14,8 @@
|
||||
"clean": "rm -rf ./packages/*/lib ./packages/*/es ./packages/*/dist ./packages/*/build",
|
||||
"lint": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet",
|
||||
"lint:fix": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet --fix",
|
||||
"pub": "lerna publish --force-publish --cd-version patch",
|
||||
"pub": "lerna publish --force-publish --cd-version prepatch",
|
||||
"pubbeta": "lerna publish --force-publish --cd-version prerelease --npm-tag beta",
|
||||
"setup": "./scripts/setup.sh",
|
||||
"start": "./scripts/start.sh",
|
||||
"start:server": "./scripts/start-server.sh",
|
||||
@ -42,7 +43,7 @@
|
||||
"xima": "^0.2.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"tnpm": {
|
||||
"mode": "yarn",
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
module.exports = {
|
||||
extends: '../../.eslintrc.js',
|
||||
extends: 'eslint-config-ali/typescript/react',
|
||||
ignorePatterns: [ 'tests/* '],
|
||||
rules: {
|
||||
'react/no-multi-comp': 0,
|
||||
'no-unused-expressions': 1,
|
||||
'no-unused-expressions': 0,
|
||||
'implicit-arrow-linebreak': 1,
|
||||
'no-nested-ternary': 1,
|
||||
'no-mixed-operators': 1,
|
||||
@ -12,5 +13,7 @@ module.exports = {
|
||||
'no-prototype-builtins': 1,
|
||||
'no-useless-constructor': 1,
|
||||
'no-empty-function': 1,
|
||||
'@typescript-eslint/member-ordering': 0,
|
||||
'lines-between-class-members': 0
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
### Bug Fixes
|
||||
|
||||
* 修复 slot 以及子节点不销毁 ([8ef62c8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8ef62c8))
|
||||
* 修复 setDevice 的时机,从 currentDocument -> simualtor ([0f14884](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0f14884))
|
||||
|
||||
|
||||
|
||||
@ -21,6 +22,8 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
### Bug Fixes
|
||||
|
||||
* 修复数据源的接入问题 ([98ae1ed](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/98ae1ed))
|
||||
* documentModel 里的 addon 相关函数跟原 vision 实现对齐 ([b0ea548](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b0ea548))
|
||||
* 原地编辑功能异常, 编辑时需要禁掉快捷键 ([3c000de](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3c000de))
|
||||
|
||||
|
||||
|
||||
@ -32,6 +35,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
### Bug Fixes
|
||||
|
||||
* typo of onResizeEnd and remove ([8df5f05](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8df5f05))
|
||||
* 修复 registerAddon 函数 ([309920a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/309920a))
|
||||
|
||||
|
||||
|
||||
@ -43,6 +47,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
### Bug Fixes
|
||||
|
||||
* 修改style-setter报错,loadAsyncLib 判断 ([7fe793a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7fe793a))
|
||||
* 优化选中页面根节点时, 直接点击组件面板插入位置 ([c1ca2c6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c1ca2c6))
|
||||
|
||||
|
||||
|
||||
@ -110,6 +115,70 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
### Bug Fixes
|
||||
|
||||
* checkId 需要传递 ([bdff2b1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bdff2b1))
|
||||
* path with / ([2470363](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2470363))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.9-9"></a>
|
||||
## [1.0.9-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-8...v1.0.9-9) (2020-09-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 支持 node.children.onInsert ([f120df5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f120df5))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.9-8"></a>
|
||||
## [1.0.9-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-7...v1.0.9-8) (2020-09-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复修改 勾选框、富文本编辑器、下拉选择 等组件标题报错 ([8ba26ee](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8ba26ee))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.9-7"></a>
|
||||
## [1.0.9-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-5...v1.0.9-7) (2020-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 1. 小程序导航配置 pagePath -> path 2.OneAPIConfig -> oneAPIConfig ([2714285](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2714285))
|
||||
* 修改 renderer 需等待 document 才开始渲染 ([e7cc9bc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e7cc9bc))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.9-6"></a>
|
||||
## [1.0.9-6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-5...v1.0.9-6) (2020-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 1. 小程序导航配置 pagePath -> path 2.OneAPIConfig -> oneAPIConfig ([2714285](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2714285))
|
||||
* 修改 renderer 需等待 document 才开始渲染 ([e7cc9bc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e7cc9bc))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.9-5"></a>
|
||||
## [1.0.9-5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-2...v1.0.9-5) (2020-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 低代码组件丢失代码找回 ([aac8126](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aac8126))
|
||||
* 1.修复 rax 路由问题 2.切换 designMode 重新 setupSelection 3.settingpane add state shouldIgnoreRoot ([890ec76](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/890ec76))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 补充一些 vision API ([933cef1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/933cef1))
|
||||
|
||||
|
||||
|
||||
@ -136,7 +205,150 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 支持 checkId 开关功能, 在 setSchema 时关闭, 避免 id 被不断重置 ([44bdda1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/44bdda1))
|
||||
* props.getNode 防死循环 ([444e25c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/444e25c))
|
||||
* **designer:** fix insertion style ([82c10d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/82c10d2))
|
||||
* fieldId 重复问题 ([e761b1a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e761b1a))
|
||||
* 🐛 add hotkey up/down/left/right ([9c8afe8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9c8afe8))
|
||||
* 🐛 add pollyfill for vision page.getHistory ([0b905d0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b905d0))
|
||||
* 🐛 error when quick search ([801d954](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/801d954))
|
||||
* 🐛 title缺少icon字段,临时转接一下 ([2f9bb25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2f9bb25))
|
||||
* 🐛 增加 getAddonData api ([68b7e29](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/68b7e29))
|
||||
* 🐛 增加剪切快捷键 ([a73a82e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a73a82e))
|
||||
* 🐛 快捷键支持 ([73374dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/73374dd))
|
||||
* 🐛 移动快捷键 ([7c8a27c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7c8a27c))
|
||||
* 1. 修复dialog拖入不显示问题 2. dialog 只能在根节点下 3. 引入 modalNodeManager ([65977e7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/65977e7))
|
||||
* add extraEnv ([9058ac8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9058ac8))
|
||||
* border action style ([6b91535](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6b91535))
|
||||
* cancel dragging on invalid position ([f961096](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f961096))
|
||||
* canDropIn 为 boolean 时失效 ([7508fb6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7508fb6))
|
||||
* createComponent 支持所有 schema ([7f946f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7f946f5))
|
||||
* currentPage.id 返回 formUuid ([775725d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/775725d))
|
||||
* documentModel toData 方法 ([1ea0d73](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1ea0d73))
|
||||
* export data ([41f7724](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/41f7724))
|
||||
* fieldId 重复 ([5d64312](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5d64312))
|
||||
* force schema ([6d0a8ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d0a8ff))
|
||||
* getSuitablePlace ([03e7c57](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/03e7c57))
|
||||
* handling the undefined variable ([0efe8b4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0efe8b4))
|
||||
* layout tabbar number ([3975571](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3975571))
|
||||
* lc-borders-actions ([56d9f5f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56d9f5f))
|
||||
* modal node locate ([9a72dd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9a72dd7))
|
||||
* modify layout props ([9baba75](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9baba75))
|
||||
* nextId append the id of document ([80a5c93](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/80a5c93))
|
||||
* nextId() 逻辑调整 ([488a5d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/488a5d8))
|
||||
* NodeChildren伪装为Array保证向前兼容 ([7950bf5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7950bf5))
|
||||
* panel visible time ([18ac1fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18ac1fa))
|
||||
* patch prototype ([f20bfaa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f20bfaa))
|
||||
* prop type=UNSET 时返回 undefined ([f437f30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f437f30))
|
||||
* quickSearch error ([a8009ef](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a8009ef))
|
||||
* revert ([dad21e2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dad21e2))
|
||||
* settingField items is empty when type is not 'group' ([582c41a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/582c41a))
|
||||
* settingfield添加props修复地区组件切换类型报错 ([88348f7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/88348f7))
|
||||
* slot 兼容问题 + loop key bug fix ([bc64017](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bc64017))
|
||||
* support dropObject is data ([809fda7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/809fda7))
|
||||
* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c))
|
||||
* try get settingfield ([56f242f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56f242f))
|
||||
* uniqueid ([8db52f0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8db52f0))
|
||||
* updateProps before init ([760e6a6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/760e6a6))
|
||||
* vc-filter bug fix ([31ea5d5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/31ea5d5))
|
||||
* VC-Filter组件的适配问题 ([1f581b8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f581b8))
|
||||
* 不对外暴露 Node ([05957ce](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05957ce))
|
||||
* 不应该限定 parent 才做解绑操作 ([2e616e3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e616e3))
|
||||
* 优化simulator样式 ([25ba893](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/25ba893))
|
||||
* 优化树子节点删除逻辑 ([47e814f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/47e814f))
|
||||
* 优化画布中点击事件屏蔽,增加富文本组件的部分屏蔽 ([a5b6557](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5b6557))
|
||||
* 优化画布中点击事件屏蔽,增加富文本组件的部分屏蔽 ([ec08c6c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ec08c6c))
|
||||
* 低代码组件 props 显示 object 问题 ([116498e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/116498e))
|
||||
* 低代码组件修改之后渲染为空 ([ef71632](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ef71632))
|
||||
* 修复 preset-vision 版本 lifeCycles 丢失以及 slot 初始化问题 ([7cf6d24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7cf6d24))
|
||||
* 修复 toolbar 弹出位置异常 ([b40b9a4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40b9a4))
|
||||
* 修复js面板引用计数问题 ([fcc1a6f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fcc1a6f))
|
||||
* 修复低代码组件设计器、区块设计器根节点为 Page 的问题,修复 topArea 样式 ([e85b542](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e85b542))
|
||||
* 修复取不到值的情况 ([5e7e488](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5e7e488))
|
||||
* 修复在切换页面时,没有销毁相应节点导致的一系列bug ([59fac25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/59fac25))
|
||||
* 修复导入的组件拖入画布报错 ([caf9915](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/caf9915))
|
||||
* 修复获取 currentPage 的逻辑 ([d8221db](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d8221db))
|
||||
* 修改移动端设备宽度 ([cd7b1e6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cd7b1e6))
|
||||
* 兼容modal模式 ([1092ee9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1092ee9))
|
||||
* 初始就create 所有documentInstance, 否则路由跳转有问题 ([fdd6978](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fdd6978))
|
||||
* 区块组件无法删除 ([d936d2b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d936d2b))
|
||||
* 卡片内容不可用拖动 ([6a85c43](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6a85c43))
|
||||
* 去掉根据 componentName 判断 isModal 的逻辑 ([28f0213](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/28f0213))
|
||||
* 可以降级到历史的 JSBlock 格式 ([af1746b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/af1746b))
|
||||
* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad))
|
||||
* 在 renderer 层面做 function component 包装,避免影响 rax 等其他场景 ([1f920dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f920dd))
|
||||
* 在Transducer中添加对MixedSetter的支持 ([7317f2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7317f2f))
|
||||
* 在设计器里,所有组件都需要展示,不管 condition 为何值 ([0e7e038](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e7e038))
|
||||
* 增加 getNode 兼容接口 ([5b6792f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5b6792f))
|
||||
* 增加兼容 API ([2960446](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2960446))
|
||||
* 处理 function component 无法选中的问题,本质上是没有 ref ([fa94aab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fa94aab))
|
||||
* 处理 schema id 重复的问题 ([d2316be](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d2316be))
|
||||
* 处理选区的 toolkit 位置不对的 bug ([bfc63db](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bfc63db))
|
||||
* 复制之后 fieldId 重复 ([36621ea](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/36621ea))
|
||||
* 实现 removeDocument ([c07b447](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c07b447))
|
||||
* 快捷键增加判断 ([0f64829](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0f64829))
|
||||
* 快捷键增加判断 ([e18a231](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e18a231))
|
||||
* 拖拽时要解除与原来节点的关系 ([7a6bf2c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7a6bf2c))
|
||||
* 支持事件 VE_EVENTS.VE_PAGE_PAGE_READY ([935ffad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/935ffad))
|
||||
* 支持页面回滚 ([5d7dc2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5d7dc2f))
|
||||
* 暂时使用 live 模式作为条件判断是否限制选中 Page 组件 ([0bab030](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0bab030))
|
||||
* 更改生成 id 的规则, 否则命中 recore 解析 id 的一个限制 ([5adff44](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5adff44))
|
||||
* 根据目标元素的canDropIn函数判断是否能放入其他元素 ([21d4f64](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/21d4f64))
|
||||
* 灵犀vc组件中调用config, 补充进去 ([7171aa2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7171aa2))
|
||||
* 画布BorderAction埋点数据 ([d813b50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d813b50))
|
||||
* 禁止组件拉到 Page 的直接子节点, 以及替换 tab 组件 ([d93a291](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d93a291))
|
||||
* 简化 onPageReady 实现逻辑 ([a36e5f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a36e5f2))
|
||||
* **designer/node.ts:** fix hasLoop logic ([99a7288](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99a7288))
|
||||
* 组件缺失占位 ([aff2f34](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aff2f34))
|
||||
* 补充documnet-model中addonData 相关方法 ([cbc70ea](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cbc70ea))
|
||||
* 解决 set('schema') 后 componentsTree 越来越多的 bug ([a171d3e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a171d3e))
|
||||
* 调整 upgrade 和 init 的流程 ([09fc1a0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/09fc1a0))
|
||||
* 调整visionNode修改未知 ([da59235](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/da59235))
|
||||
* 页面加载之后就被标记位 isModified ([2840d27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2840d27))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c286))
|
||||
* 🎸 polyfill exchange ([286e7d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/286e7d8))
|
||||
* 🎸 polyfill exchange ([7070557](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7070557))
|
||||
* 🎸 增加icon获取api ([f1a0823](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f1a0823))
|
||||
* 🎸 支持设置模拟器的 viewport 的宽高和缩放级别 ([3a54241](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3a54241))
|
||||
* add ? to component designer/src/designer/setting/utils.js ([0025e3c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0025e3c))
|
||||
* add alias for get index ([e853a4f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e853a4f))
|
||||
* add resize box ([14a55ae](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/14a55ae))
|
||||
* bord resizing ([361f4f6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/361f4f6))
|
||||
* border resizing ([c7fc28c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c7fc28c))
|
||||
* change reducer stage ([c2e83c7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c2e83c7))
|
||||
* complete live-editing expr & i18n ([3ac08ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3ac08ba))
|
||||
* duplicate ([ec932aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ec932aa))
|
||||
* Exchange ([ef5a72e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ef5a72e))
|
||||
* extend deviceClassName ([0e96074](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e96074))
|
||||
* for box resizing ([cb2854d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cb2854d))
|
||||
* for box resizing ([77e325f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/77e325f))
|
||||
* get SettingField instead of SettingPropEntry for compatibility ([2787a12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2787a12))
|
||||
* history log ([fbb3577](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fbb3577))
|
||||
* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e66168))
|
||||
* iphonex 样式 ([e3637b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e3637b0))
|
||||
* iphonex 样式 ([08d7875](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/08d7875))
|
||||
* merge live mode ([92c3039](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/92c3039))
|
||||
* rax simulator ([05b262d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05b262d))
|
||||
* run vision polyfill ([33750b7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/33750b7))
|
||||
* show value state ([bd49e50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd49e50))
|
||||
* support global inline editing ([4f7179b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4f7179b))
|
||||
* support plaintext liveediting ([ea62f12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ea62f12))
|
||||
* support prop.autorun ([c0a5235](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c0a5235))
|
||||
* support subtreeModified ([7eeb51c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7eeb51c))
|
||||
* ve事件埋点 ([700e5b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/700e5b0))
|
||||
* **designer:** add blank page logic ([aeeb9ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aeeb9ba))
|
||||
* **designer:** add builtin hotkeys ([2ec5883](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2ec5883))
|
||||
* 修复状态切换失效 ([2e3f60d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e3f60d))
|
||||
* 增加 node replaceWith 方法 ([d44f95b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d44f95b))
|
||||
* 增加miniapp外壳 ([bccce8c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bccce8c))
|
||||
* 大纲树支持模态视图 ([3785e1c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3785e1c))
|
||||
* 导出的schema增加componentsMap ([dbc958c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dbc958c))
|
||||
* 引擎层埋点 ([69de533](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/69de533))
|
||||
* 新增simulatorurl,可以设置cdn使用simulator ([1f45b05](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f45b05))
|
||||
* 新增用于小程序跳过 variable 检测设置 hotvalue 的方法 ([ef799eb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ef799eb))
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"build-plugin-component"
|
||||
]
|
||||
"build-plugin-component"
|
||||
]
|
||||
}
|
||||
|
||||
6
packages/designer/build.test.json
Normal file
6
packages/designer/build.test.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
"build-plugin-component",
|
||||
"@ali/lowcode-test-mate/plugin/index.ts"
|
||||
]
|
||||
}
|
||||
24
packages/designer/jest.config.js
Normal file
24
packages/designer/jest.config.js
Normal file
@ -0,0 +1,24 @@
|
||||
const esModules = ['@recore/obx-react'].join('|');
|
||||
|
||||
module.exports = {
|
||||
// transform: {
|
||||
// '^.+\\.[jt]sx?$': 'babel-jest',
|
||||
// // '^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
// // '^.+\\.(js|jsx)$': 'babel-jest',
|
||||
// },
|
||||
// testMatch: ['**/project.test.ts'],
|
||||
testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
|
||||
transformIgnorePatterns: [
|
||||
`/node_modules/(?!${esModules})/`,
|
||||
],
|
||||
setupFiles: ['./tests/fixtures/unhandled-rejection.ts'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
collectCoverage: false,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,tsx}',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/icons',
|
||||
'!**/node_modules/**',
|
||||
'!**/vendor/**',
|
||||
],
|
||||
};
|
||||
@ -10,8 +10,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "build-scripts build --skip-demo",
|
||||
"test": "ava",
|
||||
"test:snapshot": "ava --update-snapshots"
|
||||
"test": "build-scripts test --config build.test.json"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -24,23 +23,19 @@
|
||||
"react-dom": "^16.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alib/build-scripts": "^0.1.18",
|
||||
"@ali/lowcode-test-mate": "^1.0.1",
|
||||
"@alib/build-scripts": "^0.1.29",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/medium-editor": "^5.0.3",
|
||||
"@types/node": "^13.7.1",
|
||||
"@types/react": "^16",
|
||||
"@types/react-dom": "^16",
|
||||
"build-plugin-component": "^0.2.10"
|
||||
},
|
||||
"ava": {
|
||||
"compileEnhancements": false,
|
||||
"snapshotDir": "test/fixtures/__snapshots__",
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
]
|
||||
"babel-jest": "^26.5.2",
|
||||
"build-plugin-component": "^0.2.10",
|
||||
"build-scripts-config": "^0.1.8",
|
||||
"jest": "^26.5.2",
|
||||
"lodash": "^4.17.20",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npm.alibaba-inc.com"
|
||||
|
||||
@ -57,9 +57,13 @@ export class BorderDetecting extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
|
||||
@computed get current() {
|
||||
const { host } = this.props;
|
||||
const doc = host.document;
|
||||
const doc = host.currentDocument;
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
const { selection } = doc;
|
||||
const { current } = host.designer.detecting;
|
||||
|
||||
if (!current || current.document !== doc || selection.has(current.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Component, Fragment } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import DragResizeEngine from './drag-resize-engine';
|
||||
import { observer, computed, globalContext, Editor } from '@ali/lowcode-editor-core';
|
||||
import classNames from 'classnames';
|
||||
import { SimulatorContext } from '../context';
|
||||
import { BuiltinSimulatorHost } from '../host';
|
||||
import { OffsetObserver, Designer } from '../../designer';
|
||||
import { Node } from '../../document';
|
||||
|
||||
@observer
|
||||
export default class BoxResizing extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
@ -19,8 +20,8 @@ export default class BoxResizing extends Component<{ host: BuiltinSimulatorHost
|
||||
}
|
||||
|
||||
@computed get selecting() {
|
||||
const doc = this.host.document;
|
||||
if (doc.suspensed) {
|
||||
const doc = this.host.currentDocument;
|
||||
if (!doc || doc.suspensed) {
|
||||
return null;
|
||||
}
|
||||
const { selection } = doc;
|
||||
@ -149,9 +150,9 @@ export class BoxResizingInstance extends Component<{
|
||||
metaData.experimental.callbacks &&
|
||||
typeof metaData.experimental.callbacks.onResize === 'function'
|
||||
) {
|
||||
e.trigger = direction;
|
||||
e.deltaX = moveX;
|
||||
e.deltaY = moveY;
|
||||
(e as any).trigger = direction;
|
||||
(e as any).deltaX = moveX;
|
||||
(e as any).deltaY = moveY;
|
||||
metaData.experimental.callbacks.onResize(e, node);
|
||||
}
|
||||
};
|
||||
@ -164,7 +165,7 @@ export class BoxResizingInstance extends Component<{
|
||||
metaData.experimental.callbacks &&
|
||||
typeof metaData.experimental.callbacks.onResizeStart === 'function'
|
||||
) {
|
||||
e.trigger = direction;
|
||||
(e as any).trigger = direction;
|
||||
metaData.experimental.callbacks.onResizeStart(e, node);
|
||||
}
|
||||
};
|
||||
@ -177,7 +178,7 @@ export class BoxResizingInstance extends Component<{
|
||||
metaData.experimental.callbacks &&
|
||||
typeof metaData.experimental.callbacks.onResizeEnd === 'function'
|
||||
) {
|
||||
e.trigger = direction;
|
||||
(e as any).trigger = direction;
|
||||
metaData.experimental.callbacks.onResizeEnd(e, node);
|
||||
}
|
||||
|
||||
|
||||
@ -203,8 +203,8 @@ export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
}
|
||||
|
||||
@computed get selecting() {
|
||||
const doc = this.host.document;
|
||||
if (doc.suspensed || this.host.liveEditing.editing) {
|
||||
const doc = this.host.currentDocument;
|
||||
if (!doc || doc.suspensed || this.host.liveEditing.editing) {
|
||||
return null;
|
||||
}
|
||||
const { selection } = doc;
|
||||
|
||||
@ -38,6 +38,8 @@ export default class DragResizeEngine {
|
||||
|
||||
private dragResizing = false;
|
||||
|
||||
private designer: Designer;
|
||||
|
||||
constructor(designer: Designer) {
|
||||
this.designer = designer;
|
||||
this.emitter = new EventEmitter();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { observer } from '@ali/lowcode-editor-core';
|
||||
import { BorderDetecting } from './border-detecting';
|
||||
import { BuiltinSimulatorHost } from '../host';
|
||||
@ -16,7 +16,11 @@ export class BemTools extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
|
||||
render() {
|
||||
const { host } = this.props;
|
||||
const { designMode } = host;
|
||||
const { scrollX, scrollY, scale } = host.viewport;
|
||||
if (designMode === 'live') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="lc-bem-tools" style={{ transform: `translate(${-scrollX * scale}px,${-scrollY * scale}px)` }}>
|
||||
<BorderDetecting key="hovering" host={host} />
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { ISimulatorHost } from '../../simulator';
|
||||
import { ParentalNode } from '../../document';
|
||||
import './insertion.less';
|
||||
import { NodeData, NodeSchema } from '@ali/lowcode-types';
|
||||
|
||||
interface InsertionData {
|
||||
edge?: DOMRect;
|
||||
@ -18,6 +19,7 @@ interface InsertionData {
|
||||
vertical?: boolean;
|
||||
nearRect?: Rect;
|
||||
coverRect?: DOMRect;
|
||||
nearNode?: NodeData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,6 +43,7 @@ function processChildrenDetail(sim: ISimulatorHost, container: ParentalNode, det
|
||||
if (detail.near) {
|
||||
const { node, pos, rect, align } = detail.near;
|
||||
ret.nearRect = rect || sim.computeRect(node);
|
||||
ret.nearNode = node;
|
||||
if (pos === 'replace') {
|
||||
// FIXME: ret.nearRect mybe null
|
||||
ret.coverRect = ret.nearRect;
|
||||
@ -82,6 +85,7 @@ function processChildrenDetail(sim: ISimulatorHost, container: ParentalNode, det
|
||||
ret.insertType = 'after';
|
||||
}
|
||||
ret.vertical = isVertical(ret.nearRect);
|
||||
ret.nearNode = nearNode;
|
||||
} else {
|
||||
ret.insertType = 'cover';
|
||||
ret.coverRect = edge;
|
||||
@ -118,13 +122,17 @@ export class InsertionView extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
|
||||
render() {
|
||||
const { host } = this.props;
|
||||
const loc = host.document.dropLocation;
|
||||
const loc = host.currentDocument?.dropLocation;
|
||||
if (!loc) {
|
||||
return null;
|
||||
}
|
||||
// 如果是个绝对定位容器,不需要渲染插入标记
|
||||
if (loc.target.componentMeta.getMetadata().experimental?.isAbsoluteLayoutContainer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { scale, scrollX, scrollY } = host.viewport;
|
||||
const { edge, insertType, coverRect, nearRect, vertical } = processDetail(loc);
|
||||
const { edge, insertType, coverRect, nearRect, vertical, nearNode } = processDetail(loc);
|
||||
|
||||
if (!edge) {
|
||||
return null;
|
||||
@ -157,8 +165,12 @@ export class InsertionView extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
y = ((insertType === 'before' ? nearRect.top : nearRect.bottom) + scrollY) * scale;
|
||||
style.width = nearRect.width * scale;
|
||||
}
|
||||
if (y === 0 && (nearNode as NodeSchema)?.componentMeta?.isTopFixed) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
||||
style.transition = 'all 0.2s ease-in-out';
|
||||
|
||||
return <div className={className} style={style} />;
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { observer } from '@ali/lowcode-editor-core';
|
||||
import { BuiltinSimulatorHost, BuiltinSimulatorProps } from './host';
|
||||
import { DocumentModel } from '../document';
|
||||
import { BemTools } from './bem-tools';
|
||||
import { Project } from '../project';
|
||||
import './host.less';
|
||||
|
||||
/*
|
||||
@ -10,12 +10,11 @@ import './host.less';
|
||||
Canvas(DeviceShell) 设备壳层,通过背景图片来模拟,通过设备预设样式改变宽度、高度及定位 CanvasViewport
|
||||
CanvasViewport 页面编排场景中宽高不可溢出 Canvas 区
|
||||
Content(Shell) 内容外层,宽高紧贴 CanvasViewport,禁用边框,禁用 margin
|
||||
ContentFrame 可设置宽高,在页面场景一般只设置框,高度拉伸贴合 Content
|
||||
Auxiliary 辅助显示层,初始相对 Content 位置 0,0,紧贴 Canvas, 根据 Content 滚动位置,改变相对位置
|
||||
BemTools 辅助显示层,初始相对 Content 位置 0,0,紧贴 Canvas, 根据 Content 滚动位置,改变相对位置
|
||||
*/
|
||||
|
||||
type SimulatorHostProps = BuiltinSimulatorProps & {
|
||||
documentContext: DocumentModel;
|
||||
project: Project;
|
||||
onMount?: (host: BuiltinSimulatorHost) => void;
|
||||
};
|
||||
|
||||
@ -24,8 +23,8 @@ export class BuiltinSimulatorHostView extends Component<SimulatorHostProps> {
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const { documentContext } = this.props;
|
||||
this.host = (documentContext.simulator as BuiltinSimulatorHost) || new BuiltinSimulatorHost(documentContext);
|
||||
const { project } = this.props;
|
||||
this.host = (project.simulator as BuiltinSimulatorHost) || new BuiltinSimulatorHost(project);
|
||||
this.host.setProps(this.props);
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,27 @@
|
||||
box-shadow: 0 2px 10px 0 rgba(31,56,88,.15);
|
||||
}
|
||||
|
||||
&-device-iphonex { // 增加默认的小程序的壳
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 378px;
|
||||
height: 812px;
|
||||
max-height: calc(100vh - 50px);
|
||||
background: url(https://img.alicdn.com/tfs/TB1b4DHilFR4u4jSZFPXXanzFXa-750-1574.png) no-repeat top;
|
||||
background-size: 378px 812px;
|
||||
border-radius: 44px;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 36px 42px;
|
||||
.@{scope}-canvas-viewport {
|
||||
width: auto;
|
||||
top: 50px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
margin-top: 40px;
|
||||
max-height: 688px;
|
||||
}
|
||||
}
|
||||
|
||||
&-device-iphone6 {
|
||||
left: 50%;
|
||||
width: 378px;
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { obx, autorun, computed, getPublicPath, hotkey, focusTracker } from '@ali/lowcode-editor-core';
|
||||
import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator';
|
||||
import { ISimulatorHost, Component, NodeInstance, ComponentInstance, DropContainer } from '../simulator';
|
||||
import Viewport from './viewport';
|
||||
import { createSimulator } from './create-simulator';
|
||||
import { Node, ParentalNode, DocumentModel, isNode, contains, isRootNode } from '../document';
|
||||
import { Node, ParentalNode, isNode, contains, isRootNode } from '../document';
|
||||
import ResourceConsumer from './resource-consumer';
|
||||
import {
|
||||
AssetLevel,
|
||||
@ -13,6 +13,7 @@ import {
|
||||
AssetType,
|
||||
isElement,
|
||||
isFormEvent,
|
||||
hasOwnProperty,
|
||||
} from '@ali/lowcode-utils';
|
||||
import {
|
||||
DragObjectType,
|
||||
@ -29,12 +30,15 @@ import {
|
||||
getRectTarget,
|
||||
Rect,
|
||||
CanvasPoint,
|
||||
Designer,
|
||||
} from '../designer';
|
||||
import { parseMetadata } from './utils/parse-metadata';
|
||||
import { ComponentMetadata, NodeSchema } from '@ali/lowcode-types';
|
||||
import { ComponentMetadata, ComponentSchema } from '@ali/lowcode-types';
|
||||
import { BuiltinSimulatorRenderer } from './renderer';
|
||||
import clipboard from '../designer/clipboard';
|
||||
import { LiveEditing } from './live-editing/live-editing';
|
||||
import { Project } from '../project';
|
||||
import { Scroller } from '../designer/scroller';
|
||||
|
||||
export interface LibraryItem {
|
||||
package: string;
|
||||
@ -113,9 +117,23 @@ const defaultRaxEnvironment = [
|
||||
export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProps> {
|
||||
readonly isSimulator = true;
|
||||
|
||||
constructor(readonly document: DocumentModel) {}
|
||||
readonly project: Project;
|
||||
|
||||
readonly designer = this.document.designer;
|
||||
readonly designer: Designer;
|
||||
|
||||
readonly viewport = new Viewport();
|
||||
|
||||
readonly scroller: Scroller;
|
||||
|
||||
constructor(project: Project) {
|
||||
this.project = project;
|
||||
this.designer = project?.designer;
|
||||
this.scroller = this.designer.createScroller(this.viewport);
|
||||
}
|
||||
|
||||
get currentDocument() {
|
||||
return this.project.currentDocument;
|
||||
}
|
||||
|
||||
@computed get renderEnv(): string {
|
||||
return this.get('renderEnv') || 'default';
|
||||
@ -182,6 +200,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
return autorun(fn as any, true);
|
||||
}
|
||||
|
||||
autorun(fn: (context: { dispose: () => void; firstRun: boolean }) => void) {
|
||||
return autorun(fn as any, true);
|
||||
}
|
||||
|
||||
purge(): void {
|
||||
// todo
|
||||
}
|
||||
@ -313,9 +335,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
}
|
||||
|
||||
setupDragAndClick() {
|
||||
const documentModel = this.document;
|
||||
const { selection } = documentModel;
|
||||
const { designer } = documentModel;
|
||||
const designer = this.designer;
|
||||
const doc = this.contentDocument!;
|
||||
|
||||
// TODO: think of lock when edit a node
|
||||
@ -325,7 +345,15 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
(downEvent: MouseEvent) => {
|
||||
// fix for popups close logic
|
||||
document.dispatchEvent(new Event('mousedown'));
|
||||
if (this.liveEditing.editing) {
|
||||
const documentModel = this.project.currentDocument;
|
||||
if (this.liveEditing.editing || !documentModel) {
|
||||
return;
|
||||
}
|
||||
const selection = documentModel.selection;
|
||||
let isMulti = false;
|
||||
if (this.designMode === 'design') {
|
||||
isMulti = downEvent.metaKey || downEvent.ctrlKey;
|
||||
} else if (!downEvent.metaKey) {
|
||||
return;
|
||||
}
|
||||
// stop response document focus event
|
||||
@ -336,23 +364,32 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
(downEvent.target as HTMLElement).removeAttribute('for');
|
||||
|
||||
const nodeInst = this.getNodeInstanceFromElement(downEvent.target as Element);
|
||||
const node = nodeInst?.node || this.document.rootNode;
|
||||
if (!node?.isValidComponent()) {
|
||||
// 对于未注册组件直接返回
|
||||
return;
|
||||
}
|
||||
|
||||
const isMulti = downEvent.metaKey || downEvent.ctrlKey;
|
||||
const node = nodeInst?.node || documentModel?.rootNode;
|
||||
// if (!node?.isValidComponent()) {
|
||||
// // 对于未注册组件直接返回
|
||||
// return;
|
||||
// }
|
||||
const isLeftButton = downEvent.which === 1 || downEvent.button === 0;
|
||||
const checkSelect = (e: MouseEvent) => {
|
||||
doc.removeEventListener('mouseup', checkSelect, true);
|
||||
// 鼠标是否移动
|
||||
if (!isShaken(downEvent, e)) {
|
||||
const { id } = node;
|
||||
let id = node.id;
|
||||
designer.activeTracker.track({ node, instance: nodeInst?.instance });
|
||||
if (isMulti && !isRootNode(node) && selection.has(id)) {
|
||||
selection.remove(id);
|
||||
} else {
|
||||
// TODO: 避免选中 Page 组件,默认选中第一个子节点;新增规则 或 判断 Live 模式
|
||||
if (node.isPage() && node.getChildren()?.notEmpty() && this.designMode === 'live') {
|
||||
const firstChildId = node
|
||||
.getChildren()
|
||||
?.get(0)
|
||||
?.getId();
|
||||
if (firstChildId) id = firstChildId;
|
||||
}
|
||||
selection.select(id);
|
||||
|
||||
// dirty code should refector
|
||||
const editor = this.designer?.editor;
|
||||
const npm = node?.componentMeta?.npm;
|
||||
const selected =
|
||||
@ -376,7 +413,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
selection.add(node.id);
|
||||
ignoreUpSelected = true;
|
||||
}
|
||||
selection.remove(this.document.rootNode.id);
|
||||
selection.remove(documentModel.rootNode.id);
|
||||
// 获得顶层 nodes
|
||||
nodes = selection.getTopNodes();
|
||||
} else if (selection.containsNode(node, true)) {
|
||||
@ -447,21 +484,22 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
private disableHovering?: () => void;
|
||||
|
||||
private disableDetecting?: () => void;
|
||||
/**
|
||||
* 设置悬停处理
|
||||
*/
|
||||
setupDetecting() {
|
||||
const doc = this.contentDocument!;
|
||||
const { detecting } = this.document.designer;
|
||||
const detecting = this.designer.detecting;
|
||||
const hover = (e: MouseEvent) => {
|
||||
if (!detecting.enable) {
|
||||
if (!detecting.enable || this.designMode !== 'design') {
|
||||
return;
|
||||
}
|
||||
const nodeInst = this.getNodeInstanceFromElement(e.target as Element);
|
||||
detecting.capture(nodeInst?.node || null);
|
||||
e.stopPropagation();
|
||||
};
|
||||
const leave = () => detecting.leave(this.document);
|
||||
const leave = () => detecting.leave(this.project.currentDocument);
|
||||
|
||||
doc.addEventListener('mouseover', hover, true);
|
||||
doc.addEventListener('mouseleave', leave, false);
|
||||
@ -475,12 +513,12 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
true,
|
||||
);
|
||||
|
||||
this.disableHovering = () => {
|
||||
detecting.leave(this.document);
|
||||
doc.removeEventListener('mouseover', hover, true);
|
||||
doc.removeEventListener('mouseleave', leave, false);
|
||||
this.disableHovering = undefined;
|
||||
};
|
||||
// this.disableDetecting = () => {
|
||||
// detecting.leave(this.project.currentDocument);
|
||||
// doc.removeEventListener('mouseover', hover, true);
|
||||
// doc.removeEventListener('mouseleave', leave, false);
|
||||
// this.disableDetecting = undefined;
|
||||
// };
|
||||
}
|
||||
|
||||
readonly liveEditing = new LiveEditing();
|
||||
@ -500,7 +538,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
if (!nodeInst) {
|
||||
return;
|
||||
}
|
||||
const node = nodeInst.node || this.document.rootNode;
|
||||
const node = nodeInst.node || this.project.currentDocument?.rootNode;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootElement = this.findDOMNodes(nodeInst.instance, node.componentMeta.rootSelector)?.find(
|
||||
(item) =>
|
||||
@ -525,15 +566,22 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
* @see ISimulator
|
||||
*/
|
||||
setSuspense(suspended: boolean) {
|
||||
if (suspended) {
|
||||
if (this.disableHovering) {
|
||||
this.disableHovering();
|
||||
}
|
||||
// sleep some autorun reaction
|
||||
} else if (!this.disableHovering) {
|
||||
// weekup some autorun reaction
|
||||
this.setupDetecting();
|
||||
}
|
||||
return false;
|
||||
// if (suspended) {
|
||||
// /*
|
||||
// if (this.disableDetecting) {
|
||||
// this.disableDetecting();
|
||||
// }
|
||||
// */
|
||||
// // sleep some autorun reaction
|
||||
// } else {
|
||||
// // weekup some autorun reaction
|
||||
// /*
|
||||
// if (!this.disableDetecting) {
|
||||
// this.setupDetecting();
|
||||
// }
|
||||
// */
|
||||
// }
|
||||
}
|
||||
|
||||
setupContextMenu() {
|
||||
@ -544,10 +592,12 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
if (!nodeInst) {
|
||||
return;
|
||||
}
|
||||
const node = nodeInst.node || this.document.rootNode;
|
||||
const node = nodeInst.node || this.project.currentDocument?.rootNode;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// dirty code should refector
|
||||
const editor = this.designer?.editor;
|
||||
const npm = node?.componentMeta?.npm;
|
||||
const selected =
|
||||
@ -597,32 +647,40 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
return this.renderer?.getComponent(componentName) || null;
|
||||
}
|
||||
|
||||
createComponent(schema: NodeSchema): Component | null {
|
||||
return this.renderer?.createComponent(schema) || null;
|
||||
createComponent(schema: ComponentSchema): Component | null {
|
||||
return null;
|
||||
// return this.renderer?.createComponent(schema) || null;
|
||||
}
|
||||
|
||||
@obx.val private instancesMap = new Map<string, ComponentInstance[]>();
|
||||
|
||||
setInstance(id: string, instances: ComponentInstance[] | null) {
|
||||
@obx private instancesMap: {
|
||||
[docId: string]: Map<string, ComponentInstance[]>;
|
||||
} = {};
|
||||
setInstance(docId: string, id: string, instances: ComponentInstance[] | null) {
|
||||
if (!hasOwnProperty(this.instancesMap, docId)) {
|
||||
this.instancesMap[docId] = new Map();
|
||||
}
|
||||
if (instances == null) {
|
||||
this.instancesMap.delete(id);
|
||||
this.instancesMap[docId].delete(id);
|
||||
} else {
|
||||
this.instancesMap.set(id, instances.slice());
|
||||
this.instancesMap[docId].set(id, instances.slice());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ISimulator
|
||||
*/
|
||||
getComponentInstances(node: Node): ComponentInstance[] | null {
|
||||
return this.instancesMap.get(node.id) || null;
|
||||
}
|
||||
getComponentInstances(node: Node, context?: NodeInstance): ComponentInstance[] | null {
|
||||
const docId = node.document.id;
|
||||
|
||||
/**
|
||||
* @see ISimulator
|
||||
*/
|
||||
getComponentInstanceId(/* instance: ComponentInstance */) {
|
||||
throw new Error('Method not implemented.');
|
||||
let instances = this.instancesMap[docId]?.get(node.id) || null;
|
||||
if (!instances || !context) {
|
||||
return instances;
|
||||
}
|
||||
|
||||
// filter with context
|
||||
return instances.filter((instance) => {
|
||||
return this.getClosestNodeInstance(instance, context.nodeId)?.instance === context.instance
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -747,7 +805,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
if (!nodeIntance) {
|
||||
return null;
|
||||
}
|
||||
const node = this.document.getNode(nodeIntance.nodeId);
|
||||
const { docId } = nodeIntance;
|
||||
const doc = this.project.getDocument(docId)!;
|
||||
const node = doc.getNode(nodeIntance.nodeId);
|
||||
return {
|
||||
...nodeIntance,
|
||||
node,
|
||||
@ -875,9 +935,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
e.target = this.contentDocument?.elementFromPoint(e.canvasX!, e.canvasY!);
|
||||
}
|
||||
|
||||
// documentModel : 目标文档
|
||||
e.documentModel = this.document;
|
||||
|
||||
// 事件已订正
|
||||
e.fixed = true;
|
||||
return e;
|
||||
@ -923,6 +980,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
this.sensing = true;
|
||||
this.scroller.scrolling(e);
|
||||
const document = this.project.currentDocument;
|
||||
if (!document) {
|
||||
return null;
|
||||
}
|
||||
const dropContainer = this.getDropContainer(e);
|
||||
const canDropIn = dropContainer?.container?.componentMeta?.prototype?.options?.canDropIn;
|
||||
|
||||
@ -956,27 +1017,22 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
};
|
||||
|
||||
const locationData = {
|
||||
target: container,
|
||||
target: container as ParentalNode,
|
||||
detail,
|
||||
source: `simulator${ this.document.id}`,
|
||||
source: 'simulator' + document.id,
|
||||
event: e,
|
||||
};
|
||||
|
||||
// if (e.dragObject.type === 'node' && e.dragObject.nodes[0]?.getPrototype()?.isModal()) {
|
||||
if (
|
||||
e.dragObject &&
|
||||
e.dragObject.nodes &&
|
||||
e.dragObject.nodes.length &&
|
||||
e.dragObject.nodes[0].getPrototype()?.isModal()
|
||||
e.dragObject.nodes[0].componentMeta.isModal
|
||||
) {
|
||||
return this.designer.createLocation({
|
||||
target: this.document.rootNode,
|
||||
detail: {
|
||||
type: LocationDetailType.Children,
|
||||
index: 0,
|
||||
valid: true,
|
||||
},
|
||||
source: `simulator${ this.document.id}`,
|
||||
target: document.rootNode,
|
||||
detail,
|
||||
source: 'simulator' + document.id,
|
||||
event: e,
|
||||
});
|
||||
}
|
||||
@ -1077,10 +1133,11 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
/**
|
||||
* 查找合适的投放容器
|
||||
*/
|
||||
getDropContainer(e: LocateEvent): DropContainer | LocationData | null {
|
||||
getDropContainer(e: LocateEvent): DropContainer | null {
|
||||
const { target, dragObject } = e;
|
||||
const isAny = isDragAnyObject(dragObject);
|
||||
const { modalNode, currentRoot } = this.document;
|
||||
const document = this.project.currentDocument!;
|
||||
const { currentRoot } = document;
|
||||
let container: Node;
|
||||
let nodeInstance: NodeInstance<ComponentInstance> | undefined;
|
||||
|
||||
@ -1104,11 +1161,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
container = container.parent || currentRoot;
|
||||
}
|
||||
|
||||
// check container if in modalNode layer, if not, use modalNode
|
||||
if (modalNode && !modalNode.contains(container)) {
|
||||
container = modalNode;
|
||||
}
|
||||
|
||||
// TODO: use spec container to accept specialData
|
||||
if (isAny) {
|
||||
// will return locationData
|
||||
@ -1116,7 +1168,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
}
|
||||
|
||||
// get common parent, avoid drop container contains by dragObject
|
||||
// TODO: renderengine support pointerEvents: none for acceleration
|
||||
const drillDownExcludes = new Set<Node>();
|
||||
if (isDragNodeObject(dragObject)) {
|
||||
const { nodes } = dragObject;
|
||||
@ -1128,63 +1179,70 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
}
|
||||
}
|
||||
if (p !== container) {
|
||||
container = p || this.document.rootNode;
|
||||
container = p || document.rootNode;
|
||||
drillDownExcludes.add(container);
|
||||
}
|
||||
}
|
||||
|
||||
const ret: any = {
|
||||
container,
|
||||
};
|
||||
let instance: any;
|
||||
if (nodeInstance) {
|
||||
if (nodeInstance.node === container) {
|
||||
ret.instance = nodeInstance.instance;
|
||||
instance = nodeInstance.instance;
|
||||
} else {
|
||||
ret.instance = this.getClosestNodeInstance(nodeInstance.instance as any, container.id)?.instance;
|
||||
instance = this.getClosestNodeInstance(nodeInstance.instance as any, container.id)?.instance;
|
||||
}
|
||||
} else {
|
||||
ret.instance = this.getComponentInstances(container)?.[0];
|
||||
instance = this.getComponentInstances(container)?.[0];
|
||||
}
|
||||
|
||||
let dropContainer: DropContainer = {
|
||||
container: container as any,
|
||||
instance
|
||||
};
|
||||
|
||||
let res: any;
|
||||
let upward: any;
|
||||
// TODO: complete drill down logic
|
||||
let upward: DropContainer | null = null;
|
||||
while (container) {
|
||||
if (ret.container !== container) {
|
||||
ret.container = container;
|
||||
ret.instance = this.getClosestNodeInstance(ret.instance, container.id)?.instance;
|
||||
}
|
||||
res = this.handleAccept(ret, e);
|
||||
if (isLocationData(res)) {
|
||||
return res;
|
||||
}
|
||||
res = this.handleAccept(dropContainer, e);
|
||||
// if (isLocationData(res)) {
|
||||
// return res;
|
||||
// }
|
||||
if (res === true) {
|
||||
return ret;
|
||||
return dropContainer;
|
||||
}
|
||||
if (!res) {
|
||||
drillDownExcludes.add(container);
|
||||
if (upward) {
|
||||
container = upward;
|
||||
dropContainer = upward;
|
||||
container = dropContainer.container;
|
||||
upward = null;
|
||||
} else if (container.parent) {
|
||||
container = container.parent;
|
||||
instance = this.getClosestNodeInstance(dropContainer.instance, container.id)?.instance;
|
||||
dropContainer = {
|
||||
container: container as ParentalNode,
|
||||
instance
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (isNode(res)) {
|
||||
/* else if (res === DRILL_DOWN) {
|
||||
}/* else if (res === DRILL_DOWN) {
|
||||
if (!upward) {
|
||||
upward = container.parent;
|
||||
container = container.parent;
|
||||
instance = this.getClosestNodeInstance(dropContainer.instance, container.id)?.instance;
|
||||
upward = {
|
||||
container,
|
||||
instance
|
||||
};
|
||||
}
|
||||
container = this.getNearByContainer(container, drillExcludes, e);
|
||||
if (!container) {
|
||||
container = upward;
|
||||
dropContainer = this.getNearByContainer(dropContainer, drillDownExcludes, e);
|
||||
if (!dropContainer) {
|
||||
dropContainer = upward;
|
||||
upward = null;
|
||||
}
|
||||
} */
|
||||
container = res;
|
||||
upward = null;
|
||||
}
|
||||
} else if (isNode(res)) {
|
||||
// TODO:
|
||||
}*/
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -1204,10 +1262,11 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
/**
|
||||
* 控制接受
|
||||
*/
|
||||
handleAccept({ container/* , instance */ }: DropContainer, e: LocateEvent) {
|
||||
handleAccept({ container, instance }: DropContainer, e: LocateEvent): boolean {
|
||||
const { dragObject } = e;
|
||||
const document = this.currentDocument!;
|
||||
if (isRootNode(container)) {
|
||||
return this.document.checkDropTarget(container, dragObject as any);
|
||||
return document.checkDropTarget(container, dragObject as any);
|
||||
}
|
||||
|
||||
const meta = (container as Node).componentMeta;
|
||||
@ -1246,36 +1305,44 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
}
|
||||
|
||||
// check nesting
|
||||
return this.document.checkNesting(container, dragObject as any);
|
||||
return document.checkNesting(container, dragObject as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找邻近容器
|
||||
*/
|
||||
getNearByContainer(/* container: ParentalNode, e: LocateEvent */) {
|
||||
/*
|
||||
getNearByContainer({ container, instance }: DropContainer, drillDownExcludes: Set<Node>, e: LocateEvent) {
|
||||
const children = container.children;
|
||||
if (!children || children.length < 1) {
|
||||
const document = this.project.currentDocument!;
|
||||
if (!children || children.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let nearDistance: any = null;
|
||||
let nearBy: any = null;
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
let child: any = children[i];
|
||||
if (!isElementNode(child)) {
|
||||
for (let i = 0, l = children.size; i < l; i++) {
|
||||
let child = children.get(i);
|
||||
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
if (hasConditionFlow(child)) {
|
||||
const bn = child.conditionFlow;
|
||||
if (child.conditionGroup) {
|
||||
const bn = child.conditionGroup;
|
||||
i = bn.index + bn.length - 1;
|
||||
child = bn.visibleNode;
|
||||
}
|
||||
const rect = this.document.computeRect(child);
|
||||
if (!child.isParental() || drillDownExcludes.has(child)) {
|
||||
continue;
|
||||
}
|
||||
// TODO:
|
||||
this.findDOMNodes(instance);
|
||||
this.getComponentInstances(child)
|
||||
const rect = this.computeRect(child);
|
||||
if (!rect) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
if (isPointInRect(e, rect)) {
|
||||
return child;
|
||||
}
|
||||
@ -1284,11 +1351,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
if (nearDistance === null || distance < nearDistance) {
|
||||
nearDistance = distance;
|
||||
nearBy = child;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return nearBy;
|
||||
*/
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
@ -1354,8 +1420,3 @@ function getMatched(elements: Array<Element | Text>, selector: string): Element
|
||||
}
|
||||
return firstQueried;
|
||||
}
|
||||
|
||||
interface DropContainer {
|
||||
container: ParentalNode;
|
||||
instance: ComponentInstance;
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
fill: var(--color-icon-white, @title-bgcolor);
|
||||
}
|
||||
}
|
||||
&-current {
|
||||
.instance-node-selector-current {
|
||||
background: var(--color-brand, @brand-color-1);
|
||||
padding: 0 6px;
|
||||
display: flex;
|
||||
@ -37,14 +37,14 @@
|
||||
color: var(--color-icon-white, @title-bgcolor);
|
||||
}
|
||||
}
|
||||
&-list {
|
||||
.instance-node-selector-list {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
&-node {
|
||||
.instance-node-selector-node {
|
||||
margin: 2px 0;
|
||||
&-content {
|
||||
padding-left: 6px;
|
||||
@ -68,15 +68,15 @@
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.instance-node-selector-current {
|
||||
color: ar(--color-text-reverse, @white-alpha-2);
|
||||
}
|
||||
.instance-node-selector-popup {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: 0.2s all ease-in;
|
||||
&:hover {
|
||||
.instance-node-selector-current {
|
||||
color: ar(--color-text-reverse, @white-alpha-2);
|
||||
}
|
||||
.instance-node-selector-popup {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: 0.2s all ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ export interface BuiltinSimulatorRenderer {
|
||||
readonly isSimulatorRenderer: true;
|
||||
createComponent(schema: NodeSchema): Component | null;
|
||||
getComponent(componentName: string): Component;
|
||||
getComponentInstances(id: string): ComponentInstance[] | null;
|
||||
getClosestNodeInstance(from: ComponentInstance, nodeId?: string): NodeInstance<ComponentInstance> | null;
|
||||
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null;
|
||||
getClientRects(element: Element | Text): DOMRect[];
|
||||
|
||||
@ -54,7 +54,7 @@ const LowcodeTypes: any = {
|
||||
(window as any).PropTypes = LowcodeTypes;
|
||||
(window as any).React.PropTypes = LowcodeTypes;
|
||||
|
||||
// override primitive type chechers
|
||||
// override primitive type checkers
|
||||
primitiveTypes.forEach(type => {
|
||||
const propType = (PropTypes as any)[type];
|
||||
if (!propType) {
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
FieldConfig,
|
||||
} from '@ali/lowcode-types';
|
||||
import { computed } from '@ali/lowcode-editor-core';
|
||||
import { Node, ParentalNode } from './document';
|
||||
import { isNode, Node, ParentalNode } from './document';
|
||||
import { Designer } from './designer';
|
||||
import { intlNode } from './locale';
|
||||
import { IconContainer } from './icons/container';
|
||||
@ -70,6 +70,10 @@ export class ComponentMeta {
|
||||
return this._npm;
|
||||
}
|
||||
|
||||
set npm(_npm) {
|
||||
this._npm = _npm;
|
||||
}
|
||||
|
||||
private _componentName?: string;
|
||||
|
||||
get componentName(): string {
|
||||
@ -113,6 +117,12 @@ export class ComponentMeta {
|
||||
return this._liveTextEditing;
|
||||
}
|
||||
|
||||
private _isTopFixed?: boolean;
|
||||
|
||||
get isTopFixed() {
|
||||
return this._isTopFixed;
|
||||
}
|
||||
|
||||
private parentWhitelist?: NestingFilter | null;
|
||||
|
||||
private childWhitelist?: NestingFilter | null;
|
||||
@ -195,6 +205,12 @@ export class ComponentMeta {
|
||||
collectLiveTextEditing(this.configure);
|
||||
this._liveTextEditing = liveTextEditing.length > 0 ? liveTextEditing : undefined;
|
||||
|
||||
const isTopFiexd = this._transformedMetadata.experimental?.isTopFixed;
|
||||
|
||||
if (isTopFiexd) {
|
||||
this._isTopFixed = isTopFiexd;
|
||||
}
|
||||
|
||||
const { configure = {} } = this._transformedMetadata;
|
||||
this._acceptable = false;
|
||||
|
||||
@ -264,6 +280,9 @@ export class ComponentMeta {
|
||||
checkNestingDown(my: Node, target: Node | NodeSchema) {
|
||||
// 检查父子关系,直接约束型,在画布中拖拽直接掠过目标容器
|
||||
if (this.childWhitelist) {
|
||||
if (!isNode(target)) {
|
||||
target = new Node(my.document, target);
|
||||
}
|
||||
return this.childWhitelist(target, my);
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { hotkey } from '@ali/lowcode-editor-core';
|
||||
import { hotkey, Editor, globalContext } from '@ali/lowcode-editor-core';
|
||||
import { isFormEvent } from '@ali/lowcode-utils';
|
||||
import { focusing } from './focusing';
|
||||
import { insertChildren, TransformStage } from '../document';
|
||||
import clipboard from './clipboard';
|
||||
|
||||
function isInLiveEditing() {
|
||||
if (globalContext.has(Editor)) {
|
||||
return Boolean(globalContext.get(Editor).get('designer')?.project?.simulator?.liveEditing?.editing);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNextForSelect(next: any, head?: any, parent?: any): any {
|
||||
if (next) {
|
||||
if (!head) {
|
||||
@ -66,6 +73,7 @@ function getPrevForSelect(prev: any, head?: any, parent?: any): any {
|
||||
|
||||
// hotkey binding
|
||||
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => {
|
||||
if (isInLiveEditing()) return;
|
||||
// TODO: use focus-tracker
|
||||
const doc = focusing.focusDesigner?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
@ -86,6 +94,7 @@ hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => {
|
||||
|
||||
hotkey.bind('escape', (e: KeyboardEvent) => {
|
||||
// const currentFocus = focusing.current;
|
||||
if (isInLiveEditing()) return;
|
||||
const sel = focusing.focusDesigner?.currentDocument?.selection;
|
||||
if (isFormEvent(e) || !sel) {
|
||||
return;
|
||||
@ -98,6 +107,7 @@ hotkey.bind('escape', (e: KeyboardEvent) => {
|
||||
|
||||
// command + c copy command + x cut
|
||||
hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const doc = focusing.focusDesigner?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
return;
|
||||
@ -133,6 +143,7 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
|
||||
|
||||
// command + v paste
|
||||
hotkey.bind(['command+v', 'ctrl+v'], (e) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const designer = focusing.focusDesigner;
|
||||
const doc = designer?.currentDocument;
|
||||
if (isFormEvent(e) || !designer || !doc) {
|
||||
@ -155,6 +166,7 @@ hotkey.bind(['command+v', 'ctrl+v'], (e) => {
|
||||
|
||||
// command + z undo
|
||||
hotkey.bind(['command+z', 'ctrl+z'], (e) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const his = focusing.focusDesigner?.currentHistory;
|
||||
if (isFormEvent(e) || !his) {
|
||||
return;
|
||||
@ -166,6 +178,7 @@ hotkey.bind(['command+z', 'ctrl+z'], (e) => {
|
||||
|
||||
// command + shift + z redo
|
||||
hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const his = focusing.focusDesigner?.currentHistory;
|
||||
if (isFormEvent(e) || !his) {
|
||||
return;
|
||||
@ -177,6 +190,7 @@ hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e) => {
|
||||
|
||||
// sibling selection
|
||||
hotkey.bind(['left', 'right'], (e, action) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const designer = focusing.focusDesigner;
|
||||
const doc = designer?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
@ -193,6 +207,7 @@ hotkey.bind(['left', 'right'], (e, action) => {
|
||||
});
|
||||
|
||||
hotkey.bind(['up', 'down'], (e, action) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const designer = focusing.focusDesigner;
|
||||
const doc = designer?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
@ -215,6 +230,7 @@ hotkey.bind(['up', 'down'], (e, action) => {
|
||||
});
|
||||
|
||||
hotkey.bind(['option+left', 'option+right'], (e, action) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const designer = focusing.focusDesigner;
|
||||
const doc = designer?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
@ -246,6 +262,7 @@ hotkey.bind(['option+left', 'option+right'], (e, action) => {
|
||||
});
|
||||
|
||||
hotkey.bind(['option+up'], (e) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const designer = focusing.focusDesigner;
|
||||
const doc = designer?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
@ -284,6 +301,7 @@ hotkey.bind(['option+up'], (e) => {
|
||||
});
|
||||
|
||||
hotkey.bind(['option+down'], (e) => {
|
||||
if (isInLiveEditing()) return;
|
||||
const designer = focusing.focusDesigner;
|
||||
const doc = designer?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
CompositeObject,
|
||||
PropsList,
|
||||
isNodeSchema,
|
||||
NodeSchema,
|
||||
} from '@ali/lowcode-types';
|
||||
import { Project } from '../project';
|
||||
import { Node, DocumentModel, insertChildren, isRootNode, ParentalNode, TransformStage } from '../document';
|
||||
@ -171,20 +172,6 @@ export class Designer {
|
||||
node.document.simulator?.scrollToNode(node, detail);
|
||||
});
|
||||
|
||||
let selectionDispose: undefined | (() => void);
|
||||
const setupSelection = () => {
|
||||
if (selectionDispose) {
|
||||
selectionDispose();
|
||||
selectionDispose = undefined;
|
||||
}
|
||||
this.postEvent('selection.change', this.currentSelection);
|
||||
if (this.currentSelection) {
|
||||
const { currentSelection } = this;
|
||||
selectionDispose = currentSelection.onSelectionChange(() => {
|
||||
this.postEvent('selection.change', currentSelection);
|
||||
});
|
||||
}
|
||||
};
|
||||
let historyDispose: undefined | (() => void);
|
||||
const setupHistory = () => {
|
||||
if (historyDispose) {
|
||||
@ -203,17 +190,39 @@ export class Designer {
|
||||
this.postEvent('current-document.change', this.currentDocument);
|
||||
this.postEvent('selection.change', this.currentSelection);
|
||||
this.postEvent('history.change', this.currentHistory);
|
||||
setupSelection();
|
||||
this.setupSelection();
|
||||
setupHistory();
|
||||
});
|
||||
this.postEvent('designer.init', this);
|
||||
setupSelection();
|
||||
this.setupSelection();
|
||||
setupHistory();
|
||||
|
||||
// TODO: 先简单实现,后期通过焦点赋值
|
||||
focusing.focusDesigner = this;
|
||||
}
|
||||
|
||||
setupSelection = () => {
|
||||
let selectionDispose: undefined | (() => void);
|
||||
if (selectionDispose) {
|
||||
selectionDispose();
|
||||
selectionDispose = undefined;
|
||||
}
|
||||
const currentSelection = this.currentSelection;
|
||||
// TODO: 避免选中 Page 组件,默认选中第一个子节点;新增规则 或 判断 Live 模式
|
||||
if (currentSelection && currentSelection.selected.length === 0 && this.simulatorProps?.designMode === 'live') {
|
||||
const rootNodeChildrens = this.currentDocument.getRoot().getChildren().children;
|
||||
if (rootNodeChildrens.length > 0) {
|
||||
currentSelection.select(rootNodeChildrens[0].id);
|
||||
}
|
||||
}
|
||||
this.postEvent('selection.change', currentSelection);
|
||||
if (currentSelection) {
|
||||
selectionDispose = currentSelection.onSelectionChange(() => {
|
||||
this.postEvent('selection.change', currentSelection);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
postEvent(event: string, ...args: any[]) {
|
||||
this.editor.emit(`designer.${event}`, ...args);
|
||||
}
|
||||
@ -286,7 +295,7 @@ export class Designer {
|
||||
/**
|
||||
* 获得合适的插入位置
|
||||
*/
|
||||
getSuitableInsertion(): { target: ParentalNode; index?: number } | null {
|
||||
getSuitableInsertion(insertNode?: Node | NodeSchema): { target: ParentalNode; index?: number } | null {
|
||||
const activedDoc = this.project.currentDocument;
|
||||
if (!activedDoc) {
|
||||
return null;
|
||||
@ -298,7 +307,7 @@ export class Designer {
|
||||
target = activedDoc.rootNode;
|
||||
} else {
|
||||
const node = nodes[0];
|
||||
if (isRootNode(node)) {
|
||||
if (isRootNode(node) || node.componentMeta.isContainer) {
|
||||
target = node;
|
||||
} else {
|
||||
// FIXME!!, parent maybe null
|
||||
@ -306,6 +315,11 @@ export class Designer {
|
||||
index = node.index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (target && insertNode && !target.componentMeta.checkNestingDown(target, insertNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { target, index };
|
||||
}
|
||||
|
||||
@ -322,6 +336,10 @@ export class Designer {
|
||||
}
|
||||
if (props.simulatorProps !== this.props.simulatorProps) {
|
||||
this._simulatorProps = props.simulatorProps;
|
||||
// 重新 setupSelection
|
||||
if (props.simulatorProps?.designMode !== this.props.simulatorProps?.designMode) {
|
||||
this.setupSelection();
|
||||
}
|
||||
}
|
||||
if (props.suspensed !== this.props.suspensed && props.suspensed != null) {
|
||||
this.suspensed = props.suspensed;
|
||||
@ -362,7 +380,7 @@ export class Designer {
|
||||
|
||||
@obx.ref private _simulatorProps?: object | ((document: DocumentModel) => object);
|
||||
|
||||
@computed get simulatorProps(): object | ((document: DocumentModel) => object) {
|
||||
@computed get simulatorProps(): object | ((project: Project) => object) {
|
||||
return this._simulatorProps || {};
|
||||
}
|
||||
|
||||
@ -447,12 +465,12 @@ export class Designer {
|
||||
designer._componentMetasMap.forEach((config, key) => {
|
||||
const metaData = config.getMetadata();
|
||||
if (metaData.devMode === 'lowcode') {
|
||||
maps[key] = this.currentDocument?.simulator?.createComponent(metaData.schema!);
|
||||
maps[key] = metaData.schema;
|
||||
} else {
|
||||
const view = metaData.experimental?.view;
|
||||
if (view) {
|
||||
maps[key] = view;
|
||||
} else if (config.npm) {
|
||||
} else {
|
||||
maps[key] = config.npm;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ export class Detecting {
|
||||
}
|
||||
}
|
||||
|
||||
leave(document: DocumentModel) {
|
||||
leave(document: DocumentModel | undefined) {
|
||||
if (this.current && this.current.document === document) {
|
||||
this._current = null;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { Component } from 'react';
|
||||
import { observer, obx, Title } from '@ali/lowcode-editor-core';
|
||||
import { Designer } from '../designer';
|
||||
import { DragObject, isDragNodeObject, isDragNodeDataObject } from '../dragon';
|
||||
import { isSimulatorHost } from '../../simulator';
|
||||
import './ghost.less';
|
||||
|
||||
type offBinding = () => any;
|
||||
@ -16,6 +17,8 @@ export default class DragGhost extends Component<{ designer: Designer }> {
|
||||
|
||||
@obx.ref private y = 0;
|
||||
|
||||
@obx private isAbsoluteLayoutContainer = false;
|
||||
|
||||
private dragon = this.props.designer.dragon;
|
||||
|
||||
constructor(props: any) {
|
||||
@ -32,6 +35,14 @@ export default class DragGhost extends Component<{ designer: Designer }> {
|
||||
this.dragon.onDrag(e => {
|
||||
this.x = e.globalX;
|
||||
this.y = e.globalY;
|
||||
if (isSimulatorHost(e.sensor)) {
|
||||
const container = e.sensor.getDropContainer(e);
|
||||
if (container.container.componentMeta.getMetadata().experimental?.isAbsoluteLayoutContainer) {
|
||||
this.isAbsoluteLayoutContainer = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.isAbsoluteLayoutContainer = false;
|
||||
}),
|
||||
this.dragon.onDragend(() => {
|
||||
this.dragObject = null;
|
||||
@ -84,6 +95,10 @@ export default class DragGhost extends Component<{ designer: Designer }> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isAbsoluteLayoutContainer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="lc-ghost-group"
|
||||
|
||||
@ -418,7 +418,8 @@ export class Dragon {
|
||||
if (!sourceDocument || sourceDocument === document) {
|
||||
evt.globalX = e.clientX;
|
||||
evt.globalY = e.clientY;
|
||||
} else { // event from simulator sandbox
|
||||
} else {
|
||||
// event from simulator sandbox
|
||||
let srcSim: ISimulatorHost | undefined;
|
||||
const lastSim = lastSensor && isSimulatorHost(lastSensor) ? lastSensor : null;
|
||||
// check source simulator
|
||||
@ -519,22 +520,30 @@ export class Dragon {
|
||||
}
|
||||
|
||||
private getMasterSensors(): ISimulatorHost[] {
|
||||
return this.designer.project.documents
|
||||
.map((doc) => {
|
||||
// TODO: not use actived,
|
||||
if (doc.actived && doc.simulator?.sensorAvailable) {
|
||||
return doc.simulator;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean) as any;
|
||||
return Array.from(
|
||||
new Set(
|
||||
this.designer.project.documents
|
||||
.map((doc) => {
|
||||
// TODO: not use actived,
|
||||
if (doc.actived && doc.simulator?.sensorAvailable) {
|
||||
return doc.simulator;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean) as any,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private getSimulators() {
|
||||
return new Set(this.designer.project.documents.map(doc => doc.simulator));
|
||||
}
|
||||
|
||||
// #region ======== drag and drop helpers ============
|
||||
private setNativeSelection(enableFlag: boolean) {
|
||||
setNativeSelection(enableFlag);
|
||||
this.designer.project.documents.forEach((doc) => {
|
||||
doc.simulator?.setNativeSelection(enableFlag);
|
||||
this.getSimulators().forEach((sim) => {
|
||||
sim?.setNativeSelection(enableFlag);
|
||||
});
|
||||
}
|
||||
|
||||
@ -543,8 +552,8 @@ export class Dragon {
|
||||
*/
|
||||
private setDraggingState(state: boolean) {
|
||||
cursor.setDragging(state);
|
||||
this.designer.project.documents.forEach((doc) => {
|
||||
doc.simulator?.setDraggingState(state);
|
||||
this.getSimulators().forEach((sim) => {
|
||||
sim?.setDraggingState(state);
|
||||
});
|
||||
}
|
||||
|
||||
@ -553,8 +562,8 @@ export class Dragon {
|
||||
*/
|
||||
private setCopyState(state: boolean) {
|
||||
cursor.setCopy(state);
|
||||
this.designer.project.documents.forEach((doc) => {
|
||||
doc.simulator?.setCopyState(state);
|
||||
this.getSimulators().forEach((sim) => {
|
||||
sim?.setCopyState(state);
|
||||
});
|
||||
}
|
||||
|
||||
@ -563,8 +572,8 @@ export class Dragon {
|
||||
*/
|
||||
private clearState() {
|
||||
cursor.release();
|
||||
this.designer.project.documents.forEach((doc) => {
|
||||
doc.simulator?.clearState();
|
||||
this.getSimulators().forEach((sim) => {
|
||||
sim?.clearState();
|
||||
});
|
||||
}
|
||||
// #endregion
|
||||
|
||||
@ -140,6 +140,18 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
|
||||
return this.transducer.toHot(v);
|
||||
}
|
||||
|
||||
setMiniAppDataSourceValue(data: any, options?: any) {
|
||||
this.hotValue = data;
|
||||
const v = this.transducer.toNative(data);
|
||||
this.setValue(v, false, false, options);
|
||||
// dirty fix list setter
|
||||
if (Array.isArray(data) && data[0] && data[0].__sid__) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueChange();
|
||||
}
|
||||
|
||||
setHotValue(data: any, options?: any) {
|
||||
this.hotValue = data;
|
||||
const v = this.transducer.toNative(data);
|
||||
@ -154,6 +166,8 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
|
||||
this.setValue(v, false, false, options);
|
||||
}
|
||||
|
||||
this.notifyValueChange();
|
||||
|
||||
// dirty fix list setter
|
||||
if (Array.isArray(data) && data[0] && data[0].__sid__) {
|
||||
return;
|
||||
|
||||
@ -276,6 +276,10 @@ export class SettingPropEntry implements SettingEntry {
|
||||
this.emitter.emit('valuechange');
|
||||
}
|
||||
|
||||
notifyValueChange() {
|
||||
this.editor.emit('node.prop.change', { node: this.getNode(), prop: this });
|
||||
}
|
||||
|
||||
getDefaultValue() {
|
||||
return this.extraProps.defaultValue;
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ export class SettingTopEntry implements SettingEntry {
|
||||
readonly designer: Designer;
|
||||
|
||||
constructor(readonly editor: IEditor, readonly nodes: Node[]) {
|
||||
if (nodes.length < 1) {
|
||||
if (!Array.isArray(nodes) || nodes.length < 1) {
|
||||
throw new ReferenceError('nodes should not be empty');
|
||||
}
|
||||
this.id = generateSessionId(nodes);
|
||||
|
||||
@ -31,6 +31,10 @@ function combineTransducer(transducer, arr, context) {
|
||||
}
|
||||
|
||||
export class Transducer {
|
||||
setterTransducer: any;
|
||||
|
||||
context: any;
|
||||
|
||||
constructor(context, config) {
|
||||
let { setter } = config;
|
||||
|
||||
|
||||
@ -4,12 +4,12 @@ import { EventEmitter } from 'events';
|
||||
import { Project } from '../project';
|
||||
import { ISimulatorHost } from '../simulator';
|
||||
import { ComponentMeta } from '../component-meta';
|
||||
import { isDragNodeDataObject, DragNodeObject, DragNodeDataObject, DropLocation } from '../designer';
|
||||
import { isDragNodeDataObject, DragNodeObject, DragNodeDataObject, DropLocation, Designer } from '../designer';
|
||||
import { Node, insertChildren, insertChild, isNode, RootNode, ParentalNode } from './node/node';
|
||||
import { Selection } from './selection';
|
||||
import { History } from './history';
|
||||
import { TransformStage, ModalNodesManager } from './node';
|
||||
import { uniqueId } from '@ali/lowcode-utils';
|
||||
import { uniqueId, isPlainObject } from '@ali/lowcode-utils';
|
||||
|
||||
export type GetDataType<T, NodeType> = T extends undefined
|
||||
? NodeType extends {
|
||||
@ -56,6 +56,10 @@ export class DocumentModel {
|
||||
|
||||
private _nodesMap = new Map<string, Node>();
|
||||
|
||||
readonly project: Project;
|
||||
|
||||
readonly designer: Designer;
|
||||
|
||||
@obx.val private nodes = new Set<Node>();
|
||||
|
||||
private seqId = 0;
|
||||
@ -69,13 +73,13 @@ export class DocumentModel {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
private _addons: { [key: string]: { exportData: () => any; isProp: boolean;} } = {};
|
||||
private _addons: Array<{ name: string, exportData: any }> = [];
|
||||
|
||||
/**
|
||||
* 模拟器
|
||||
*/
|
||||
get simulator(): ISimulatorHost | null {
|
||||
return this._simulator || null;
|
||||
return this.project.simulator;
|
||||
}
|
||||
|
||||
get nodesMap(): Map<string, Node> {
|
||||
@ -83,28 +87,20 @@ export class DocumentModel {
|
||||
}
|
||||
|
||||
get fileName(): string {
|
||||
return this.rootNode.getExtraProp('fileName')?.getAsString() || this.id;
|
||||
return this.rootNode?.getExtraProp('fileName')?.getAsString() || this.id;
|
||||
}
|
||||
|
||||
set fileName(fileName: string) {
|
||||
this.rootNode.getExtraProp('fileName', true)?.setValue(fileName);
|
||||
this.rootNode?.getExtraProp('fileName', true)?.setValue(fileName);
|
||||
}
|
||||
|
||||
private _modalNode?: ParentalNode;
|
||||
|
||||
private _blank?: boolean;
|
||||
|
||||
get modalNode() {
|
||||
return this._modalNode;
|
||||
}
|
||||
|
||||
get currentRoot() {
|
||||
return this.modalNode || this.rootNode;
|
||||
}
|
||||
|
||||
private inited = false;
|
||||
|
||||
constructor(readonly project: Project, schema?: RootSchema) {
|
||||
constructor(project: Project, schema?: RootSchema) {
|
||||
/*
|
||||
// TODO
|
||||
// use special purge process
|
||||
@ -112,6 +108,8 @@ export class DocumentModel {
|
||||
console.info(this.willPurgeSpace);
|
||||
}, true);
|
||||
*/
|
||||
this.project = project;
|
||||
this.designer = this.project?.designer;
|
||||
this.emitter = new EventEmitter();
|
||||
|
||||
if (!schema) {
|
||||
@ -140,6 +138,14 @@ export class DocumentModel {
|
||||
|
||||
@obx.val private willPurgeSpace: Node[] = [];
|
||||
|
||||
get modalNode() {
|
||||
return this._modalNode;
|
||||
}
|
||||
|
||||
get currentRoot() {
|
||||
return this.modalNode || this.rootNode;
|
||||
}
|
||||
|
||||
addWillPurge(node: Node) {
|
||||
this.willPurgeSpace.push(node);
|
||||
}
|
||||
@ -155,16 +161,14 @@ export class DocumentModel {
|
||||
return this._blank && !this.isModified();
|
||||
}
|
||||
|
||||
readonly designer = this.project.designer;
|
||||
|
||||
/**
|
||||
* 生成唯一id
|
||||
*/
|
||||
nextId() {
|
||||
let id;
|
||||
do {
|
||||
id = `node_${ (this.id.slice(-10) + (++this.seqId).toString(36)).toLocaleLowerCase()}`;
|
||||
} while (this.nodesMap.get(id));
|
||||
nextId(possibleId: string | undefined) {
|
||||
let id = possibleId;
|
||||
while (!id || this.nodesMap.get(id)) {
|
||||
id = `node_${(String(this.id).slice(-10) + (++this.seqId).toString(36)).toLocaleLowerCase()}`;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
@ -186,10 +190,6 @@ export class DocumentModel {
|
||||
|
||||
@obx.val private activeNodes?: Node[];
|
||||
|
||||
private setupListenActiveNodes() {
|
||||
// todo:
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 schema 创建一个节点
|
||||
*/
|
||||
@ -205,7 +205,7 @@ export class DocumentModel {
|
||||
}
|
||||
|
||||
let node: Node | null = null;
|
||||
if (this.inited && checkId) {
|
||||
if (this.hasNode(schema?.id)) {
|
||||
schema.id = null;
|
||||
}
|
||||
if (schema.id) {
|
||||
@ -274,7 +274,7 @@ export class DocumentModel {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
this.internalRemoveAndPurgeNode(node);
|
||||
this.internalRemoveAndPurgeNode(node, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -350,7 +350,18 @@ export class DocumentModel {
|
||||
}
|
||||
|
||||
export(stage: TransformStage = TransformStage.Serilize) {
|
||||
return this.rootNode?.export(stage);
|
||||
// 置顶只作用于 Page 的第一级子节点,目前还用不到里层的置顶;如果后面有需要可以考虑将这段写到 node-children 中的 export
|
||||
const currentSchema = this.rootNode?.export(stage);
|
||||
if (Array.isArray(currentSchema?.children) && currentSchema?.children.length > 0) {
|
||||
const FixedTopNodeIndex = currentSchema.children
|
||||
.filter(i => isPlainObject(i))
|
||||
.findIndex((i => (i as NodeSchema).props?.__isTopFixed__));
|
||||
if (FixedTopNodeIndex > 0) {
|
||||
const FixedTopNode = currentSchema.children.splice(FixedTopNodeIndex, 1);
|
||||
currentSchema.children.unshift(FixedTopNode[0]);
|
||||
}
|
||||
}
|
||||
return currentSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -542,7 +553,7 @@ export class DocumentModel {
|
||||
|
||||
// add toData
|
||||
toData(extraComps?: string[]) {
|
||||
const node = this.project?.currentDocument?.export(TransformStage.Save);
|
||||
const node = this.export(TransformStage.Save);
|
||||
const data = {
|
||||
componentsMap: this.getComponentsMap(extraComps),
|
||||
componentsTree: [node],
|
||||
@ -558,23 +569,30 @@ export class DocumentModel {
|
||||
return this.rootNode;
|
||||
}
|
||||
|
||||
onRendererReady(fn: (args: any) => void): () => void {
|
||||
this.emitter.on('lowcode_engine_renderer_ready', fn);
|
||||
return () => {
|
||||
this.emitter.removeListener('lowcode_engine_renderer_ready', fn);
|
||||
};
|
||||
}
|
||||
|
||||
setRendererReady(renderer: any) {
|
||||
this.emitter.emit('lowcode_engine_renderer_ready', renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
getAddonData(name: string) {
|
||||
const addon = this._addons[name];
|
||||
return addon?.exportData();
|
||||
const addon = this._addons.find((item) => item.name === name);
|
||||
if (addon) {
|
||||
return addon.exportData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
exportAddonData() {
|
||||
const addons = {};
|
||||
this._addons.forEach((addon) => {
|
||||
const data = addon.exportData();
|
||||
if (data === null) {
|
||||
delete addons[addon.name];
|
||||
} else {
|
||||
addons[addon.name] = data;
|
||||
}
|
||||
});
|
||||
return addons;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -584,17 +602,16 @@ export class DocumentModel {
|
||||
if (['id', 'params', 'layout'].indexOf(name) > -1) {
|
||||
throw new Error('addon name cannot be id, params, layout');
|
||||
}
|
||||
const i = this._addons?.findIndex((item) => item.name === name);
|
||||
const i = this._addons.findIndex((item) => item.name === name);
|
||||
if (i > -1) {
|
||||
this._addons?.splice(i, 1);
|
||||
this._addons.splice(i, 1);
|
||||
}
|
||||
this._addons?.push({
|
||||
this._addons.push({
|
||||
exportData,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
acceptRootNodeVisitor(
|
||||
visitorName = 'default',
|
||||
visitorFn: (node: RootNode) => any,
|
||||
@ -675,6 +692,17 @@ export class DocumentModel {
|
||||
onRefresh(/* func: () => void */) {
|
||||
console.warn('onRefresh method is deprecated');
|
||||
}
|
||||
|
||||
onReady(fn: Function) {
|
||||
this.designer.editor.on('document-open', fn);
|
||||
return () => {
|
||||
this.designer.editor.removeListener('document-open', fn);
|
||||
};
|
||||
}
|
||||
|
||||
private setupListenActiveNodes() {
|
||||
// todo:
|
||||
}
|
||||
}
|
||||
|
||||
export function isDocumentModel(obj: any): obj is DocumentModel {
|
||||
|
||||
@ -9,7 +9,7 @@ export interface Serialization<T = any> {
|
||||
unserialize(data: T): NodeSchema;
|
||||
}
|
||||
|
||||
let currentSerializion: Serialization<any> = {
|
||||
let currentSerialization: Serialization<any> = {
|
||||
serialize(data: NodeSchema): string {
|
||||
return JSON.stringify(data);
|
||||
},
|
||||
@ -18,8 +18,8 @@ let currentSerializion: Serialization<any> = {
|
||||
},
|
||||
};
|
||||
|
||||
export function setSerialization(serializion: Serialization) {
|
||||
currentSerializion = serializion;
|
||||
export function setSerialization(serialization: Serialization) {
|
||||
currentSerialization = serialization;
|
||||
}
|
||||
|
||||
export class History {
|
||||
@ -46,7 +46,7 @@ export class History {
|
||||
return;
|
||||
}
|
||||
untracked(() => {
|
||||
const log = currentSerializion.serialize(data);
|
||||
const log = currentSerialization.serialize(data);
|
||||
if (this.session.cursor === 0 && this.session.isActive()) {
|
||||
// first log
|
||||
this.session.log(log);
|
||||
@ -98,7 +98,7 @@ export class History {
|
||||
|
||||
this.obx.sleep();
|
||||
try {
|
||||
this.redoer(currentSerializion.unserialize(hotData));
|
||||
this.redoer(currentSerialization.unserialize(hotData));
|
||||
this.emitter.emit('cursor', hotData);
|
||||
} catch (e) {
|
||||
//
|
||||
@ -222,7 +222,7 @@ class Session {
|
||||
end() {
|
||||
if (this.isActive()) {
|
||||
this.clearTimer();
|
||||
console.info('session end');
|
||||
// console.info('session end');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from './document-view';
|
||||
export * from './document-model';
|
||||
export * from './node';
|
||||
export * from './selection';
|
||||
|
||||
@ -35,6 +35,10 @@ export class ExclusiveGroup {
|
||||
return this.children[0]!;
|
||||
}
|
||||
|
||||
get index() {
|
||||
return this.firstNode.index;
|
||||
}
|
||||
|
||||
add(node: Node) {
|
||||
if (node.nextSibling && node.nextSibling.conditionGroup === this) {
|
||||
const i = this.children.indexOf(node.nextSibling);
|
||||
|
||||
@ -4,8 +4,7 @@ import { DocumentModel } from '../document-model';
|
||||
|
||||
function getModalNodes(node: Node) {
|
||||
let nodes: any = [];
|
||||
const prototype = node.getPrototype();
|
||||
if (prototype && prototype.isModal()) {
|
||||
if (node.componentMeta.isModal) {
|
||||
nodes.push(node);
|
||||
}
|
||||
const children = node.getChildren();
|
||||
@ -85,8 +84,7 @@ export class ModalNodesManager {
|
||||
}
|
||||
|
||||
private addNode(node: Node) {
|
||||
const prototype = node.getPrototype();
|
||||
if (prototype && prototype.isModal()) {
|
||||
if (node.componentMeta.isModal) {
|
||||
this.hideModalNodes();
|
||||
this.modalNodes.push(node);
|
||||
this.addNodeEvent(node);
|
||||
@ -96,8 +94,7 @@ export class ModalNodesManager {
|
||||
}
|
||||
|
||||
private removeNode(node: Node) {
|
||||
const prototype = node.getPrototype();
|
||||
if (prototype && prototype.isModal()) {
|
||||
if (node.componentMeta.isModal) {
|
||||
const index = this.modalNodes.indexOf(node);
|
||||
if (index >= 0) {
|
||||
this.modalNodes.splice(index, 1);
|
||||
|
||||
@ -183,6 +183,7 @@ export class NodeChildren {
|
||||
}
|
||||
|
||||
this.emitter.emit('change');
|
||||
this.emitter.emit('insert', node);
|
||||
// this.reportModified(node, this.owner, { type: 'insert' });
|
||||
|
||||
// check condition group
|
||||
@ -335,6 +336,13 @@ export class NodeChildren {
|
||||
};
|
||||
}
|
||||
|
||||
onInsert(fn: (node: Node) => void) {
|
||||
this.emitter.on('insert', fn);
|
||||
return () => {
|
||||
this.emitter.removeListener('insert', fn);
|
||||
};
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
// 保证向前兼容性
|
||||
return 'Array';
|
||||
|
||||
@ -158,24 +158,25 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
|
||||
constructor(readonly document: DocumentModel, nodeSchema: Schema, options: any = {}) {
|
||||
const { componentName, id, children, props, ...extras } = nodeSchema;
|
||||
this.id = id || document.nextId();
|
||||
this.id = document.nextId(id);
|
||||
this.componentName = componentName;
|
||||
if (this.componentName === 'Leaf') {
|
||||
this.props = new Props(this, {
|
||||
children: isDOMText(children) || isJSExpression(children) ? children : '',
|
||||
});
|
||||
this.settingEntry = this.document.designer.createSettingEntry([this]);
|
||||
} else {
|
||||
// 这里 props 被初始化两次,一次 new,一次 import,new 的实例需要给 propsReducer 的钩子去使用,
|
||||
// import 是为了使用钩子返回的值,并非完全幂等的操作,部分行为执行两次会有 bug,
|
||||
// 所以在 props 里会对 new / import 做一些区别化的解析
|
||||
this.props = new Props(this, props, extras);
|
||||
this._children = new NodeChildren(this as ParentalNode, this.initialChildren(children), options);
|
||||
this.settingEntry = this.document.designer.createSettingEntry([this]);
|
||||
this._children = new NodeChildren(this as ParentalNode, this.initialChildren(children));
|
||||
this._children.internalInitParent();
|
||||
this.props.import(this.upgradeProps(this.initProps(props || {})), this.upgradeProps(extras || {}));
|
||||
this.setupAutoruns();
|
||||
}
|
||||
|
||||
this.settingEntry = this.document.designer.createSettingEntry([this]);
|
||||
this.emitter = new EventEmitter();
|
||||
}
|
||||
|
||||
@ -264,6 +265,16 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
}
|
||||
}
|
||||
|
||||
private didDropOut(dragment: Node) {
|
||||
const callbacks = this.componentMeta.getMetadata().experimental?.callbacks;
|
||||
if (callbacks?.onNodeRemove) {
|
||||
callbacks?.onNodeRemove.call(this, dragment, this);
|
||||
}
|
||||
if (this._parent) {
|
||||
this._parent.didDropOut(dragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部方法,请勿使用
|
||||
* @param useMutator 是否触发联动逻辑
|
||||
@ -281,9 +292,12 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
this._parent.children.unlinkChild(this);
|
||||
}
|
||||
}
|
||||
// 建立新的父子关系
|
||||
this._parent = parent;
|
||||
if (useMutator) {
|
||||
this._parent?.didDropOut(this);
|
||||
}
|
||||
if (parent) {
|
||||
// 建立新的父子关系,尤其注意:对于 parent 为 null 的场景,不会赋值,因为 subtreeModified 等事件可能需要知道该 node 被删除前的父子关系
|
||||
this._parent = parent;
|
||||
this.document.removeWillPurge(this);
|
||||
if (!this.conditionGroup) {
|
||||
// initial conditionGroup
|
||||
@ -609,6 +623,9 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
if (stage !== TransformStage.Clone) {
|
||||
baseSchema.id = this.id;
|
||||
}
|
||||
if (stage === TransformStage.Render) {
|
||||
baseSchema.docId = this.document.id;
|
||||
}
|
||||
|
||||
if (this.isLeaf()) {
|
||||
baseSchema.children = this.props.get('children')?.export(stage);
|
||||
@ -893,7 +910,14 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
if (dropElement) {
|
||||
return { container: dropElement, ref };
|
||||
}
|
||||
return { container: this, ref };
|
||||
const rootCanDropIn = this.componentMeta?.prototype?.options?.canDropIn;
|
||||
if (rootCanDropIn === undefined
|
||||
|| rootCanDropIn === true
|
||||
|| (typeof rootCanDropIn === 'function' && rootCanDropIn(node))) {
|
||||
return { container: this, ref };
|
||||
}
|
||||
// 假如最后找不到合适位置,返回 undefined 阻止继续插入节点
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const canDropIn = this.componentMeta?.prototype?.options?.canDropIn;
|
||||
|
||||
@ -119,7 +119,7 @@ export class Prop implements IPropParent {
|
||||
}
|
||||
|
||||
if (type === 'slot') {
|
||||
const schema = this._slotNode!.export(stage);
|
||||
const schema = this._slotNode?.export(stage) || {};
|
||||
if (stage === TransformStage.Render) {
|
||||
return {
|
||||
type: 'JSSlot',
|
||||
@ -233,7 +233,7 @@ export class Prop implements IPropParent {
|
||||
} else if (Array.isArray(val)) {
|
||||
this._type = 'list';
|
||||
} else if (isPlainObject(val)) {
|
||||
if (isJSSlot(val) && this.options.propsMode !== 'init') {
|
||||
if (isJSSlot(val) && this.options.skipSetSlot !== true) {
|
||||
this.setAsSlot(val);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -59,9 +59,9 @@ export class Props implements IPropParent {
|
||||
constructor(readonly owner: Node, value?: PropsMap | PropsList | null, extras?: object) {
|
||||
if (Array.isArray(value)) {
|
||||
this.type = 'list';
|
||||
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread, { propsMode: 'init' }));
|
||||
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread, { skipSetSlot: true }));
|
||||
} else if (value != null) {
|
||||
this.items = Object.keys(value).map(key => new Prop(this, value[key], key, false, { propsMode: 'init' }));
|
||||
this.items = Object.keys(value).map(key => new Prop(this, value[key], key, false, { skipSetSlot: true }));
|
||||
}
|
||||
if (extras) {
|
||||
Object.keys(extras).forEach(key => {
|
||||
@ -241,8 +241,8 @@ export class Props implements IPropParent {
|
||||
/**
|
||||
* 添加值
|
||||
*/
|
||||
add(value: CompositeValue | null, key?: string | number, spread = false): Prop {
|
||||
const prop = new Prop(this, value, key, spread);
|
||||
add(value: CompositeValue | null, key?: string | number, spread = false, options: any = {}): Prop {
|
||||
const prop = new Prop(this, value, key, spread, options);
|
||||
this.items.push(prop);
|
||||
return prop;
|
||||
}
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
import { Component } from 'react';
|
||||
import { observer } from '@ali/lowcode-editor-core';
|
||||
import { Designer } from '../designer';
|
||||
import { DocumentView } from '../document';
|
||||
import { intl } from '../locale';
|
||||
import { BuiltinSimulatorHostView } from '../builtin-simulator';
|
||||
import './project.less';
|
||||
|
||||
@observer
|
||||
export class ProjectView extends Component<{ designer: Designer }> {
|
||||
render() {
|
||||
const { designer } = this.props;
|
||||
// TODO: support splitview
|
||||
const opens = designer.project.documents.filter((doc) => doc.opened);
|
||||
const project = designer.project;
|
||||
const simulatorProps = project.simulatorProps;
|
||||
const Simulator = designer.simulatorComponent || BuiltinSimulatorHostView;
|
||||
|
||||
return (
|
||||
<div className="lc-project">
|
||||
{opens.length > 0 ? (
|
||||
opens.map((doc) => <DocumentView key={doc.id} document={doc} />)
|
||||
) : (
|
||||
<div className="lc-project-empty">{intl('No opened document')}</div>
|
||||
)}
|
||||
<div className="lc-simulator-shell">
|
||||
<Simulator {...simulatorProps} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,17 +12,9 @@
|
||||
background: transparent url(//img.alicdn.com/tfs/TB1xLKQAbj1gK0jSZFuXXcrHpXa-90-90.png) center 30% no-repeat;
|
||||
padding-top: 50%;
|
||||
}
|
||||
.lc-document {
|
||||
|
||||
.lc-simulator-shell {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&-hidden {
|
||||
// todo:
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lc-simulator-shell {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { obx, computed } from '@ali/lowcode-editor-core';
|
||||
import { Designer } from '../designer';
|
||||
import { DocumentModel, isDocumentModel, isPageSchema } from '../document';
|
||||
import { ProjectSchema, RootSchema } from '@ali/lowcode-types';
|
||||
import { ISimulatorHost } from '../simulator';
|
||||
|
||||
export class Project {
|
||||
private emitter = new EventEmitter();
|
||||
@ -11,7 +12,14 @@ export class Project {
|
||||
|
||||
private data: ProjectSchema = { version: '1.0.0', componentsMap: [], componentsTree: [] };
|
||||
|
||||
@obx.ref canvasDisplayMode: 'exclusive' | 'overview' = 'exclusive';
|
||||
private _simulator?: ISimulatorHost;
|
||||
|
||||
/**
|
||||
* 模拟器
|
||||
*/
|
||||
get simulator(): ISimulatorHost | null {
|
||||
return this._simulator || null;
|
||||
}
|
||||
|
||||
// TODO: 考虑项目级别 History
|
||||
|
||||
@ -23,6 +31,15 @@ export class Project {
|
||||
return this.documents.find((doc) => doc.actived);
|
||||
}
|
||||
|
||||
@obx private _config: any = {};
|
||||
@computed get config(): any {
|
||||
// TODO: parse layout Component
|
||||
return this._config;
|
||||
}
|
||||
set config(value: any) {
|
||||
this._config = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目整体 schema
|
||||
*/
|
||||
@ -40,7 +57,7 @@ export class Project {
|
||||
*/
|
||||
setSchema(schema?: ProjectSchema) {
|
||||
const doc = this.documents.find((doc) => doc.actived);
|
||||
doc && doc.import(schema?.componentsTree[0], false);
|
||||
doc && doc.import(schema?.componentsTree[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,11 +74,19 @@ export class Project {
|
||||
componentsTree: [],
|
||||
...schema,
|
||||
};
|
||||
this.config = schema?.config;
|
||||
|
||||
if (autoOpen) {
|
||||
if (autoOpen === true) {
|
||||
// auto open first document or open a blank page
|
||||
this.open(this.data.componentsTree[0]);
|
||||
// this.open(this.data.componentsTree[0]);
|
||||
const documentInstances = this.data.componentsTree.map((data) => this.createDocument(data));
|
||||
// TODO: 暂时先读 config tabBar 里的值,后面看整个 layout 结构是否能作为引擎规范
|
||||
if (this.config?.layout?.props?.tabBar?.items?.length > 0) {
|
||||
documentInstances.find((i) => i.fileName === this.config.layout.props.tabBar.items[0].path?.slice(1))?.open();
|
||||
} else {
|
||||
documentInstances[0].open();
|
||||
}
|
||||
} else {
|
||||
// auto open should be string of fileName
|
||||
this.open(autoOpen);
|
||||
@ -76,7 +101,9 @@ export class Project {
|
||||
if (this.documents.length < 1) {
|
||||
return;
|
||||
}
|
||||
this.documents.forEach((doc) => doc.remove());
|
||||
for (let i = this.documents.length - 1; i >= 0; i--) {
|
||||
this.documents[i].remove();
|
||||
}
|
||||
}
|
||||
|
||||
removeDocument(doc: DocumentModel) {
|
||||
@ -85,6 +112,7 @@ export class Project {
|
||||
return;
|
||||
}
|
||||
this.documents.splice(index, 1);
|
||||
this.documentsMap.delete(doc.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,6 +133,9 @@ export class Project {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
value: any,
|
||||
): void {
|
||||
if (key === 'config') {
|
||||
this.config = value;
|
||||
}
|
||||
Object.assign(this.data, { [key]: value });
|
||||
}
|
||||
|
||||
@ -114,27 +145,44 @@ export class Project {
|
||||
get(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
key:
|
||||
| 'version'
|
||||
| 'componentsTree'
|
||||
| 'componentsMap'
|
||||
| 'utils'
|
||||
| 'constants'
|
||||
| 'i18n'
|
||||
| 'css'
|
||||
| 'dataSource'
|
||||
| string,
|
||||
| 'version'
|
||||
| 'componentsTree'
|
||||
| 'componentsMap'
|
||||
| 'utils'
|
||||
| 'constants'
|
||||
| 'i18n'
|
||||
| 'css'
|
||||
| 'dataSource'
|
||||
| 'config'
|
||||
| string,
|
||||
): any {
|
||||
if (key === 'config') {
|
||||
return this.config;
|
||||
}
|
||||
return Reflect.get(this.data, key);
|
||||
}
|
||||
|
||||
open(doc?: string | DocumentModel | RootSchema): DocumentModel {
|
||||
private documentsMap = new Map<string, DocumentModel>();
|
||||
|
||||
getDocument(id: string): DocumentModel | null {
|
||||
// 此处不能使用 this.documentsMap.get(id),因为在乐高 rollback 场景,document.id 会被改成其他值
|
||||
return this.documents.find(doc => doc.id === id) || null;
|
||||
}
|
||||
|
||||
createDocument(data?: RootSchema): DocumentModel {
|
||||
const doc = new DocumentModel(this, data || this?.data?.componentsTree?.[0]);
|
||||
this.documents.push(doc);
|
||||
this.documentsMap.set(doc.id, doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
open(doc?: string | DocumentModel | RootSchema): DocumentModel | null {
|
||||
if (!doc) {
|
||||
const got = this.documents.find((item) => item.isBlank());
|
||||
if (got) {
|
||||
return got.open();
|
||||
}
|
||||
doc = new DocumentModel(this);
|
||||
this.documents.push(doc);
|
||||
doc = this.createDocument();
|
||||
return doc.open();
|
||||
}
|
||||
if (typeof doc === 'string') {
|
||||
@ -145,27 +193,24 @@ export class Project {
|
||||
|
||||
const data = this.data.componentsTree.find((data) => data.fileName === doc);
|
||||
if (data) {
|
||||
doc = new DocumentModel(this, data);
|
||||
this.documents.push(doc);
|
||||
doc = this.createDocument(data);
|
||||
return doc.open();
|
||||
}
|
||||
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isDocumentModel(doc)) {
|
||||
return doc.open();
|
||||
} else if (isPageSchema(doc)) {
|
||||
const foundDoc = this.documents.find(
|
||||
(curDoc) => curDoc?.rootNode?.id && curDoc?.rootNode?.id === doc?.id,
|
||||
);
|
||||
if (foundDoc) {
|
||||
foundDoc.remove();
|
||||
}
|
||||
// 暂时注释掉,影响了 diff 功能
|
||||
// const foundDoc = this.documents.find(curDoc => curDoc?.rootNode?.id && curDoc?.rootNode?.id === doc?.id);
|
||||
// if (foundDoc) {
|
||||
// foundDoc.remove();
|
||||
// }
|
||||
}
|
||||
|
||||
doc = new DocumentModel(this, doc);
|
||||
this.documents.push(doc);
|
||||
doc = this.createDocument(doc);
|
||||
return doc.open();
|
||||
}
|
||||
|
||||
@ -186,13 +231,42 @@ export class Project {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供给模拟器的参数
|
||||
*/
|
||||
@computed get simulatorProps(): object {
|
||||
let simulatorProps = this.designer.simulatorProps;
|
||||
if (typeof simulatorProps === 'function') {
|
||||
simulatorProps = simulatorProps(this);
|
||||
}
|
||||
return {
|
||||
...simulatorProps,
|
||||
project: this,
|
||||
onMount: this.mountSimulator.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
private mountSimulator(simulator: ISimulatorHost) {
|
||||
// TODO: 多设备 simulator 支持
|
||||
this._simulator = simulator;
|
||||
this.designer.editor.set('simulator', simulator);
|
||||
}
|
||||
|
||||
setRendererReady(renderer: any) {
|
||||
this.emitter.emit('lowcode_engine_renderer_ready', renderer);
|
||||
}
|
||||
|
||||
onRendererReady(fn: (args: any) => void): () => void {
|
||||
this.emitter.on('lowcode_engine_renderer_ready', fn);
|
||||
return () => {
|
||||
this.emitter.removeListener('lowcode_engine_renderer_ready', fn);
|
||||
};
|
||||
}
|
||||
|
||||
onCurrentDocumentChange(fn: (doc: DocumentModel) => void): () => void {
|
||||
this.emitter.on('current-document.change', fn);
|
||||
return () => {
|
||||
this.emitter.removeListener('current-document.change', fn);
|
||||
};
|
||||
}
|
||||
// 通知标记删除,需要告知服务端
|
||||
// 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁,
|
||||
// 哪个删除就
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component as ReactComponent, ComponentType } from 'react';
|
||||
import { ComponentMetadata, NodeSchema } from '@ali/lowcode-types';
|
||||
import { ISensor, Point, ScrollTarget, IScrollable } from './designer';
|
||||
import { Node } from './document';
|
||||
import { ISensor, Point, ScrollTarget, IScrollable, LocateEvent, LocationData } from './designer';
|
||||
import { Node, ParentalNode } from './document';
|
||||
|
||||
export type AutoFit = '100%';
|
||||
export const AutoFit = '100%';
|
||||
@ -60,6 +60,11 @@ export interface IViewport extends IScrollable {
|
||||
toGlobalPoint(point: Point): Point;
|
||||
}
|
||||
|
||||
export interface DropContainer {
|
||||
container: ParentalNode;
|
||||
instance: ComponentInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟器控制进程协议
|
||||
*/
|
||||
@ -143,6 +148,8 @@ export interface ISimulatorHost<P = object> extends ISensor {
|
||||
|
||||
findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | null;
|
||||
|
||||
getDropContainer(e: LocateEvent): DropContainer | null;
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
@ -154,6 +161,7 @@ export function isSimulatorHost(obj: any): obj is ISimulatorHost {
|
||||
}
|
||||
|
||||
export interface NodeInstance<T = ComponentInstance> {
|
||||
docId: string;
|
||||
nodeId: string;
|
||||
instance: T;
|
||||
node?: Node | null;
|
||||
|
||||
10
packages/designer/tests/__mocks__/document-model.ts
Normal file
10
packages/designer/tests/__mocks__/document-model.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export class DocumentModel {
|
||||
a = 1;
|
||||
c = {};
|
||||
constructor() {
|
||||
console.log('xxxxxxxxxxxxxxxxxxxx');
|
||||
const b = { x: { y: 2 } };
|
||||
const c: number = 2;
|
||||
this.a = b?.x?.y;
|
||||
}
|
||||
}
|
||||
9
packages/designer/tests/__mocks__/node.ts
Normal file
9
packages/designer/tests/__mocks__/node.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export class Node2 {
|
||||
a = 1;
|
||||
c = {};
|
||||
constructor() {
|
||||
const b = { x: { y: 2 } };
|
||||
const c: number = 2;
|
||||
this.a = b?.x?.y;
|
||||
}
|
||||
}
|
||||
55
packages/designer/tests/bugs/misc.ts
Normal file
55
packages/designer/tests/bugs/misc.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import '../fixtures/window';
|
||||
import { Project } from '../../src/project/project';
|
||||
// import { Node } from '../../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getComponentMeta() {
|
||||
return {
|
||||
getMetadata() {
|
||||
return { experimental: null };
|
||||
},
|
||||
};
|
||||
},
|
||||
transformProps(props) { return props; },
|
||||
createSettingEntry: mockCreateSettingEntry,
|
||||
postEvent() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
it.todo('在同一个节点下,相同名称的 slot 只能有一个', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
expect(nodesMap.size).toBe(expectedNodeCnt);
|
||||
ids.forEach(id => {
|
||||
expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
|
||||
});
|
||||
|
||||
const exportSchema = currentDocument?.export(1);
|
||||
expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
|
||||
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
|
||||
});
|
||||
32
packages/designer/tests/builtin-simulator/host-view.test.tsx
Normal file
32
packages/designer/tests/builtin-simulator/host-view.test.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/clonedeep';
|
||||
import '../fixtures/window';
|
||||
import { Editor } from '@ali/lowcode-editor-core';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import { configure, render, mount } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
import { BuiltinSimulatorHostView } from '../../src/builtin-simulator/host-view';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
const editor = new Editor();
|
||||
|
||||
describe('host-view 测试', () => {
|
||||
let designer: Designer;
|
||||
beforeEach(() => {
|
||||
designer = new Designer({ editor });
|
||||
});
|
||||
afterEach(() => {
|
||||
designer._componentMetasMap.clear();
|
||||
designer = null;
|
||||
});
|
||||
|
||||
it('host-view', () => {
|
||||
const hostView = render(<BuiltinSimulatorHostView project={designer.project} />);
|
||||
})
|
||||
});
|
||||
145
packages/designer/tests/builtin-simulator/host.test.tsx
Normal file
145
packages/designer/tests/builtin-simulator/host.test.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/clonedeep';
|
||||
import '../fixtures/window';
|
||||
import { Editor } from '@ali/lowcode-editor-core';
|
||||
import {
|
||||
AssetLevel,
|
||||
Asset,
|
||||
AssetList,
|
||||
assetBundle,
|
||||
assetItem,
|
||||
AssetType,
|
||||
} from '@ali/lowcode-utils';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getMockDocument, getMockWindow, getMockEvent } from '../utils';
|
||||
import { BuiltinSimulatorHost } from '../../src/builtin-simulator/host';
|
||||
import { eq } from 'lodash';
|
||||
|
||||
const editor = new Editor();
|
||||
|
||||
describe('host 测试', () => {
|
||||
let designer: Designer;
|
||||
beforeEach(() => {
|
||||
designer = new Designer({ editor });
|
||||
});
|
||||
afterEach(() => {
|
||||
designer._componentMetasMap.clear();
|
||||
designer = null;
|
||||
});
|
||||
|
||||
it('基础方法测试', async () => {
|
||||
const host = new BuiltinSimulatorHost(designer.project);
|
||||
expect(host.currentDocument).toBe(designer.project.currentDocument);
|
||||
expect(host.renderEnv).toBe('default');
|
||||
expect(host.device).toBe('default');
|
||||
expect(host.deviceClassName).toBeUndefined;
|
||||
host.setProps({
|
||||
renderEnv: 'rax',
|
||||
device: 'mobile',
|
||||
deviceClassName: 'mobile-rocks',
|
||||
componentsAsset: [{
|
||||
type: AssetType.JSText,
|
||||
content: 'console.log(1)',
|
||||
}, {
|
||||
type: AssetType.JSUrl,
|
||||
content: '//path/to/js',
|
||||
}],
|
||||
theme: {
|
||||
type: AssetType.CSSText,
|
||||
content: '.theme {font-size: 50px;}',
|
||||
}
|
||||
});
|
||||
expect(host.renderEnv).toBe('rax');
|
||||
expect(host.device).toBe('mobile');
|
||||
expect(host.deviceClassName).toBe('mobile-rocks');
|
||||
expect(host.componentsAsset).toEqual([{
|
||||
type: AssetType.JSText,
|
||||
content: 'console.log(1)',
|
||||
}, {
|
||||
type: AssetType.JSUrl,
|
||||
content: '//path/to/js',
|
||||
}]);
|
||||
expect(host.theme).toEqual({
|
||||
type: AssetType.CSSText,
|
||||
content: '.theme {font-size: 50px;}',
|
||||
});
|
||||
expect(host.componentsMap).toBe(designer.componentsMap);
|
||||
|
||||
host.set('renderEnv', 'vue');
|
||||
expect(host.renderEnv).toBe('vue');
|
||||
|
||||
expect(host.getComponentContext).toThrow('Method not implemented.');
|
||||
});
|
||||
|
||||
it('事件测试', async () => {
|
||||
const host = new BuiltinSimulatorHost(designer.project);
|
||||
const mockDocument = getMockDocument();
|
||||
const mockWindow = getMockWindow(mockDocument);
|
||||
const mockIframe = {
|
||||
contentWindow: mockWindow,
|
||||
contentDocument: mockDocument,
|
||||
dispatchEvent() {},
|
||||
};
|
||||
|
||||
// 非法分支测试
|
||||
host.mountContentFrame();
|
||||
expect(host._iframe).toBeUndefined();
|
||||
|
||||
host.set('library', [{
|
||||
package: '@ali/vc-deep',
|
||||
library: 'lib',
|
||||
urls: ['a.js', 'b.js']
|
||||
}]);
|
||||
|
||||
host.componentsConsumer.consume(() => {});
|
||||
host.injectionConsumer.consume(() => {});
|
||||
await host.mountContentFrame(mockIframe);
|
||||
|
||||
expect(host.contentWindow).toBe(mockWindow);
|
||||
|
||||
mockDocument.triggerEventListener(
|
||||
'mouseover',
|
||||
getMockEvent(mockDocument.createElement('div')),
|
||||
host,
|
||||
);
|
||||
mockDocument.triggerEventListener(
|
||||
'mouseleave',
|
||||
getMockEvent(mockDocument.createElement('div')),
|
||||
host,
|
||||
);
|
||||
mockDocument.triggerEventListener(
|
||||
'mousedown',
|
||||
getMockEvent(mockDocument.createElement('div')),
|
||||
host,
|
||||
);
|
||||
mockDocument.triggerEventListener(
|
||||
'mouseup',
|
||||
getMockEvent(mockDocument.createElement('div')),
|
||||
host,
|
||||
);
|
||||
mockDocument.triggerEventListener(
|
||||
'mousemove',
|
||||
getMockEvent(mockDocument.createElement('div')),
|
||||
host,
|
||||
);
|
||||
mockDocument.triggerEventListener(
|
||||
'click',
|
||||
getMockEvent(document.createElement('input')),
|
||||
host,
|
||||
);
|
||||
mockDocument.triggerEventListener(
|
||||
'dblclick',
|
||||
getMockEvent(mockDocument.createElement('div')),
|
||||
host,
|
||||
);
|
||||
mockDocument.triggerEventListener(
|
||||
'contextmenu',
|
||||
getMockEvent(mockDocument.createElement('div')),
|
||||
host,
|
||||
);
|
||||
})
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import '../fixtures/window';
|
||||
import { parseMetadata } from '../../src/builtin-simulator/utils/parse-metadata';
|
||||
|
||||
describe('parseMetadata', () => {
|
||||
it('parseMetadata', async () => {
|
||||
const md1 = parseMetadata('Div');
|
||||
const md2 = parseMetadata({ componentName: 'Div' });
|
||||
});
|
||||
});
|
||||
78
packages/designer/tests/builtin-simulator/path.test.ts
Normal file
78
packages/designer/tests/builtin-simulator/path.test.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
generateComponentName,
|
||||
getNormalizedImportPath,
|
||||
isPackagePath,
|
||||
toTitleCase,
|
||||
makeRelativePath,
|
||||
removeVersion,
|
||||
resolveAbsoluatePath,
|
||||
joinPath,
|
||||
} from '../../src/builtin-simulator/utils/path';
|
||||
|
||||
describe('builtin-simulator/utils/path 测试', () => {
|
||||
it('isPackagePath', () => {
|
||||
expect(isPackagePath('a')).toBeTruthy;
|
||||
expect(isPackagePath('@ali/a')).toBeTruthy;
|
||||
expect(isPackagePath('@alife/a')).toBeTruthy;
|
||||
expect(isPackagePath('a.b')).toBeTruthy;
|
||||
expect(isPackagePath('./a')).toBeFalsy;
|
||||
expect(isPackagePath('../a')).toBeFalsy;
|
||||
expect(isPackagePath('/a')).toBeFalsy;
|
||||
});
|
||||
|
||||
it('toTitleCase', () => {
|
||||
expect(toTitleCase('a')).toBe('A');
|
||||
expect(toTitleCase('a_b')).toBe('AB');
|
||||
expect(toTitleCase('a b')).toBe('AB');
|
||||
expect(toTitleCase('a-b')).toBe('AB');
|
||||
expect(toTitleCase('a.b')).toBe('AB');
|
||||
expect(toTitleCase('a.b.cx')).toBe('ABCx');
|
||||
});
|
||||
|
||||
it('generateComponentName', () => {
|
||||
expect(generateComponentName('a/index.js')).toBe('A');
|
||||
expect(generateComponentName('a_b/index.js')).toBe('AB');
|
||||
expect(generateComponentName('a_b/index.web.js')).toBe('AB');
|
||||
expect(generateComponentName('a_b/index.xxx.js')).toBe('AB');
|
||||
expect(generateComponentName('a_b')).toBe('AB');
|
||||
expect(generateComponentName('')).toBe('Component');
|
||||
});
|
||||
|
||||
it('getNormalizedImportPath', () => {
|
||||
expect(getNormalizedImportPath('/a')).toBe('/a');
|
||||
expect(getNormalizedImportPath('/a/')).toBe('/a/');
|
||||
expect(getNormalizedImportPath('/a/index.js')).toBe('/a');
|
||||
expect(getNormalizedImportPath('/a/index.ts')).toBe('/a');
|
||||
expect(getNormalizedImportPath('/a/index.jsx')).toBe('/a');
|
||||
expect(getNormalizedImportPath('/a/index.tsx')).toBe('/a');
|
||||
expect(getNormalizedImportPath('/a/index.x')).toBe('/a/index.x');
|
||||
});
|
||||
|
||||
it('makeRelativePath', () => {
|
||||
expect(makeRelativePath('/a/b/c', '/a/b')).toBe('c');
|
||||
expect(makeRelativePath('a/b/c', '/a/c')).toBe('a/b/c');
|
||||
expect(makeRelativePath('/a/b/c', '/a/c')).toBe('./b/c');
|
||||
expect(makeRelativePath('/a/b/c', '/a/c/d')).toBe('../b/c');
|
||||
});
|
||||
|
||||
it('resolveAbsoluatePath', () => {
|
||||
expect(resolveAbsoluatePath('/a/b/c', '/a')).toBe('/a/b/c');
|
||||
expect(resolveAbsoluatePath('@ali/fe', '/a')).toBe('@ali/fe');
|
||||
expect(resolveAbsoluatePath('./a/b', '/c')).toBe('/c/a/b');
|
||||
expect(resolveAbsoluatePath('./a/b/d', '/c')).toBe('/c/a/b/d');
|
||||
expect(resolveAbsoluatePath('../a/b', '/c')).toBe('/a/b');
|
||||
expect(resolveAbsoluatePath('../a/b/d', '/c')).toBe('/a/b/d');
|
||||
expect(resolveAbsoluatePath('../../a', 'c')).toBe('../a');
|
||||
});
|
||||
|
||||
it('joinPath', () => {
|
||||
expect(joinPath('/a', 'b', 'c')).toBe('/a/b/c');
|
||||
expect(joinPath('a', 'b', 'c')).toBe('./a/b/c');
|
||||
});
|
||||
|
||||
it('removeVersion', () => {
|
||||
expect(removeVersion('@ali/fe')).toBe('@ali/fe');
|
||||
expect(removeVersion('@ali/fe@1.0.0/index')).toBe('@ali/fe/index');
|
||||
expect(removeVersion('haha')).toBe('haha');
|
||||
});
|
||||
});
|
||||
21
packages/designer/tests/builtin-simulator/renderer.test.tsx
Normal file
21
packages/designer/tests/builtin-simulator/renderer.test.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/clonedeep';
|
||||
import '../fixtures/window';
|
||||
import { Editor } from '@ali/lowcode-editor-core';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import { configure, render, mount } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getMockRenderer } from '../utils';
|
||||
import { isSimulatorRenderer } from '../../src/builtin-simulator/renderer';
|
||||
|
||||
|
||||
describe('renderer 测试', () => {
|
||||
it('renderer', () => {
|
||||
expect(isSimulatorRenderer(getMockRenderer())).toBeTruthy;
|
||||
})
|
||||
});
|
||||
22
packages/designer/tests/builtin-simulator/throttle.test.ts
Normal file
22
packages/designer/tests/builtin-simulator/throttle.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import '../fixtures/disable-raf';
|
||||
import { throttle } from '../../src/builtin-simulator/utils/throttle';
|
||||
|
||||
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const cb = jest.fn();
|
||||
|
||||
describe('throttle', () => {
|
||||
it('simple', async () => {
|
||||
const fn = throttle(cb, 1000);
|
||||
fn();
|
||||
|
||||
expect(cb).toBeCalledTimes(1);
|
||||
|
||||
await delay(200);
|
||||
fn();
|
||||
|
||||
await delay(400);
|
||||
fn();
|
||||
expect(cb).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
202
packages/designer/tests/designer/builtin-hotkey.test.ts
Normal file
202
packages/designer/tests/designer/builtin-hotkey.test.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/clonedeep';
|
||||
import '../fixtures/window';
|
||||
import { Editor, globalContext } from '@ali/lowcode-editor-core';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import { Project } from '../../src/project/project';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import '../../src/designer/builtin-hotkey';
|
||||
|
||||
const editor = new Editor();
|
||||
|
||||
let designer: Designer;
|
||||
beforeAll(() => {
|
||||
globalContext.register(editor, Editor);
|
||||
});
|
||||
beforeEach(() => {
|
||||
designer = new Designer({ editor });
|
||||
designer.project.open(formSchema);
|
||||
});
|
||||
afterEach(() => {
|
||||
designer = null;
|
||||
});
|
||||
|
||||
// keyCode 对应表:https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
|
||||
// hotkey 模块底层用的 keyCode,所以还不能用 key / code 测试
|
||||
describe('快捷键测试', () => {
|
||||
it('right', () => {
|
||||
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbj')!;
|
||||
firstCardNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 39 });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy;
|
||||
});
|
||||
|
||||
it('left', () => {
|
||||
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
|
||||
firstCardNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 37 });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(designer.currentSelection?.selected.includes('node_k1ow3cbj')).toBeTruthy;
|
||||
});
|
||||
|
||||
it('down', () => {
|
||||
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbl')!;
|
||||
firstCardNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 40 });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(designer.currentSelection?.selected.includes('node_k1ow3cbm')).toBeTruthy;
|
||||
});
|
||||
|
||||
it('up', () => {
|
||||
const secondCardNode = designer.currentDocument?.getNode('node_k1ow3cbm')!;
|
||||
secondCardNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 38 });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(designer.currentSelection?.selected.includes('node_k1ow3cbl')).toBeTruthy;
|
||||
});
|
||||
|
||||
// 跟右侧节点调换位置
|
||||
it('option + right', () => {
|
||||
const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
|
||||
firstButtonNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 39, altKey: true });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(firstButtonNode.prevSibling?.getId()).toBe('node_k1ow3cbp');
|
||||
});
|
||||
|
||||
// 跟左侧节点调换位置
|
||||
it('option + left', () => {
|
||||
const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
secondButtonNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 37, altKey: true });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(secondButtonNode.nextSibling?.getId()).toBe('node_k1ow3cbn');
|
||||
});
|
||||
|
||||
// 向父级移动该节点
|
||||
it('option + up', () => {
|
||||
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
firstCardNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 38, altKey: true });
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// 将节点移入到兄弟节点中
|
||||
it('option + up', () => {
|
||||
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
firstCardNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 40, altKey: true });
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// 撤销
|
||||
it('command + z', async () => {
|
||||
const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
|
||||
let secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
|
||||
firstButtonNode.remove();
|
||||
expect(secondButtonNode.getParent()?.children.size).toBe(1);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 90, metaKey: true });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
// 重新获取一次节点,因为 documentModel.import 是全画布刷新
|
||||
secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
expect(secondButtonNode.getParent()?.children.size).toBe(2);
|
||||
});
|
||||
|
||||
// 重做
|
||||
it('command + y', async () => {
|
||||
const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
|
||||
let secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
|
||||
firstButtonNode.remove();
|
||||
expect(secondButtonNode.getParent()?.children.size).toBe(1);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 90, metaKey: true });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
// 重新获取一次节点,因为 documentModel.import 是全画布刷新
|
||||
secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
expect(secondButtonNode.getParent()?.children.size).toBe(2);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
event = new KeyboardEvent('keydown', { keyCode: 89, metaKey: true });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
// 重新获取一次节点,因为 documentModel.import 是全画布刷新
|
||||
secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
expect(secondButtonNode.getParent()?.children.size).toBe(1);
|
||||
});
|
||||
|
||||
it('command + c', () => {
|
||||
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
firstCardNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 67, metaKey: true });
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
it('command + v', async () => {
|
||||
const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
secondButtonNode.select();
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 67, metaKey: true });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
event = new KeyboardEvent('keydown', { keyCode: 86, metaKey: true });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// clipboard 异步,先注释
|
||||
// expect(secondButtonNode.getParent()?.children.size).toBe(3);
|
||||
});
|
||||
|
||||
// 撤销所有选中
|
||||
it('escape', () => {
|
||||
const firstCardNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
firstCardNode.select();
|
||||
|
||||
expect(designer.currentSelection!.selected.includes('node_k1ow3cbp')).toBeTruthy;
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 27 });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(designer.currentSelection!.selected.length).toBe(0);
|
||||
});
|
||||
|
||||
// 删除节点
|
||||
it('delete', () => {
|
||||
const firstButtonNode = designer.currentDocument?.getNode('node_k1ow3cbn')!;
|
||||
const secondButtonNode = designer.currentDocument?.getNode('node_k1ow3cbp')!;
|
||||
firstButtonNode.select();
|
||||
|
||||
expect(secondButtonNode.prevSibling.id).toBe('node_k1ow3cbn');
|
||||
|
||||
let event = new KeyboardEvent('keydown', { keyCode: 46 });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
expect(secondButtonNode.prevSibling).toBeNull;
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,105 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/clonedeep';
|
||||
import '../../fixtures/window';
|
||||
import { Editor } from '@ali/lowcode-editor-core';
|
||||
import { Project } from '../../../src/project/project';
|
||||
import { Node } from '../../../src/document/node/node';
|
||||
import { Designer } from '../../../src/designer/designer';
|
||||
import formSchema from '../../../fixtures/schema/form';
|
||||
import settingSchema from '../../fixtures/schema/setting';
|
||||
import divMeta from '../../fixtures/prototype/div-meta';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils';
|
||||
|
||||
const editor = new Editor();
|
||||
|
||||
describe('setting-prop-entry 测试', () => {
|
||||
let designer: Designer;
|
||||
beforeEach(() => {
|
||||
designer = new Designer({ editor });
|
||||
});
|
||||
afterEach(() => {
|
||||
designer._componentMetasMap.clear();
|
||||
designer = null;
|
||||
});
|
||||
|
||||
describe('node 构造函数生成 settingEntry', () => {
|
||||
it('常规方法测试', () => {
|
||||
designer.createComponentMeta(divMeta);
|
||||
designer.project.open(settingSchema);
|
||||
const { currentDocument } = designer.project;
|
||||
const divNode = currentDocument?.getNode('div');
|
||||
|
||||
const { settingEntry } = divNode!;
|
||||
const behaviorProp = settingEntry.getProp('behavior');
|
||||
expect(behaviorProp.getProps()).toBe(settingEntry);
|
||||
expect(behaviorProp.props).toBe(settingEntry);
|
||||
expect(behaviorProp.getName()).toBe('behavior');
|
||||
expect(behaviorProp.getKey()).toBe('behavior');
|
||||
expect(behaviorProp.isIgnore()).toBeFalsy;
|
||||
behaviorProp.setKey('behavior2');
|
||||
expect(behaviorProp.getKey()).toBe('behavior2');
|
||||
behaviorProp.setKey('behavior');
|
||||
expect(behaviorProp.getValue()).toBe('NORMAL');
|
||||
expect(behaviorProp.getMockOrValue()).toBe('NORMAL');
|
||||
|
||||
behaviorProp.setValue('LARGE');
|
||||
expect(behaviorProp.getValue()).toBe('LARGE');
|
||||
// behaviorProp.setPropValue('behavior', 'SMALL');
|
||||
// expect(behaviorProp.getValue()).toBe('SMALL');
|
||||
behaviorProp.setValue('NORMAL');
|
||||
expect(behaviorProp.getValue()).toBe('NORMAL');
|
||||
|
||||
behaviorProp.clearValue();
|
||||
behaviorProp.clearPropValue();
|
||||
expect(settingEntry.getProp('behavior').getValue()).toBeUndefined;
|
||||
|
||||
behaviorProp.setValue('LARGE');
|
||||
expect(behaviorProp.getValue()).toBe('LARGE');
|
||||
behaviorProp.remove();
|
||||
expect(settingEntry.getProp('behavior').getValue()).toBeUndefined;
|
||||
|
||||
expect(behaviorProp.getNode()).toBe(divNode);
|
||||
expect(behaviorProp.getId().startsWith('entry')).toBeTruthy;
|
||||
expect(behaviorProp.designer).toBe(designer);
|
||||
expect(behaviorProp.isSingle).toBeTruthy;
|
||||
expect(behaviorProp.isMultiple).toBeFalsy;
|
||||
expect(behaviorProp.isGroup).toBeFalsy;
|
||||
expect(behaviorProp.isSameComponent).toBeTruthy;
|
||||
expect(typeof settingEntry.getValue).toBe('function');
|
||||
settingEntry.getValue();
|
||||
|
||||
behaviorProp.setExtraPropValue('extraPropA', 'heihei');
|
||||
expect(behaviorProp.getExtraPropValue('extraPropA', 'heihei'));
|
||||
});
|
||||
|
||||
it.skip('type: group 场景测试', () => {
|
||||
|
||||
});
|
||||
|
||||
it('JSExpression 类型的 prop', () => {
|
||||
designer.createComponentMeta(divMeta);
|
||||
designer.project.open(settingSchema);
|
||||
const { currentDocument } = designer.project;
|
||||
const divNode = currentDocument?.getNode('div');
|
||||
|
||||
const { settingEntry } = divNode!;
|
||||
const customClassNameProp = settingEntry.getProp('customClassName');
|
||||
expect(customClassNameProp.isUseVariable()).toBeTruthy;
|
||||
expect(customClassNameProp.useVariable).toBeTruthy;
|
||||
|
||||
expect(customClassNameProp.getValue()).toEqual({
|
||||
type: 'JSExpression',
|
||||
value: 'getFromSomewhere()'
|
||||
});
|
||||
expect(customClassNameProp.getMockOrValue()).toBeUndefined;
|
||||
expect(customClassNameProp.getVariableValue()).toBe('getFromSomewhere()');
|
||||
customClassNameProp.setVariableValue('xxx');
|
||||
expect(customClassNameProp.getVariableValue()).toBe('xxx');
|
||||
|
||||
const customClassName2Prop = settingEntry.getProp('customClassName2');
|
||||
expect(customClassName2Prop.getMockOrValue()).toEqual({
|
||||
hi: 'mock',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,174 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/clonedeep';
|
||||
import '../../fixtures/window';
|
||||
import { Editor } from '@ali/lowcode-editor-core';
|
||||
import { Project } from '../../../src/project/project';
|
||||
import { Node } from '../../../src/document/node/node';
|
||||
import { Designer } from '../../../src/designer/designer';
|
||||
import formSchema from '../../fixtures/schema/form';
|
||||
import settingSchema from '../../fixtures/schema/setting';
|
||||
import divMeta from '../../fixtures/prototype/div-meta';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils';
|
||||
|
||||
const editor = new Editor();
|
||||
|
||||
describe('setting-top-entry 测试', () => {
|
||||
let designer: Designer;
|
||||
beforeEach(() => {
|
||||
designer = new Designer({ editor });
|
||||
});
|
||||
afterEach(() => {
|
||||
designer._componentMetasMap.clear();
|
||||
designer = null;
|
||||
});
|
||||
|
||||
describe('node 构造函数生成 settingEntry', () => {
|
||||
it('常规方法测试', () => {
|
||||
designer.createComponentMeta(divMeta);
|
||||
designer.project.open(settingSchema);
|
||||
const { currentDocument } = designer.project;
|
||||
const divNode = currentDocument?.getNode('div');
|
||||
|
||||
const { settingEntry } = divNode!;
|
||||
expect(settingEntry.getPropValue('behavior')).toBe('NORMAL');
|
||||
expect(settingEntry.getProp('behavior').getValue()).toBe('NORMAL');
|
||||
settingEntry.setPropValue('behavior', 'LARGE');
|
||||
expect(settingEntry.getPropValue('behavior')).toBe('LARGE');
|
||||
expect(settingEntry.get('behavior').getValue()).toBe('LARGE');
|
||||
settingEntry.getProp('behavior').setValue('SMALL');
|
||||
expect(settingEntry.getPropValue('behavior')).toBe('SMALL');
|
||||
settingEntry.clearPropValue('behavior');
|
||||
expect(settingEntry.getPropValue('behavior')).toBeUndefined;
|
||||
|
||||
expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o');
|
||||
settingEntry.setPropValue('fieldId', 'div_k1ow3h1o_new');
|
||||
expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o_new');
|
||||
|
||||
expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha');
|
||||
settingEntry.setExtraPropValue('extraPropA', 'haha2');
|
||||
expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha2');
|
||||
|
||||
settingEntry.mergeProps({
|
||||
newPropA: 'haha',
|
||||
});
|
||||
expect(settingEntry.getPropValue('newPropA')).toBe('haha');
|
||||
settingEntry.setProps({
|
||||
newPropB: 'haha',
|
||||
});
|
||||
expect(settingEntry.getPropValue('newPropB')).toBe('haha');
|
||||
settingEntry.setValue({
|
||||
newPropC: 'haha',
|
||||
});
|
||||
expect(settingEntry.getPropValue('newPropC')).toBe('haha');
|
||||
|
||||
expect(settingEntry.getPage()).toBe(currentDocument);
|
||||
expect(settingEntry.getNode()).toBe(divNode);
|
||||
expect(settingEntry.node).toBe(divNode);
|
||||
expect(settingEntry.getId()).toBe('div');
|
||||
expect(settingEntry.first).toBe(divNode);
|
||||
expect(settingEntry.designer).toBe(designer);
|
||||
expect(settingEntry.isSingle).toBeTruthy;
|
||||
expect(settingEntry.isMultiple).toBeFalsy;
|
||||
expect(settingEntry.isSameComponent).toBeTruthy;
|
||||
|
||||
expect(typeof settingEntry.getValue).toBe('function');
|
||||
settingEntry.getValue();
|
||||
});
|
||||
|
||||
it('清理方法测试', () => {
|
||||
designer.createComponentMeta(divMeta);
|
||||
designer.project.open(settingSchema);
|
||||
const { currentDocument } = designer.project;
|
||||
const divNode = currentDocument?.getNode('div');
|
||||
|
||||
const { settingEntry } = divNode!;
|
||||
expect(settingEntry.items).toHaveLength(3);
|
||||
settingEntry.purge();
|
||||
expect(settingEntry.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('vision 兼容测试', () => {
|
||||
designer.createComponentMeta(divMeta);
|
||||
designer.project.open(settingSchema);
|
||||
const { currentDocument } = designer.project;
|
||||
const divNode = currentDocument?.getNode('div');
|
||||
|
||||
console.log(divNode?.getPropValue('behavior'));
|
||||
const { settingEntry } = divNode!;
|
||||
|
||||
expect(typeof settingEntry.getChildren).toBe('function');
|
||||
expect(typeof settingEntry.getDOMNode).toBe('function');
|
||||
expect(typeof settingEntry.getStatus).toBe('function');
|
||||
expect(typeof settingEntry.setStatus).toBe('function');
|
||||
settingEntry.getStatus();
|
||||
settingEntry.setStatus();
|
||||
settingEntry.getChildren();
|
||||
settingEntry.getDOMNode();
|
||||
});
|
||||
|
||||
it('没有 node', () => {
|
||||
const create1 = designer.createSettingEntry.bind(designer);
|
||||
const create2 = designer.createSettingEntry.bind(designer, []);
|
||||
expect(create1).toThrowError('nodes should not be empty');
|
||||
expect(create2).toThrowError('nodes should not be empty');
|
||||
});
|
||||
});
|
||||
|
||||
describe('designer.createSettingEntry 生成 settingEntry(多 node 场景)', () => {
|
||||
it('相同类型的 node', () => {
|
||||
designer.project.open(settingSchema);
|
||||
const { currentDocument } = designer.project;
|
||||
const divNode = currentDocument?.getNode('div');
|
||||
const divNode2 = currentDocument?.getNode('div2');
|
||||
const settingEntry = designer.createSettingEntry([divNode, divNode2]);
|
||||
|
||||
expect(settingEntry.isMultiple).toBeTruthy;
|
||||
expect(settingEntry.isSameComponent).toBeTruthy;
|
||||
expect(settingEntry.isSingle).toBeFalsy;
|
||||
|
||||
expect(settingEntry.getPropValue('behavior')).toBe('NORMAL');
|
||||
expect(settingEntry.getProp('behavior').getValue()).toBe('NORMAL');
|
||||
settingEntry.setPropValue('behavior', 'LARGE');
|
||||
expect(settingEntry.getPropValue('behavior')).toBe('LARGE');
|
||||
expect(settingEntry.get('behavior').getValue()).toBe('LARGE');
|
||||
// 多个 node 都被成功设值
|
||||
expect(divNode?.getPropValue('behavior')).toBe('LARGE');
|
||||
expect(divNode2?.getPropValue('behavior')).toBe('LARGE');
|
||||
|
||||
settingEntry.getProp('behavior').setValue('SMALL');
|
||||
expect(settingEntry.getPropValue('behavior')).toBe('SMALL');
|
||||
// 多个 node 都被成功设值
|
||||
expect(divNode?.getPropValue('behavior')).toBe('SMALL');
|
||||
expect(divNode2?.getPropValue('behavior')).toBe('SMALL');
|
||||
|
||||
settingEntry.clearPropValue('behavior');
|
||||
expect(settingEntry.getPropValue('behavior')).toBeUndefined;
|
||||
// 多个 node 都被成功设值
|
||||
expect(divNode?.getPropValue('behavior')).toBeUndefined;
|
||||
expect(divNode2?.getPropValue('behavior')).toBeUndefined;
|
||||
|
||||
expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o');
|
||||
settingEntry.setPropValue('fieldId', 'div_k1ow3h1o_new');
|
||||
expect(settingEntry.getPropValue('fieldId')).toBe('div_k1ow3h1o_new');
|
||||
|
||||
expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha');
|
||||
settingEntry.setExtraPropValue('extraPropA', 'haha2');
|
||||
expect(settingEntry.getExtraPropValue('extraPropA')).toBe('haha2');
|
||||
});
|
||||
|
||||
it('不同类型的 node', () => {
|
||||
designer.project.open(settingSchema);
|
||||
const { currentDocument } = designer.project;
|
||||
const divNode = currentDocument?.getNode('div');
|
||||
const testNode = currentDocument?.getNode('test');
|
||||
const settingEntry = designer.createSettingEntry([divNode, testNode]);
|
||||
|
||||
expect(settingEntry.isMultiple).toBeTruthy;
|
||||
expect(settingEntry.isSameComponent).toBeFalsy;
|
||||
expect(settingEntry.isSingle).toBeFalsy;
|
||||
|
||||
// 不同类型的 node 场景下,理论上从页面上已没有修改属性的方法调用,所以此处不再断言各设值方法
|
||||
// 思考:假如以后面向其他场景,比如用户用 API 强行调用,是否需要做健壮性保护?
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import '../../fixtures/window';
|
||||
window.matchMedia('width=600px');
|
||||
import { DocumentModel } from '../../../src/document/document-model';
|
||||
// const { DocumentModel } = require('../../../src/document/document-model');
|
||||
// const { Node } = require('../__mocks__/node');
|
||||
|
||||
describe.skip('basic utility', () => {
|
||||
test('delegateMethod - useOriginMethodName', () => {
|
||||
|
||||
const node = new DocumentModel({}, {
|
||||
componentName: 'Component',
|
||||
});
|
||||
console.log(node);
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
});
|
||||
28
packages/designer/tests/document/document-model/node.test.ts
Normal file
28
packages/designer/tests/document/document-model/node.test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import '../../fixtures/window';
|
||||
import { DocumentModel } from '../../../src/document/document-model';
|
||||
import { Node } from '../../../src/document/node/node';
|
||||
// import { Node2 } from './__mocks__/node';
|
||||
|
||||
jest.mock('../../../src/document/document-model', () => {
|
||||
return {
|
||||
DocumentModel: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
project: {
|
||||
designer: { createSettingEntry() {}, transformProps() {} },
|
||||
getSchema() {},
|
||||
},
|
||||
nextId() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe.skip('basic utility', () => {
|
||||
test('delegateMethod - useOriginMethodName', () => {
|
||||
const dm = new DocumentModel({} as any, {} as any);
|
||||
console.log(dm.nextId);
|
||||
const node = new Node(dm, { componentName: 'Leaf' });
|
||||
console.log(node);
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
});
|
||||
245
packages/designer/tests/document/selection.test.ts
Normal file
245
packages/designer/tests/document/selection.test.ts
Normal file
@ -0,0 +1,245 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import '../fixtures/window';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getComponentMeta() {
|
||||
return {
|
||||
getMetadata() {
|
||||
return { experimental: null };
|
||||
},
|
||||
};
|
||||
},
|
||||
transformProps(props) { return props; },
|
||||
createSettingEntry: mockCreateSettingEntry,
|
||||
postEvent() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
describe('选择区测试', () => {
|
||||
it('常规方法', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
const selectionChangeHandler = jest.fn();
|
||||
selection.onSelectionChange(selectionChangeHandler);
|
||||
|
||||
selection.select('form');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selection.selected).toEqual(['form']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
selection.select('form');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
|
||||
expect(selection.selected).toEqual(['form']);
|
||||
|
||||
selection.select('node_k1ow3cbj');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['node_k1ow3cbj']);
|
||||
expect(selection.selected).toEqual(['node_k1ow3cbj']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
selection.selectAll(['node_k1ow3cbj', 'form']);
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['node_k1ow3cbj', 'form']);
|
||||
expect(selection.selected).toEqual(['node_k1ow3cbj', 'form']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
selection.remove('node_k1ow3cbj');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
|
||||
expect(selection.selected).toEqual(['form']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
selection.clear();
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual([]);
|
||||
expect(selection.selected).toEqual([]);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
// 无选中时调用 clear,不再触发事件
|
||||
selection.clear();
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
|
||||
expect(selection.selected).toEqual([]);
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
|
||||
it('add 方法', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
const selectionChangeHandler = jest.fn();
|
||||
selection.onSelectionChange(selectionChangeHandler);
|
||||
|
||||
selection.add('form');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
|
||||
expect(selection.selected).toEqual(['form']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
// 再加一次相同的节点,不触发事件
|
||||
selection.add('form');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
|
||||
expect(selection.selected).toEqual(['form']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
selection.add('form2');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form', 'form2']);
|
||||
expect(selection.selected).toEqual(['form', 'form2']);
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
|
||||
it('dispose 方法', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
|
||||
selection.selectAll(['form', 'node_k1ow3cbj', 'form2']);
|
||||
|
||||
const selectionChangeHandler = jest.fn();
|
||||
selection.onSelectionChange(selectionChangeHandler);
|
||||
selection.dispose();
|
||||
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form', 'node_k1ow3cbj']);
|
||||
expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
|
||||
it('dispose 方法', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
|
||||
selection.selectAll(['form', 'node_k1ow3cbj', 'form2']);
|
||||
|
||||
const selectionChangeHandler = jest.fn();
|
||||
selection.onSelectionChange(selectionChangeHandler);
|
||||
selection.dispose();
|
||||
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form', 'node_k1ow3cbj']);
|
||||
expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
|
||||
it('containsNode 方法', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
const selectionChangeHandler = jest.fn();
|
||||
selection.onSelectionChange(selectionChangeHandler);
|
||||
|
||||
selection.select('form');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
|
||||
expect(selection.selected).toEqual(['form']);
|
||||
expect(selection.has('form')).toBe(true);
|
||||
expect(selection.containsNode(currentDocument?.getNode('form'))).toBe(true);
|
||||
expect(selection.containsNode(currentDocument?.getNode('node_k1ow3cbj'))).toBe(true);
|
||||
expect(selection.containsNode(currentDocument?.getNode('node_k1ow3cb9'))).toBe(false);
|
||||
expect(selection.getNodes()).toEqual([currentDocument?.getNode('form')]);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
selection.add('node_k1ow3cbj');
|
||||
expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
|
||||
expect(selection.getTopNodes()).toEqual([currentDocument?.getNode('form')]);
|
||||
expect(selection.getTopNodes(true)).toEqual([currentDocument?.getNode('form')]);
|
||||
});
|
||||
|
||||
it('containsNode 方法 - excludeRoot: true', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
const selectionChangeHandler = jest.fn();
|
||||
selection.onSelectionChange(selectionChangeHandler);
|
||||
|
||||
selection.select('node_k1ow3cb9');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['node_k1ow3cb9']);
|
||||
expect(selection.selected).toEqual(['node_k1ow3cb9']);
|
||||
expect(selection.has('node_k1ow3cb9')).toBe(true);
|
||||
expect(selection.containsNode(currentDocument?.getNode('form'))).toBe(true);
|
||||
expect(selection.containsNode(currentDocument?.getNode('form'), true)).toBe(false);
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
|
||||
it('containsNode 方法 - excludeRoot: true', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
const selectionChangeHandler = jest.fn();
|
||||
const dispose = selection.onSelectionChange(selectionChangeHandler);
|
||||
|
||||
selection.select('form');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
// dispose 后,selected 会被赋值,但是变更事件不会被触发
|
||||
dispose();
|
||||
selection.select('node_k1ow3cb9');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(0);
|
||||
expect(selection.selected).toEqual(['node_k1ow3cb9']);
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
});
|
||||
275
packages/designer/tests/fixtures/component-metadata/div.ts
vendored
Normal file
275
packages/designer/tests/fixtures/component-metadata/div.ts
vendored
Normal file
@ -0,0 +1,275 @@
|
||||
export default {
|
||||
componentName: 'Div',
|
||||
title: '容器',
|
||||
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
|
||||
devMode: 'procode',
|
||||
tags: ['布局'],
|
||||
configure: {
|
||||
props: [
|
||||
{
|
||||
type: 'field',
|
||||
name: 'behavior',
|
||||
title: '默认状态',
|
||||
extraProps: {
|
||||
display: 'inline',
|
||||
defaultValue: 'NORMAL',
|
||||
},
|
||||
setter: {
|
||||
componentName: 'MixedSetter',
|
||||
props: {
|
||||
setters: [
|
||||
{
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
options: [
|
||||
{
|
||||
title: '普通',
|
||||
value: 'NORMAL',
|
||||
},
|
||||
{
|
||||
title: '隐藏',
|
||||
value: 'HIDDEN',
|
||||
},
|
||||
],
|
||||
loose: false,
|
||||
cancelable: false,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
'VariableSetter',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: '__style__',
|
||||
title: {
|
||||
label: '样式设置',
|
||||
tip: '点击 ? 查看样式设置器用法指南',
|
||||
docUrl: 'https://lark.alipay.com/legao/help/design-tool-style',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'accordion',
|
||||
defaultValue: {},
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
advanced: true,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupkgzzeo41',
|
||||
title: '高级',
|
||||
extraProps: {
|
||||
display: 'accordion',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'field',
|
||||
name: 'fieldId',
|
||||
title: {
|
||||
label: '唯一标识',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'block',
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
placeholder: '请输入唯一标识',
|
||||
multiline: false,
|
||||
rows: 10,
|
||||
required: false,
|
||||
pattern: null,
|
||||
maxLength: null,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'useFieldIdAsDomId',
|
||||
title: {
|
||||
label: '将唯一标识用作 DOM ID',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'block',
|
||||
defaultValue: false,
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'customClassName',
|
||||
title: '自定义样式类',
|
||||
extraProps: {
|
||||
display: 'block',
|
||||
defaultValue: '',
|
||||
},
|
||||
setter: {
|
||||
componentName: 'MixedSetter',
|
||||
props: {
|
||||
setters: [
|
||||
{
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
placeholder: null,
|
||||
multiline: false,
|
||||
rows: 10,
|
||||
required: false,
|
||||
pattern: null,
|
||||
maxLength: null,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
'VariableSetter',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'events',
|
||||
title: {
|
||||
label: '动作设置',
|
||||
tip: '点击 ? 查看如何设置组件的事件响应动作',
|
||||
docUrl: 'https://lark.alipay.com/legao/legao/events-call',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'accordion',
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
events: [
|
||||
{
|
||||
name: 'onClick',
|
||||
title: '当点击时',
|
||||
initialValue:
|
||||
"/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}",
|
||||
},
|
||||
{
|
||||
name: 'onMouseEnter',
|
||||
title: '当鼠标进入时',
|
||||
initialValue:
|
||||
"/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}",
|
||||
},
|
||||
{
|
||||
name: 'onMouseLeave',
|
||||
title: '当鼠标离开时',
|
||||
initialValue:
|
||||
"/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}",
|
||||
},
|
||||
],
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'onClick',
|
||||
extraProps: {
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: 'I18nSetter',
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'onMouseEnter',
|
||||
extraProps: {
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: 'I18nSetter',
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'onMouseLeave',
|
||||
extraProps: {
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: 'I18nSetter',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
component: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
parentWhitelist: 'Div',
|
||||
childWhitelist: 'Div',
|
||||
},
|
||||
},
|
||||
supports: {},
|
||||
},
|
||||
experimental: {
|
||||
callbacks: {},
|
||||
initials: [
|
||||
{
|
||||
name: 'behavior',
|
||||
},
|
||||
{
|
||||
name: '__style__',
|
||||
},
|
||||
{
|
||||
name: 'fieldId',
|
||||
},
|
||||
{
|
||||
name: 'useFieldIdAsDomId',
|
||||
},
|
||||
{
|
||||
name: 'customClassName',
|
||||
},
|
||||
{
|
||||
name: 'events',
|
||||
},
|
||||
{
|
||||
name: 'onClick',
|
||||
},
|
||||
{
|
||||
name: 'onMouseEnter',
|
||||
},
|
||||
{
|
||||
name: 'onMouseLeave',
|
||||
},
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
name: 'events',
|
||||
},
|
||||
{
|
||||
name: 'onClick',
|
||||
},
|
||||
{
|
||||
name: 'onMouseEnter',
|
||||
},
|
||||
{
|
||||
name: 'onMouseLeave',
|
||||
},
|
||||
],
|
||||
autoruns: [],
|
||||
},
|
||||
};
|
||||
3
packages/designer/tests/fixtures/disable-raf.ts
vendored
Normal file
3
packages/designer/tests/fixtures/disable-raf.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
Object.defineProperty(window, 'requestAnimationFrame', {
|
||||
value: null,
|
||||
})
|
||||
259
packages/designer/tests/fixtures/prototype/div-meta.ts
vendored
Normal file
259
packages/designer/tests/fixtures/prototype/div-meta.ts
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
export default {
|
||||
componentName: 'Div',
|
||||
title: '容器',
|
||||
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
|
||||
devMode: 'procode',
|
||||
tags: ['布局'],
|
||||
configure: {
|
||||
props: [
|
||||
{
|
||||
type: 'field',
|
||||
name: 'behavior',
|
||||
title: '默认状态',
|
||||
extraProps: {
|
||||
display: 'inline',
|
||||
defaultValue: 'NORMAL',
|
||||
},
|
||||
setter: {
|
||||
componentName: 'MixedSetter',
|
||||
props: {
|
||||
setters: [
|
||||
{
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
options: [
|
||||
{
|
||||
title: '普通',
|
||||
value: 'NORMAL',
|
||||
},
|
||||
{
|
||||
title: '隐藏',
|
||||
value: 'HIDDEN',
|
||||
},
|
||||
],
|
||||
loose: false,
|
||||
cancelable: false,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
'VariableSetter',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: '__style__',
|
||||
title: {
|
||||
label: '样式设置',
|
||||
tip: '点击 ? 查看样式设置器用法指南',
|
||||
docUrl: 'https://lark.alipay.com/legao/help/design-tool-style',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'accordion',
|
||||
defaultValue: {},
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
advanced: true,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupkh97h5kc',
|
||||
title: '高级',
|
||||
extraProps: {
|
||||
display: 'accordion',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'field',
|
||||
name: 'fieldId',
|
||||
title: {
|
||||
label: '唯一标识',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'block',
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
placeholder: '请输入唯一标识',
|
||||
multiline: false,
|
||||
rows: 10,
|
||||
required: false,
|
||||
pattern: null,
|
||||
maxLength: null,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'useFieldIdAsDomId',
|
||||
title: {
|
||||
label: '将唯一标识用作 DOM ID',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'block',
|
||||
defaultValue: false,
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'customClassName',
|
||||
title: '自定义样式类',
|
||||
extraProps: {
|
||||
display: 'block',
|
||||
defaultValue: '',
|
||||
},
|
||||
setter: {
|
||||
componentName: 'MixedSetter',
|
||||
props: {
|
||||
setters: [
|
||||
{
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
placeholder: null,
|
||||
multiline: false,
|
||||
rows: 10,
|
||||
required: false,
|
||||
pattern: null,
|
||||
maxLength: null,
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
'VariableSetter',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'events',
|
||||
title: {
|
||||
label: '动作设置',
|
||||
tip: '点击 ? 查看如何设置组件的事件响应动作',
|
||||
docUrl: 'https://lark.alipay.com/legao/legao/events-call',
|
||||
},
|
||||
extraProps: {
|
||||
display: 'accordion',
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: {
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
events: [
|
||||
{
|
||||
name: 'onClick',
|
||||
title: '当点击时',
|
||||
initialValue:
|
||||
"/**\n * 容器 当点击时\n */\nfunction onClick(event) {\n console.log('onClick', event);\n}",
|
||||
},
|
||||
{
|
||||
name: 'onMouseEnter',
|
||||
title: '当鼠标进入时',
|
||||
initialValue:
|
||||
"/**\n * 容器 当鼠标进入时\n */\nfunction onMouseEnter(event) {\n console.log('onMouseEnter', event);\n}",
|
||||
},
|
||||
{
|
||||
name: 'onMouseLeave',
|
||||
title: '当鼠标离开时',
|
||||
initialValue:
|
||||
"/**\n * 容器 当鼠标离开时\n */\nfunction onMouseLeave(event) {\n console.log('onMouseLeave', event);\n}",
|
||||
},
|
||||
],
|
||||
},
|
||||
_owner: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'onClick',
|
||||
extraProps: {
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: 'I18nSetter',
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'onMouseEnter',
|
||||
extraProps: {
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: 'I18nSetter',
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
name: 'onMouseLeave',
|
||||
extraProps: {
|
||||
defaultValue: {
|
||||
ignored: true,
|
||||
},
|
||||
},
|
||||
setter: 'I18nSetter',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
component: {
|
||||
isContainer: true,
|
||||
nestingRule: {},
|
||||
},
|
||||
supports: {},
|
||||
},
|
||||
experimental: {
|
||||
callbacks: {},
|
||||
initials: [
|
||||
{
|
||||
name: 'behavior',
|
||||
},
|
||||
{
|
||||
name: '__style__',
|
||||
},
|
||||
{
|
||||
name: 'fieldId',
|
||||
},
|
||||
{
|
||||
name: 'useFieldIdAsDomId',
|
||||
},
|
||||
{
|
||||
name: 'customClassName',
|
||||
},
|
||||
{
|
||||
name: 'events',
|
||||
},
|
||||
{
|
||||
name: 'onClick',
|
||||
},
|
||||
{
|
||||
name: 'onMouseEnter',
|
||||
},
|
||||
{
|
||||
name: 'onMouseLeave',
|
||||
},
|
||||
],
|
||||
filters: [],
|
||||
autoruns: [],
|
||||
},
|
||||
};
|
||||
983
packages/designer/tests/fixtures/schema/form.ts
vendored
Normal file
983
packages/designer/tests/fixtures/schema/form.ts
vendored
Normal file
@ -0,0 +1,983 @@
|
||||
export default {
|
||||
componentName: 'Page',
|
||||
id: 'node_k1ow3cb9',
|
||||
title: 'hey, i\' a page!',
|
||||
props: {
|
||||
extensions: {
|
||||
启用页头: true,
|
||||
},
|
||||
pageStyle: {
|
||||
backgroundColor: '#f2f3f5',
|
||||
},
|
||||
containerStyle: {},
|
||||
className: 'page_kgaqfbm4',
|
||||
templateVersion: '1.0.0',
|
||||
},
|
||||
lifeCycles: {
|
||||
constructor: {
|
||||
type: 'js',
|
||||
compiled:
|
||||
"function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
|
||||
source:
|
||||
"function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
css:
|
||||
'body{background-color:#f2f3f5}.card_kgaqfbm5 {\n margin-bottom: 12px;\n}.card_kgaqfbm6 {\n margin-bottom: 12px;\n}.button_kgaqfbm7 {\n margin-right: 16px;\n width: 80px\n}.button_kgaqfbm8 {\n width: 80px;\n}.div_kgaqfbm9 {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
|
||||
methods: {
|
||||
__initMethods__: {
|
||||
type: 'js',
|
||||
source: 'function (exports, module) { /*set actions code here*/ }',
|
||||
compiled: 'function (exports, module) { /*set actions code here*/ }',
|
||||
},
|
||||
},
|
||||
dataSource: {
|
||||
offline: [],
|
||||
globalConfig: {
|
||||
fit: {
|
||||
compiled: '',
|
||||
source: '',
|
||||
type: 'js',
|
||||
error: {},
|
||||
},
|
||||
},
|
||||
online: [],
|
||||
sync: true,
|
||||
list: [],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
componentName: 'RootHeader',
|
||||
id: 'node_k1ow3cba',
|
||||
props: {},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'PageHeader',
|
||||
id: 'node_k1ow3cbd',
|
||||
props: {
|
||||
extraContent: '',
|
||||
__slot__extraContent: false,
|
||||
__slot__action: false,
|
||||
title: {
|
||||
// type: 'JSSlot',
|
||||
value: [
|
||||
{
|
||||
componentName: 'Text',
|
||||
id: 'node_k1ow3cbf',
|
||||
props: {
|
||||
showTitle: false,
|
||||
behavior: 'NORMAL',
|
||||
content: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'Title',
|
||||
zh_CN: '个人信息',
|
||||
type: 'i18n',
|
||||
},
|
||||
__style__: {},
|
||||
fieldId: 'text_k1ow3h1j',
|
||||
maxLine: 0,
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
content: '',
|
||||
__slot__logo: false,
|
||||
__slot__crumb: false,
|
||||
crumb: '',
|
||||
tab: '',
|
||||
logo: '',
|
||||
action: '',
|
||||
__slot__tab: false,
|
||||
__style__: {},
|
||||
__slot__content: false,
|
||||
fieldId: 'pageHeader_k1ow3h1i',
|
||||
subTitle: false,
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
componentName: 'RootContent',
|
||||
id: 'node_k1ow3cbb',
|
||||
props: {
|
||||
contentBgColor: 'transparent',
|
||||
contentPadding: '0',
|
||||
contentMargin: '20',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'Form',
|
||||
id: 'form',
|
||||
extraPropA: 'extraPropA',
|
||||
props: {
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
autoValidate: true,
|
||||
scrollToFirstError: true,
|
||||
autoUnmount: true,
|
||||
behavior: 'NORMAL',
|
||||
dataSource: {
|
||||
type: 'variable',
|
||||
variable: 'state.formData',
|
||||
},
|
||||
obj: {
|
||||
a: 1,
|
||||
b: false,
|
||||
c: 'string',
|
||||
},
|
||||
__style__: {},
|
||||
fieldId: 'form',
|
||||
fieldOptions: {},
|
||||
slotA: '',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'Card',
|
||||
id: 'node_k1ow3cbj',
|
||||
props: {
|
||||
__slot__title: false,
|
||||
subTitle: {
|
||||
use: 'zh_CN',
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
__slot__subTitle: false,
|
||||
extra: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
className: 'card_kgaqfbm5',
|
||||
title: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'Title',
|
||||
zh_CN: '基本信息',
|
||||
type: 'i18n',
|
||||
},
|
||||
__slot__extra: false,
|
||||
showHeadDivider: true,
|
||||
__style__: ':root {\n margin-bottom: 12px;\n}',
|
||||
showTitleBullet: true,
|
||||
contentHeight: '',
|
||||
fieldId: 'card_k1ow3h1l',
|
||||
dividerNoInset: false,
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'CardContent',
|
||||
id: 'node_k1ow3cbk',
|
||||
props: {},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'ColumnsLayout',
|
||||
id: 'node_k1ow3cbw',
|
||||
props: {
|
||||
layout: '6:6',
|
||||
columnGap: '20',
|
||||
rowGap: 0,
|
||||
__style__: {},
|
||||
fieldId: 'columns_k1ow3h1v',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'Column',
|
||||
id: 'node_k1ow3cbx',
|
||||
props: {
|
||||
colSpan: '',
|
||||
__style__: {},
|
||||
fieldId: 'column_k1p1bnjm',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'TextField',
|
||||
id: 'node_k1ow3cbz',
|
||||
props: {
|
||||
fieldName: 'name',
|
||||
hasClear: false,
|
||||
autoFocus: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
trim: false,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please input',
|
||||
zh_CN: '请输入',
|
||||
type: 'i18n',
|
||||
},
|
||||
state: '',
|
||||
behavior: 'NORMAL',
|
||||
value: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
addonBefore: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'required',
|
||||
},
|
||||
],
|
||||
hasLimitHint: false,
|
||||
cutString: false,
|
||||
__style__: {},
|
||||
fieldId: 'textField_k1ow3h1w',
|
||||
htmlType: 'input',
|
||||
autoHeight: false,
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'TextField',
|
||||
zh_CN: '姓名',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
rows: 4,
|
||||
addonAfter: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
wrapperColOffset: 0,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
componentName: 'TextField',
|
||||
id: 'node_k1ow3cc1',
|
||||
props: {
|
||||
fieldName: 'englishName',
|
||||
hasClear: false,
|
||||
autoFocus: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
trim: false,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please input',
|
||||
zh_CN: '请输入',
|
||||
type: 'i18n',
|
||||
},
|
||||
state: '',
|
||||
behavior: 'NORMAL',
|
||||
value: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
addonBefore: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
validation: [],
|
||||
hasLimitHint: false,
|
||||
cutString: false,
|
||||
__style__: {},
|
||||
fieldId: 'textField_k1ow3h1y',
|
||||
htmlType: 'input',
|
||||
autoHeight: false,
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'TextField',
|
||||
zh_CN: '英文名',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
rows: 4,
|
||||
addonAfter: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
wrapperColOffset: 0,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
componentName: 'TextField',
|
||||
id: 'node_k1ow3cc3',
|
||||
props: {
|
||||
fieldName: 'jobTitle',
|
||||
hasClear: false,
|
||||
autoFocus: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
trim: false,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please input',
|
||||
zh_CN: '请输入',
|
||||
type: 'i18n',
|
||||
},
|
||||
state: '',
|
||||
behavior: 'NORMAL',
|
||||
value: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
addonBefore: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
validation: [],
|
||||
hasLimitHint: false,
|
||||
cutString: false,
|
||||
__style__: {},
|
||||
fieldId: 'textField_k1ow3h20',
|
||||
htmlType: 'input',
|
||||
autoHeight: false,
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'TextField',
|
||||
zh_CN: '职位',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
rows: 4,
|
||||
addonAfter: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
wrapperColOffset: 0,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
componentName: 'Column',
|
||||
id: 'node_k1ow3cby',
|
||||
props: {
|
||||
colSpan: '',
|
||||
__style__: {},
|
||||
fieldId: 'column_k1p1bnjn',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'TextField',
|
||||
id: 'node_k1ow3cc2',
|
||||
props: {
|
||||
fieldName: 'nickName',
|
||||
hasClear: false,
|
||||
autoFocus: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
trim: false,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please input',
|
||||
zh_CN: '请输入',
|
||||
type: 'i18n',
|
||||
},
|
||||
state: '',
|
||||
behavior: 'NORMAL',
|
||||
value: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
addonBefore: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
validation: [],
|
||||
hasLimitHint: false,
|
||||
cutString: false,
|
||||
__style__: {},
|
||||
fieldId: 'textField_k1ow3h1z',
|
||||
htmlType: 'input',
|
||||
autoHeight: false,
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'TextField',
|
||||
zh_CN: '花名',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
rows: 4,
|
||||
addonAfter: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
wrapperColOffset: 0,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
componentName: 'SelectField',
|
||||
id: 'node_k1ow3cc0',
|
||||
props: {
|
||||
fieldName: 'gender',
|
||||
hasClear: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
mode: 'single',
|
||||
showSearch: false,
|
||||
autoWidth: true,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please select',
|
||||
zh_CN: '请选择',
|
||||
type: 'i18n',
|
||||
},
|
||||
hasBorder: true,
|
||||
behavior: 'NORMAL',
|
||||
value: '',
|
||||
validation: [
|
||||
{
|
||||
type: 'required',
|
||||
},
|
||||
],
|
||||
__style__: {},
|
||||
fieldId: 'select_k1ow3h1x',
|
||||
notFoundContent: {
|
||||
use: 'zh_CN',
|
||||
type: 'i18n',
|
||||
},
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'SelectField',
|
||||
zh_CN: '性别',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
wrapperColOffset: 0,
|
||||
hasSelectAll: false,
|
||||
hasArrow: true,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
filterLocal: true,
|
||||
dataSource: [
|
||||
{
|
||||
defaultChecked: false,
|
||||
text: {
|
||||
en_US: 'Option 1',
|
||||
zh_CN: '男',
|
||||
type: 'i18n',
|
||||
__sid__: 'param_k1owc4tb',
|
||||
},
|
||||
__sid__: 'serial_k1owc4t1',
|
||||
value: 'M',
|
||||
sid: 'opt_k1owc4t2',
|
||||
},
|
||||
{
|
||||
defaultChecked: false,
|
||||
text: {
|
||||
en_US: 'Option 2',
|
||||
zh_CN: '女',
|
||||
type: 'i18n',
|
||||
__sid__: 'param_k1owc4tf',
|
||||
},
|
||||
__sid__: 'serial_k1owc4t2',
|
||||
value: 'F',
|
||||
sid: 'opt_k1owc4t3',
|
||||
},
|
||||
],
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
searchDelay: 300,
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
componentName: 'Card',
|
||||
id: 'node_k1ow3cbl',
|
||||
props: {
|
||||
__slot__title: false,
|
||||
subTitle: {
|
||||
use: 'zh_CN',
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
__slot__subTitle: false,
|
||||
extra: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
className: 'card_kgaqfbm6',
|
||||
title: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'Title',
|
||||
zh_CN: '部门信息',
|
||||
type: 'i18n',
|
||||
},
|
||||
__slot__extra: false,
|
||||
showHeadDivider: true,
|
||||
__style__: ':root {\n margin-bottom: 12px;\n}',
|
||||
showTitleBullet: true,
|
||||
contentHeight: '',
|
||||
fieldId: 'card_k1ow3h1m',
|
||||
dividerNoInset: false,
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'CardContent',
|
||||
id: 'node_k1ow3cbm',
|
||||
props: {},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'TextField',
|
||||
id: 'node_k1ow3cc4',
|
||||
props: {
|
||||
fieldName: 'department',
|
||||
hasClear: false,
|
||||
autoFocus: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
trim: false,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please input',
|
||||
zh_CN: '请输入',
|
||||
type: 'i18n',
|
||||
},
|
||||
state: '',
|
||||
behavior: 'NORMAL',
|
||||
value: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
addonBefore: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
validation: [],
|
||||
hasLimitHint: false,
|
||||
cutString: false,
|
||||
__style__: {},
|
||||
fieldId: 'textField_k1ow3h21',
|
||||
htmlType: 'input',
|
||||
autoHeight: false,
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'TextField',
|
||||
zh_CN: '所属部门',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
rows: 4,
|
||||
addonAfter: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
wrapperColOffset: 0,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
componentName: 'ColumnsLayout',
|
||||
id: 'node_k1ow3cc5',
|
||||
props: {
|
||||
layout: '6:6',
|
||||
columnGap: '20',
|
||||
rowGap: 0,
|
||||
__style__: {},
|
||||
fieldId: 'columns_k1ow3h22',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'Column',
|
||||
id: 'node_k1ow3cc6',
|
||||
props: {
|
||||
colSpan: '',
|
||||
__style__: {},
|
||||
fieldId: 'column_k1p1bnjo',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'TextField',
|
||||
id: 'node_k1ow3cc8',
|
||||
props: {
|
||||
fieldName: 'leader',
|
||||
hasClear: false,
|
||||
autoFocus: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
trim: false,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please input',
|
||||
zh_CN: '请输入',
|
||||
type: 'i18n',
|
||||
},
|
||||
state: '',
|
||||
behavior: 'NORMAL',
|
||||
value: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
addonBefore: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
validation: [],
|
||||
hasLimitHint: false,
|
||||
cutString: false,
|
||||
__style__: {},
|
||||
fieldId: 'textField_k1ow3h23',
|
||||
htmlType: 'input',
|
||||
autoHeight: false,
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'TextField',
|
||||
zh_CN: '主管',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
rows: 4,
|
||||
addonAfter: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
wrapperColOffset: 0,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
componentName: 'Column',
|
||||
id: 'node_k1ow3cc7',
|
||||
props: {
|
||||
colSpan: '',
|
||||
__style__: {},
|
||||
fieldId: 'column_k1p1bnjp',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'TextField',
|
||||
id: 'node_k1ow3cc9',
|
||||
props: {
|
||||
fieldName: 'hrg',
|
||||
hasClear: false,
|
||||
autoFocus: false,
|
||||
tips: {
|
||||
en_US: '',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
trim: false,
|
||||
labelTextAlign: 'right',
|
||||
placeholder: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'please input',
|
||||
zh_CN: '请输入',
|
||||
type: 'i18n',
|
||||
},
|
||||
state: '',
|
||||
behavior: 'NORMAL',
|
||||
value: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
addonBefore: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
validation: [],
|
||||
hasLimitHint: false,
|
||||
cutString: false,
|
||||
__style__: {},
|
||||
fieldId: 'textField_k1ow3h24',
|
||||
htmlType: 'input',
|
||||
autoHeight: false,
|
||||
labelColOffset: 0,
|
||||
label: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'TextField',
|
||||
zh_CN: 'HRG',
|
||||
type: 'i18n',
|
||||
},
|
||||
__category__: 'form',
|
||||
labelColSpan: 4,
|
||||
wrapperColSpan: 0,
|
||||
rows: 4,
|
||||
addonAfter: {
|
||||
use: 'zh_CN',
|
||||
zh_CN: '',
|
||||
type: 'i18n',
|
||||
},
|
||||
wrapperColOffset: 0,
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
__useMediator: 'value',
|
||||
labelTipsTypes: 'none',
|
||||
labelTipsIcon: '',
|
||||
labelTipsText: {
|
||||
type: 'i18n',
|
||||
use: 'zh_CN',
|
||||
en_US: null,
|
||||
zh_CN: '',
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
componentName: 'Div',
|
||||
id: 'node_k1ow3cbo',
|
||||
props: {
|
||||
className: 'div_kgaqfbm9',
|
||||
behavior: 'NORMAL',
|
||||
__style__:
|
||||
':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
|
||||
events: {},
|
||||
fieldId: 'div_k1ow3h1o',
|
||||
useFieldIdAsDomId: false,
|
||||
customClassName: '',
|
||||
},
|
||||
condition: true,
|
||||
children: [
|
||||
{
|
||||
componentName: 'Button',
|
||||
id: 'node_k1ow3cbn',
|
||||
props: {
|
||||
triggerEventsWhenLoading: false,
|
||||
onClick: {
|
||||
rawType: 'events',
|
||||
type: 'JSExpression',
|
||||
value: 'this.utils.legaoBuiltin.execEventFlow.bind(this, [this.submit])',
|
||||
events: [
|
||||
{
|
||||
name: 'submit',
|
||||
id: 'submit',
|
||||
params: {},
|
||||
type: 'actionRef',
|
||||
uuid: '1570966253282_0',
|
||||
},
|
||||
],
|
||||
},
|
||||
size: 'medium',
|
||||
baseIcon: '',
|
||||
otherIcon: '',
|
||||
className: 'button_kgaqfbm7',
|
||||
type: 'primary',
|
||||
behavior: 'NORMAL',
|
||||
loading: false,
|
||||
content: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'Button',
|
||||
zh_CN: '提交',
|
||||
type: 'i18n',
|
||||
},
|
||||
__style__: ':root {\n margin-right: 16px;\n width: 80px\n}',
|
||||
fieldId: 'button_k1ow3h1n',
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
componentName: 'Button',
|
||||
id: 'node_k1ow3cbp',
|
||||
props: {
|
||||
triggerEventsWhenLoading: false,
|
||||
size: 'medium',
|
||||
baseIcon: '',
|
||||
otherIcon: '',
|
||||
className: 'button_kgaqfbm8',
|
||||
type: 'normal',
|
||||
behavior: 'NORMAL',
|
||||
loading: false,
|
||||
content: {
|
||||
use: 'zh_CN',
|
||||
en_US: 'Button',
|
||||
zh_CN: '取消',
|
||||
type: 'i18n',
|
||||
},
|
||||
__style__: ':root {\n width: 80px;\n}',
|
||||
fieldId: 'button_k1ow3h1p',
|
||||
greeting: {
|
||||
// type: 'JSSlot',
|
||||
value: [{
|
||||
componentName: 'Text',
|
||||
props: {},
|
||||
}]
|
||||
}
|
||||
},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
componentName: 'RootFooter',
|
||||
id: 'node_k1ow3cbc',
|
||||
props: {},
|
||||
condition: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
90
packages/designer/tests/fixtures/schema/setting.ts
vendored
Normal file
90
packages/designer/tests/fixtures/schema/setting.ts
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
export default {
|
||||
componentName: 'Page',
|
||||
id: 'node_k1ow3cb9',
|
||||
title: 'hey, i\' a page!',
|
||||
props: {
|
||||
extensions: {
|
||||
启用页头: true,
|
||||
},
|
||||
pageStyle: {
|
||||
backgroundColor: '#f2f3f5',
|
||||
},
|
||||
containerStyle: {},
|
||||
className: 'page_kgaqfbm4',
|
||||
templateVersion: '1.0.0',
|
||||
},
|
||||
lifeCycles: {
|
||||
constructor: {
|
||||
type: 'js',
|
||||
compiled:
|
||||
"function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
|
||||
source:
|
||||
"function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
|
||||
},
|
||||
},
|
||||
condition: true,
|
||||
css:
|
||||
'body{background-color:#f2f3f5}.card_kgaqfbm5 {\n margin-bottom: 12px;\n}.card_kgaqfbm6 {\n margin-bottom: 12px;\n}.button_kgaqfbm7 {\n margin-right: 16px;\n width: 80px\n}.button_kgaqfbm8 {\n width: 80px;\n}.div_kgaqfbm9 {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
|
||||
methods: {
|
||||
__initMethods__: {
|
||||
type: 'js',
|
||||
source: 'function (exports, module) { /*set actions code here*/ }',
|
||||
compiled: 'function (exports, module) { /*set actions code here*/ }',
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
componentName: 'Div',
|
||||
id: 'div',
|
||||
props: {
|
||||
className: 'div_kgaqfbm9',
|
||||
behavior: 'NORMAL',
|
||||
__style__:
|
||||
':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
|
||||
events: {},
|
||||
fieldId: 'div_k1ow3h1o',
|
||||
useFieldIdAsDomId: false,
|
||||
customClassName: {
|
||||
type: 'JSExpression',
|
||||
value: 'getFromSomewhere()'
|
||||
},
|
||||
customClassName2: {
|
||||
type: 'JSExpression',
|
||||
mock: { hi: 'mock' },
|
||||
value: 'getFromSomewhere()'
|
||||
},
|
||||
},
|
||||
extraPropA: 'haha',
|
||||
},
|
||||
{
|
||||
componentName: 'Div',
|
||||
id: 'div2',
|
||||
props: {
|
||||
className: 'div_kgaqfbm9',
|
||||
behavior: 'NORMAL',
|
||||
__style__:
|
||||
':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
|
||||
events: {},
|
||||
fieldId: 'div_k1ow3h1o',
|
||||
useFieldIdAsDomId: false,
|
||||
customClassName: '',
|
||||
},
|
||||
extraPropA: 'haha',
|
||||
},
|
||||
{
|
||||
componentName: 'Test',
|
||||
id: 'test',
|
||||
props: {
|
||||
className: 'div_kgaqfbm9',
|
||||
behavior: 'NORMAL',
|
||||
__style__:
|
||||
':root {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n background: #fff;\n padding: 20px 0;\n}',
|
||||
events: {},
|
||||
fieldId: 'div_k1ow3h1o',
|
||||
useFieldIdAsDomId: false,
|
||||
customClassName: '',
|
||||
},
|
||||
extraPropA: 'haha',
|
||||
}
|
||||
],
|
||||
};
|
||||
7
packages/designer/tests/fixtures/unhandled-rejection.ts
vendored
Normal file
7
packages/designer/tests/fixtures/unhandled-rejection.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
|
||||
process.on('unhandledRejection', reason => {
|
||||
throw reason;
|
||||
})
|
||||
// Avoid memory leak by adding too many listeners
|
||||
process.env.LISTENING_TO_UNHANDLED_REJECTION = true;
|
||||
}
|
||||
18
packages/designer/tests/fixtures/window.ts
vendored
Normal file
18
packages/designer/tests/fixtures/window.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
Object.defineProperty(window, 'React', {
|
||||
writable: true,
|
||||
value: {},
|
||||
});
|
||||
71
packages/designer/tests/meta/component-meta.test.ts
Normal file
71
packages/designer/tests/meta/component-meta.test.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import '../fixtures/window';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import divMeta from '../fixtures/component-metadata/div';
|
||||
import { ComponentMeta, isComponentMeta, removeBuiltinComponentAction, addBuiltinComponentAction } from '../../src/component-meta';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getGlobalComponentActions: () => [],
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
describe('组件元数据处理', () => {
|
||||
it('构造函数', () => {
|
||||
const meta = new ComponentMeta(designer, divMeta);
|
||||
expect(meta.isContainer).toBeTruthy;
|
||||
expect(isComponentMeta(meta)).toBeTruthy;
|
||||
expect(meta.acceptable).toBeTruthy;
|
||||
expect(meta.isRootComponent()).toBeTruthy;
|
||||
expect(meta.isModal).toBeFalsy;
|
||||
expect(meta.rootSelector).toBeUndefined;
|
||||
expect(meta.liveTextEditing).toBeUndefined;
|
||||
expect(meta.descriptor).toBeUndefined;
|
||||
expect(meta.icon).toBeUndefined;
|
||||
expect(meta.getMetadata().title).toBe('容器');
|
||||
expect(meta.title).toEqual({ type: 'i18n', 'en-US': 'Div', 'zh-CN': '容器' });
|
||||
|
||||
meta.setNpm({ package: '@ali/vc-div', componentName: 'Div' });
|
||||
expect(meta.npm).toEqual({ package: '@ali/vc-div', componentName: 'Div' });
|
||||
|
||||
meta.setMetadata(divMeta);
|
||||
});
|
||||
|
||||
it('availableActions', () => {
|
||||
const meta = new ComponentMeta(designer, divMeta);
|
||||
expect(meta.availableActions).toHaveLength(3);
|
||||
expect(meta.availableActions[0].name).toBe('remove');
|
||||
expect(meta.availableActions[1].name).toBe('hide');
|
||||
expect(meta.availableActions[2].name).toBe('copy');
|
||||
|
||||
removeBuiltinComponentAction('remove');
|
||||
// availableActions 有 computed 缓存
|
||||
expect(meta.availableActions[0].name).toBe('remove');
|
||||
expect(meta.availableActions[1].name).toBe('hide');
|
||||
expect(meta.availableActions[2].name).toBe('copy');
|
||||
|
||||
addBuiltinComponentAction({
|
||||
name: 'new',
|
||||
content: {
|
||||
action() {}
|
||||
}
|
||||
});
|
||||
// availableActions 有 computed 缓存
|
||||
expect(meta.availableActions).toHaveLength(3);
|
||||
expect(meta.availableActions[0].name).toBe('remove');
|
||||
expect(meta.availableActions[1].name).toBe('hide');
|
||||
expect(meta.availableActions[2].name).toBe('copy');
|
||||
});
|
||||
});
|
||||
564
packages/designer/tests/node/node.add.test.ts
Normal file
564
packages/designer/tests/node/node.add.test.ts
Normal file
@ -0,0 +1,564 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import '../fixtures/window';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
import { EBADF } from 'constants';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getComponentMeta() {
|
||||
return {
|
||||
getMetadata() {
|
||||
return { experimental: null };
|
||||
},
|
||||
};
|
||||
},
|
||||
transformProps(props) { return props; },
|
||||
createSettingEntry: mockCreateSettingEntry,
|
||||
postEvent() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
describe('schema 生成节点模型测试', () => {
|
||||
describe('block ❌ | component ❌ | slot ❌', () => {
|
||||
let project: Project;
|
||||
beforeEach(() => {
|
||||
project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
});
|
||||
afterEach(() => {
|
||||
project.unload();
|
||||
});
|
||||
it('基本的节点模型初始化,模型导出', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
expect(nodesMap.size).toBe(expectedNodeCnt);
|
||||
ids.forEach(id => {
|
||||
expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
|
||||
});
|
||||
|
||||
const pageNode = currentDocument?.getNode('node_k1ow3cb9');
|
||||
expect(pageNode?.getComponentName()).toBe('Page');
|
||||
expect(pageNode?.getIcon()).toBeUndefined;
|
||||
|
||||
const exportSchema = currentDocument?.export(1);
|
||||
expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
|
||||
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
|
||||
});
|
||||
|
||||
it('基本的节点模型初始化,节点深度', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const getNode = currentDocument.getNode.bind(currentDocument);
|
||||
|
||||
const pageNode = getNode('node_k1ow3cb9');
|
||||
const rootHeaderNode = getNode('node_k1ow3cba');
|
||||
const rootContentNode = getNode('node_k1ow3cbb');
|
||||
const rootFooterNode = getNode('node_k1ow3cbc');
|
||||
const formNode = getNode('form');
|
||||
const cardNode = getNode('node_k1ow3cbj');
|
||||
const cardContentNode = getNode('node_k1ow3cbk');
|
||||
const columnsLayoutNode = getNode('node_k1ow3cbw');
|
||||
const columnNode = getNode('node_k1ow3cbx');
|
||||
const textFieldNode = getNode('node_k1ow3cbz');
|
||||
|
||||
expect(pageNode?.zLevel).toBe(0);
|
||||
expect(rootHeaderNode?.zLevel).toBe(1);
|
||||
expect(rootContentNode?.zLevel).toBe(1);
|
||||
expect(rootFooterNode?.zLevel).toBe(1);
|
||||
expect(formNode?.zLevel).toBe(2);
|
||||
expect(cardNode?.zLevel).toBe(3);
|
||||
expect(cardContentNode?.zLevel).toBe(4);
|
||||
expect(columnsLayoutNode?.zLevel).toBe(5);
|
||||
expect(columnNode?.zLevel).toBe(6);
|
||||
expect(textFieldNode?.zLevel).toBe(7);
|
||||
|
||||
expect(textFieldNode?.getZLevelTop(7)).toEqual(textFieldNode);
|
||||
expect(textFieldNode?.getZLevelTop(6)).toEqual(columnNode);
|
||||
expect(textFieldNode?.getZLevelTop(5)).toEqual(columnsLayoutNode);
|
||||
expect(textFieldNode?.getZLevelTop(4)).toEqual(cardContentNode);
|
||||
expect(textFieldNode?.getZLevelTop(3)).toEqual(cardNode);
|
||||
expect(textFieldNode?.getZLevelTop(2)).toEqual(formNode);
|
||||
expect(textFieldNode?.getZLevelTop(1)).toEqual(rootContentNode);
|
||||
expect(textFieldNode?.getZLevelTop(0)).toEqual(pageNode);
|
||||
|
||||
// 异常情况
|
||||
expect(textFieldNode?.getZLevelTop(8)).toBeNull;
|
||||
expect(textFieldNode?.getZLevelTop(-1)).toBeNull;
|
||||
});
|
||||
|
||||
it('基本的节点模型初始化,节点父子、兄弟相关方法', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const getNode = currentDocument.getNode.bind(currentDocument);
|
||||
|
||||
const pageNode = getNode('node_k1ow3cb9');
|
||||
const rootHeaderNode = getNode('node_k1ow3cba');
|
||||
const rootContentNode = getNode('node_k1ow3cbb');
|
||||
const rootFooterNode = getNode('node_k1ow3cbc');
|
||||
const formNode = getNode('form');
|
||||
const cardNode = getNode('node_k1ow3cbj');
|
||||
const cardContentNode = getNode('node_k1ow3cbk');
|
||||
const columnsLayoutNode = getNode('node_k1ow3cbw');
|
||||
const columnNode = getNode('node_k1ow3cbx');
|
||||
const textFieldNode = getNode('node_k1ow3cbz');
|
||||
|
||||
expect(pageNode?.index).toBe(-1);
|
||||
expect(pageNode?.children.toString()).toBe('[object Array]');
|
||||
expect(pageNode?.children?.get(1)).toBe(rootContentNode);
|
||||
expect(pageNode?.getChildren()?.get(1)).toBe(rootContentNode);
|
||||
expect(pageNode?.getNode()).toBe(pageNode);
|
||||
|
||||
expect(rootFooterNode?.index).toBe(2);
|
||||
|
||||
expect(textFieldNode?.getParent()).toBe(columnNode);
|
||||
expect(columnNode?.getParent()).toBe(columnsLayoutNode);
|
||||
expect(columnsLayoutNode?.getParent()).toBe(cardContentNode);
|
||||
expect(cardContentNode?.getParent()).toBe(cardNode);
|
||||
expect(cardNode?.getParent()).toBe(formNode);
|
||||
expect(formNode?.getParent()).toBe(rootContentNode);
|
||||
expect(rootContentNode?.getParent()).toBe(pageNode);
|
||||
expect(rootContentNode?.prevSibling).toBe(rootHeaderNode);
|
||||
expect(rootContentNode?.nextSibling).toBe(rootFooterNode);
|
||||
|
||||
expect(pageNode?.isRoot()).toBe(true);
|
||||
expect(pageNode?.contains(textFieldNode)).toBe(true);
|
||||
expect(textFieldNode?.getRoot()).toBe(pageNode);
|
||||
expect(columnNode?.getRoot()).toBe(pageNode);
|
||||
expect(columnsLayoutNode?.getRoot()).toBe(pageNode);
|
||||
expect(cardContentNode?.getRoot()).toBe(pageNode);
|
||||
expect(cardNode?.getRoot()).toBe(pageNode);
|
||||
expect(formNode?.getRoot()).toBe(pageNode);
|
||||
expect(rootContentNode?.getRoot()).toBe(pageNode);
|
||||
});
|
||||
|
||||
it('基本的节点模型初始化,节点新建、删除等事件', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const getNode = currentDocument.getNode.bind(currentDocument);
|
||||
const createNode = currentDocument.createNode.bind(currentDocument);
|
||||
|
||||
const pageNode = getNode('node_k1ow3cb9');
|
||||
const nodeCreateHandler = jest.fn();
|
||||
currentDocument?.onNodeCreate(nodeCreateHandler);
|
||||
|
||||
const node = createNode({
|
||||
componentName: 'TextInput',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
}
|
||||
});
|
||||
currentDocument?.insertNode(pageNode, node);
|
||||
|
||||
expect(nodeCreateHandler).toHaveBeenCalledTimes(1);
|
||||
expect(nodeCreateHandler.mock.calls[0][0]).toBe(node);
|
||||
expect(nodeCreateHandler.mock.calls[0][0].componentName).toBe('TextInput');
|
||||
expect(nodeCreateHandler.mock.calls[0][0].getPropValue('propA')).toBe('haha');
|
||||
|
||||
const nodeDestroyHandler = jest.fn();
|
||||
currentDocument?.onNodeDestroy(nodeDestroyHandler);
|
||||
node.remove();
|
||||
expect(nodeDestroyHandler).toHaveBeenCalledTimes(1);
|
||||
expect(nodeDestroyHandler.mock.calls[0][0]).toBe(node);
|
||||
expect(nodeDestroyHandler.mock.calls[0][0].componentName).toBe('TextInput');
|
||||
expect(nodeDestroyHandler.mock.calls[0][0].getPropValue('propA')).toBe('haha');
|
||||
});
|
||||
|
||||
it.skip('基本的节点模型初始化,节点插入等方法', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const getNode = currentDocument.getNode.bind(currentDocument);
|
||||
|
||||
const formNode = getNode('form');
|
||||
const node1 = currentDocument.createNode({
|
||||
componentName: 'TextInput',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
}
|
||||
});
|
||||
const node2 = currentDocument.createNode({
|
||||
componentName: 'TextInput',
|
||||
props: {
|
||||
propA: 'heihei',
|
||||
}
|
||||
});
|
||||
const node3 = currentDocument.createNode({
|
||||
componentName: 'TextInput',
|
||||
props: {
|
||||
propA: 'heihei2',
|
||||
}
|
||||
});
|
||||
const node4 = currentDocument.createNode({
|
||||
componentName: 'TextInput',
|
||||
props: {
|
||||
propA: 'heihei3',
|
||||
}
|
||||
});
|
||||
|
||||
formNode?.insertBefore(node2);
|
||||
// formNode?.insertBefore(node1, node2);
|
||||
// formNode?.insertAfter(node3);
|
||||
// formNode?.insertAfter(node4, node3);
|
||||
|
||||
expect(formNode?.children?.get(0)).toBe(node1);
|
||||
expect(formNode?.children?.get(1)).toBe(node2);
|
||||
// expect(formNode?.children?.get(5)).toBe(node3);
|
||||
// expect(formNode?.children?.get(6)).toBe(node4);
|
||||
});
|
||||
|
||||
it('基本的节点模型初始化,节点其他方法', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const getNode = currentDocument.getNode.bind(currentDocument);
|
||||
|
||||
const pageNode = getNode('node_k1ow3cb9');
|
||||
expect(pageNode?.isPage()).toBe(true);
|
||||
expect(pageNode?.isComponent()).toBe(false);
|
||||
expect(pageNode?.isSlot()).toBe(false);
|
||||
expect(pageNode?.title).toBe('hey, i\' a page!');
|
||||
});
|
||||
|
||||
describe('节点新增(insertNode)', () => {
|
||||
let project: Project;
|
||||
beforeEach(() => {
|
||||
project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
});
|
||||
it('场景一:插入 NodeSchema,不指定 index', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form') as Node;
|
||||
const formNode2 = currentDocument?.getNode('form');
|
||||
expect(formNode).toEqual(formNode2);
|
||||
currentDocument?.insertNode(formNode, {
|
||||
componentName: 'TextInput',
|
||||
id: 'nodeschema-id1',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
});
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
expect(formNode.children?.length).toBe(4);
|
||||
const insertedNode = formNode.children.get(formNode.children.length - 1);
|
||||
expect(insertedNode.componentName).toBe('TextInput');
|
||||
expect(insertedNode.propsData).toEqual({
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
});
|
||||
// TODO: 把 checkId 的 commit pick 过来
|
||||
// expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
|
||||
});
|
||||
|
||||
it('场景一:插入 NodeSchema,指定 index: 0', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const nodesMap = currentDocument.nodesMap;
|
||||
const formNode = nodesMap.get('form');
|
||||
currentDocument?.insertNode(formNode, {
|
||||
componentName: 'TextInput',
|
||||
id: 'nodeschema-id1',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
}, 0);
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
expect(formNode.children.length).toBe(4);
|
||||
const insertedNode = formNode.children.get(0);
|
||||
expect(insertedNode.componentName).toBe('TextInput');
|
||||
expect(insertedNode.propsData).toEqual({
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
});
|
||||
// TODO: 把 checkId 的 commit pick 过来
|
||||
// expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
|
||||
});
|
||||
|
||||
it('场景一:插入 NodeSchema,指定 index: 1', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form');
|
||||
currentDocument?.insertNode(formNode, {
|
||||
componentName: 'TextInput',
|
||||
id: 'nodeschema-id1',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
}, 1);
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
expect(formNode.children.length).toBe(4);
|
||||
const insertedNode = formNode.children.get(1);
|
||||
expect(insertedNode.componentName).toBe('TextInput');
|
||||
expect(insertedNode.propsData).toEqual({
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
});
|
||||
// TODO: 把 checkId 的 commit pick 过来
|
||||
// expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
|
||||
});
|
||||
|
||||
it('场景一:插入 NodeSchema,有 children', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form') as Node;
|
||||
currentDocument?.insertNode(formNode, {
|
||||
componentName: 'ParentNode',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
},
|
||||
children: [
|
||||
{
|
||||
componentName: 'SubNode',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
componentName: 'SubNode2',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(nodesMap.size).toBe(ids.length + 3);
|
||||
expect(formNode.children.length).toBe(4);
|
||||
expect(formNode.children?.get(3)?.componentName).toBe('ParentNode');
|
||||
expect(formNode.children?.get(3)?.children?.get(0)?.componentName).toBe('SubNode');
|
||||
expect(formNode.children?.get(3)?.children?.get(1)?.componentName).toBe('SubNode2');
|
||||
});
|
||||
|
||||
it.skip('场景一:插入 NodeSchema,id 与现有 schema 里的 id 重复', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form');
|
||||
currentDocument?.insertNode(formNode, {
|
||||
componentName: 'TextInput',
|
||||
id: 'nodeschema-id1',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
});
|
||||
expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
});
|
||||
|
||||
it.skip('场景一:插入 NodeSchema,id 与现有 schema 里的 id 重复,但关闭了 id 检测器', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form');
|
||||
currentDocument?.insertNode(formNode, {
|
||||
componentName: 'TextInput',
|
||||
id: 'nodeschema-id1',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
});
|
||||
expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
});
|
||||
|
||||
it('场景二:插入 Node 实例', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form');
|
||||
const inputNode = currentDocument?.createNode({
|
||||
componentName: 'TextInput',
|
||||
id: 'nodeschema-id2',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
});
|
||||
currentDocument?.insertNode(formNode, inputNode);
|
||||
expect(formNode.children?.get(3)?.componentName).toBe('TextInput');
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
});
|
||||
|
||||
it('场景三:插入 JSExpression', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form') as Node;
|
||||
currentDocument?.insertNode(formNode, {
|
||||
type: 'JSExpression',
|
||||
value: 'just a expression'
|
||||
});
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
expect(formNode.children?.get(3)?.componentName).toBe('Leaf');
|
||||
// expect(formNode.children?.get(3)?.children).toEqual({
|
||||
// type: 'JSExpression',
|
||||
// value: 'just a expression'
|
||||
// });
|
||||
});
|
||||
it('场景四:插入 string', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form') as Node;
|
||||
currentDocument?.insertNode(formNode, 'just a string');
|
||||
expect(nodesMap.size).toBe(ids.length + 1);
|
||||
expect(formNode.children?.get(3)?.componentName).toBe('Leaf');
|
||||
// expect(formNode.children?.get(3)?.children).toBe('just a string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('节点新增(insertNodes)', () => {
|
||||
let project: Project;
|
||||
beforeEach(() => {
|
||||
project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
});
|
||||
it('场景一:插入 NodeSchema,指定 index', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form') as Node;
|
||||
const formNode2 = currentDocument?.getNode('form');
|
||||
expect(formNode).toEqual(formNode2);
|
||||
currentDocument?.insertNodes(formNode, [
|
||||
{
|
||||
componentName: 'TextInput',
|
||||
props: {
|
||||
propA: 'haha2',
|
||||
propB: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
componentName: 'TextInput2',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
}
|
||||
], 1);
|
||||
expect(nodesMap.size).toBe(ids.length + 2);
|
||||
expect(formNode.children?.length).toBe(5);
|
||||
const insertedNode1 = formNode.children.get(1);
|
||||
const insertedNode2 = formNode.children.get(2);
|
||||
expect(insertedNode1.componentName).toBe('TextInput');
|
||||
expect(insertedNode1.propsData).toEqual({
|
||||
propA: 'haha2',
|
||||
propB: 3
|
||||
});
|
||||
expect(insertedNode2.componentName).toBe('TextInput2');
|
||||
expect(insertedNode2.propsData).toEqual({
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
});
|
||||
});
|
||||
|
||||
it('场景二:插入 Node 实例,指定 index', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const formNode = nodesMap.get('form') as Node;
|
||||
const formNode2 = currentDocument?.getNode('form');
|
||||
expect(formNode).toEqual(formNode2);
|
||||
const createdNode1 = currentDocument?.createNode({
|
||||
componentName: 'TextInput',
|
||||
props: {
|
||||
propA: 'haha2',
|
||||
propB: 3
|
||||
}
|
||||
});
|
||||
const createdNode2 = currentDocument?.createNode({
|
||||
componentName: 'TextInput2',
|
||||
props: {
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
}
|
||||
});
|
||||
currentDocument?.insertNodes(formNode, [ createdNode1, createdNode2 ], 1);
|
||||
expect(nodesMap.size).toBe(ids.length + 2);
|
||||
expect(formNode.children?.length).toBe(5);
|
||||
const insertedNode1 = formNode.children.get(1);
|
||||
const insertedNode2 = formNode.children.get(2);
|
||||
expect(insertedNode1.componentName).toBe('TextInput');
|
||||
expect(insertedNode1.propsData).toEqual({
|
||||
propA: 'haha2',
|
||||
propB: 3
|
||||
});
|
||||
expect(insertedNode2.componentName).toBe('TextInput2');
|
||||
expect(insertedNode2.propsData).toEqual({
|
||||
propA: 'haha',
|
||||
propB: 3
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
describe('block ❌ | component ❌ | slot ✅', () => {
|
||||
it('基本的 slot 创建', () => {
|
||||
const formSchemaWithSlot = set(cloneDeep(formSchema), 'children[0].children[0].props.title.type', 'JSSlot');
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchemaWithSlot,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
// 目前每个 slot 会新增(1 + children.length)个节点
|
||||
const expectedNodeCnt = ids.length + 2;
|
||||
expect(nodesMap.size).toBe(expectedNodeCnt);
|
||||
// PageHeader
|
||||
expect(nodesMap.get('node_k1ow3cbd').slots).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
61
packages/designer/tests/node/node.dragdrop.test.ts
Normal file
61
packages/designer/tests/node/node.dragdrop.test.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import '../fixtures/window';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getComponentMeta() {
|
||||
return {
|
||||
getMetadata() {
|
||||
return { experimental: null };
|
||||
},
|
||||
};
|
||||
},
|
||||
transformProps(props) { return props; },
|
||||
createSettingEntry: mockCreateSettingEntry,
|
||||
postEvent() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
describe.skip('节点拖拽测试', () => {
|
||||
describe('block ❌ | component ❌ | slot ❌', () => {
|
||||
it('修改普通属性,string | number', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
expect(nodesMap.size).toBe(expectedNodeCnt);
|
||||
ids.forEach(id => {
|
||||
expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
|
||||
});
|
||||
|
||||
const exportSchema = currentDocument?.export(1);
|
||||
expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
|
||||
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
456
packages/designer/tests/node/node.modify.test.ts
Normal file
456
packages/designer/tests/node/node.modify.test.ts
Normal file
@ -0,0 +1,456 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import '../fixtures/window';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getComponentMeta() {
|
||||
return {
|
||||
getMetadata() {
|
||||
return { experimental: null };
|
||||
},
|
||||
};
|
||||
},
|
||||
transformProps(props) { return props; },
|
||||
createSettingEntry: mockCreateSettingEntry,
|
||||
postEvent() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
describe('schema 生成节点模型测试', () => {
|
||||
describe('block ❌ | component ❌ | slot ❌', () => {
|
||||
let project: Project;
|
||||
beforeEach(() => {
|
||||
project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
});
|
||||
it('读取普通属性,string | number | object', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
const formNode = currentDocument?.getNode('form');
|
||||
/*
|
||||
props: {
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
autoValidate: true,
|
||||
scrollToFirstError: true,
|
||||
autoUnmount: true,
|
||||
behavior: 'NORMAL',
|
||||
dataSource: {
|
||||
type: 'variable',
|
||||
variable: 'state.formData',
|
||||
},
|
||||
obj: {
|
||||
a: 1,
|
||||
b: false,
|
||||
c: 'string',
|
||||
},
|
||||
__style__: {},
|
||||
fieldId: 'form',
|
||||
fieldOptions: {},
|
||||
},
|
||||
id: 'form',
|
||||
condition: true,
|
||||
*/
|
||||
const sizeProp = formNode?.getProp('size');
|
||||
const sizeProp2 = formNode?.getProps().getProp('size');
|
||||
expect(sizeProp).toBe(sizeProp2);
|
||||
expect(sizeProp?.getAsString()).toBe('medium');
|
||||
expect(sizeProp?.getValue()).toBe('medium');
|
||||
|
||||
const autoValidateProp = formNode?.getProp('autoValidate');
|
||||
expect(autoValidateProp?.getValue()).toBe(true);
|
||||
|
||||
const objProp = formNode?.getProp('obj');
|
||||
expect(objProp?.getValue()).toEqual({
|
||||
a: 1,
|
||||
b: false,
|
||||
c: 'string',
|
||||
});
|
||||
const objAProp = formNode?.getProp('obj.a');
|
||||
const objBProp = formNode?.getProp('obj.b');
|
||||
const objCProp = formNode?.getProp('obj.c');
|
||||
expect(objAProp?.getValue()).toBe(1);
|
||||
expect(objBProp?.getValue()).toBe(false);
|
||||
expect(objCProp?.getValue()).toBe('string');
|
||||
|
||||
const idProp = formNode?.getExtraProp('extraPropA');
|
||||
expect(idProp?.getValue()).toBe('extraPropA');
|
||||
});
|
||||
|
||||
it('修改普通属性,string | number | object,使用 Node 实例接口', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
const formNode = currentDocument?.getNode('form');
|
||||
/*
|
||||
props: {
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
autoValidate: true,
|
||||
scrollToFirstError: true,
|
||||
autoUnmount: true,
|
||||
behavior: 'NORMAL',
|
||||
dataSource: {
|
||||
type: 'variable',
|
||||
variable: 'state.formData',
|
||||
},
|
||||
obj: {
|
||||
a: 1,
|
||||
b: false,
|
||||
c: 'string',
|
||||
},
|
||||
__style__: {},
|
||||
fieldId: 'form',
|
||||
fieldOptions: {},
|
||||
},
|
||||
id: 'form',
|
||||
condition: true,
|
||||
*/
|
||||
formNode?.setPropValue('size', 'large');
|
||||
const sizeProp = formNode?.getProp('size');
|
||||
expect(sizeProp?.getAsString()).toBe('large');
|
||||
expect(sizeProp?.getValue()).toBe('large');
|
||||
|
||||
formNode?.setPropValue('autoValidate', false);
|
||||
const autoValidateProp = formNode?.getProp('autoValidate');
|
||||
expect(autoValidateProp?.getValue()).toBe(false);
|
||||
|
||||
formNode?.setPropValue('obj', {
|
||||
a: 2,
|
||||
b: true,
|
||||
c: 'another string'
|
||||
});
|
||||
const objProp = formNode?.getProp('obj');
|
||||
expect(objProp?.getValue()).toEqual({
|
||||
a: 2,
|
||||
b: true,
|
||||
c: 'another string',
|
||||
});
|
||||
formNode?.setPropValue('obj.a', 3);
|
||||
formNode?.setPropValue('obj.b', false);
|
||||
formNode?.setPropValue('obj.c', 'string');
|
||||
const objAProp = formNode?.getProp('obj.a');
|
||||
const objBProp = formNode?.getProp('obj.b');
|
||||
const objCProp = formNode?.getProp('obj.c');
|
||||
expect(objAProp?.getValue()).toBe(3);
|
||||
expect(objBProp?.getValue()).toBe(false);
|
||||
expect(objCProp?.getValue()).toBe('string');
|
||||
expect(objProp?.getValue()).toEqual({
|
||||
a: 3,
|
||||
b: false,
|
||||
c: 'string',
|
||||
});
|
||||
});
|
||||
|
||||
it('修改普通属性,string | number | object,使用 Props 实例接口', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
const formNode = currentDocument?.getNode('form');
|
||||
/*
|
||||
props: {
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
autoValidate: true,
|
||||
scrollToFirstError: true,
|
||||
autoUnmount: true,
|
||||
behavior: 'NORMAL',
|
||||
dataSource: {
|
||||
type: 'variable',
|
||||
variable: 'state.formData',
|
||||
},
|
||||
obj: {
|
||||
a: 1,
|
||||
b: false,
|
||||
c: 'string',
|
||||
},
|
||||
__style__: {},
|
||||
fieldId: 'form',
|
||||
fieldOptions: {},
|
||||
},
|
||||
id: 'form',
|
||||
condition: true,
|
||||
*/
|
||||
const props = formNode?.getProps();
|
||||
props?.setPropValue('size', 'large');
|
||||
const sizeProp = formNode?.getProp('size');
|
||||
expect(sizeProp?.getAsString()).toBe('large');
|
||||
expect(sizeProp?.getValue()).toBe('large');
|
||||
|
||||
props?.setPropValue('autoValidate', false);
|
||||
const autoValidateProp = formNode?.getProp('autoValidate');
|
||||
expect(autoValidateProp?.getValue()).toBe(false);
|
||||
|
||||
props?.setPropValue('obj', {
|
||||
a: 2,
|
||||
b: true,
|
||||
c: 'another string'
|
||||
});
|
||||
const objProp = formNode?.getProp('obj');
|
||||
expect(objProp?.getValue()).toEqual({
|
||||
a: 2,
|
||||
b: true,
|
||||
c: 'another string',
|
||||
});
|
||||
props?.setPropValue('obj.a', 3);
|
||||
props?.setPropValue('obj.b', false);
|
||||
props?.setPropValue('obj.c', 'string');
|
||||
const objAProp = formNode?.getProp('obj.a');
|
||||
const objBProp = formNode?.getProp('obj.b');
|
||||
const objCProp = formNode?.getProp('obj.c');
|
||||
expect(objAProp?.getValue()).toBe(3);
|
||||
expect(objBProp?.getValue()).toBe(false);
|
||||
expect(objCProp?.getValue()).toBe('string');
|
||||
expect(objProp?.getValue()).toEqual({
|
||||
a: 3,
|
||||
b: false,
|
||||
c: 'string',
|
||||
});
|
||||
});
|
||||
|
||||
it('修改普通属性,string | number | object,使用 Prop 实例接口', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
const formNode = currentDocument?.getNode('form');
|
||||
/*
|
||||
props: {
|
||||
size: 'medium',
|
||||
labelAlign: 'top',
|
||||
autoValidate: true,
|
||||
scrollToFirstError: true,
|
||||
autoUnmount: true,
|
||||
behavior: 'NORMAL',
|
||||
dataSource: {
|
||||
type: 'variable',
|
||||
variable: 'state.formData',
|
||||
},
|
||||
obj: {
|
||||
a: 1,
|
||||
b: false,
|
||||
c: 'string',
|
||||
},
|
||||
__style__: {},
|
||||
fieldId: 'form',
|
||||
fieldOptions: {},
|
||||
},
|
||||
id: 'form',
|
||||
condition: true,
|
||||
*/
|
||||
const sizeProp = formNode?.getProp('size');
|
||||
sizeProp?.setValue('large');
|
||||
expect(sizeProp?.getAsString()).toBe('large');
|
||||
expect(sizeProp?.getValue()).toBe('large');
|
||||
|
||||
const autoValidateProp = formNode?.getProp('autoValidate');
|
||||
autoValidateProp?.setValue(false);
|
||||
expect(autoValidateProp?.getValue()).toBe(false);
|
||||
|
||||
|
||||
const objProp = formNode?.getProp('obj');
|
||||
objProp?.setValue({
|
||||
a: 2,
|
||||
b: true,
|
||||
c: 'another string'
|
||||
});
|
||||
expect(objProp?.getValue()).toEqual({
|
||||
a: 2,
|
||||
b: true,
|
||||
c: 'another string',
|
||||
});
|
||||
const objAProp = formNode?.getProp('obj.a');
|
||||
const objBProp = formNode?.getProp('obj.b');
|
||||
const objCProp = formNode?.getProp('obj.c');
|
||||
objAProp?.setValue(3);
|
||||
objBProp?.setValue(false);
|
||||
objCProp?.setValue('string');
|
||||
expect(objAProp?.getValue()).toBe(3);
|
||||
expect(objBProp?.getValue()).toBe(false);
|
||||
expect(objCProp?.getValue()).toBe('string');
|
||||
expect(objProp?.getValue()).toEqual({
|
||||
a: 3,
|
||||
b: false,
|
||||
c: 'string',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('block ❌ | component ❌ | slot ✅', () => {
|
||||
let project: Project;
|
||||
beforeEach(() => {
|
||||
project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
});
|
||||
it('修改 slot 属性,初始存在 slot 属性名,正常生成节点模型', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
const formNode = currentDocument?.getNode('form');
|
||||
|
||||
formNode?.setPropValue('slotA', {
|
||||
type: 'JSSlot',
|
||||
value: [{
|
||||
componentName: 'TextInput1',
|
||||
props: {
|
||||
txt: 'haha',
|
||||
num: 1,
|
||||
bool: true
|
||||
}
|
||||
}, {
|
||||
componentName: 'TextInput2',
|
||||
props: {
|
||||
txt: 'heihei',
|
||||
num: 2,
|
||||
bool: false
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
expect(nodesMap.size).toBe(ids.length + 3);
|
||||
expect(formNode?.slots).toHaveLength(1);
|
||||
expect(formNode?.slots[0].children).toHaveLength(2);
|
||||
const firstChildNode = formNode?.slots[0].children?.get(0);
|
||||
const secondChildNode = formNode?.slots[0].children?.get(1);
|
||||
expect(firstChildNode?.componentName).toBe('TextInput1');
|
||||
expect(firstChildNode?.getPropValue('txt')).toBe('haha');
|
||||
expect(firstChildNode?.getPropValue('num')).toBe(1);
|
||||
expect(firstChildNode?.getPropValue('bool')).toBe(true);
|
||||
expect(secondChildNode?.componentName).toBe('TextInput2');
|
||||
expect(secondChildNode?.getPropValue('txt')).toBe('heihei');
|
||||
expect(secondChildNode?.getPropValue('num')).toBe(2);
|
||||
expect(secondChildNode?.getPropValue('bool')).toBe(false);
|
||||
});
|
||||
|
||||
it('修改 slot 属性,初始存在 slot 属性名,关闭 slot', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
const formNode = currentDocument?.getNode('form');
|
||||
|
||||
formNode?.setPropValue('slotA', {
|
||||
type: 'JSSlot',
|
||||
value: [{
|
||||
componentName: 'TextInput1',
|
||||
props: {
|
||||
txt: 'haha',
|
||||
num: 1,
|
||||
bool: true
|
||||
}
|
||||
}, {
|
||||
componentName: 'TextInput2',
|
||||
props: {
|
||||
txt: 'heihei',
|
||||
num: 2,
|
||||
bool: false
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
expect(nodesMap.size).toBe(ids.length + 3);
|
||||
expect(formNode?.slots).toHaveLength(1);
|
||||
|
||||
formNode?.setPropValue('slotA', '');
|
||||
|
||||
expect(nodesMap.size).toBe(ids.length);
|
||||
expect(formNode?.slots).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('修改 slot 属性,初始存在 slot 属性名,同名覆盖 slot', () => {
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
const formNode = currentDocument?.getNode('form');
|
||||
|
||||
formNode?.setPropValue('slotA', {
|
||||
type: 'JSSlot',
|
||||
name: 'slotA',
|
||||
value: [{
|
||||
componentName: 'TextInput1',
|
||||
props: {
|
||||
txt: 'haha',
|
||||
num: 1,
|
||||
bool: true
|
||||
}
|
||||
}, {
|
||||
componentName: 'TextInput2',
|
||||
props: {
|
||||
txt: 'heihei',
|
||||
num: 2,
|
||||
bool: false
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
expect(nodesMap.size).toBe(ids.length + 3);
|
||||
expect(formNode?.slots).toHaveLength(1);
|
||||
expect(formNode?.slots[0].children).toHaveLength(2);
|
||||
|
||||
let firstChildNode = formNode?.slots[0].children?.get(0);
|
||||
expect(firstChildNode?.componentName).toBe('TextInput1');
|
||||
expect(firstChildNode?.getPropValue('txt')).toBe('haha');
|
||||
expect(firstChildNode?.getPropValue('num')).toBe(1);
|
||||
expect(firstChildNode?.getPropValue('bool')).toBe(true);
|
||||
|
||||
formNode?.setPropValue('slotA', {
|
||||
type: 'JSSlot',
|
||||
name: 'slotA',
|
||||
value: [{
|
||||
componentName: 'TextInput3',
|
||||
props: {
|
||||
txt: 'xixi',
|
||||
num: 3,
|
||||
bool: false
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
expect(nodesMap.size).toBe(ids.length + 2);
|
||||
expect(formNode?.slots).toHaveLength(1);
|
||||
expect(formNode?.slots[0].children).toHaveLength(1);
|
||||
firstChildNode = formNode?.slots[0].children?.get(0);
|
||||
expect(firstChildNode?.componentName).toBe('TextInput3');
|
||||
expect(firstChildNode?.getPropValue('txt')).toBe('xixi');
|
||||
expect(firstChildNode?.getPropValue('num')).toBe(3);
|
||||
expect(firstChildNode?.getPropValue('bool')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
122
packages/designer/tests/node/node.remove.test.ts
Normal file
122
packages/designer/tests/node/node.remove.test.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import '../fixtures/window';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getComponentMeta() {
|
||||
return {
|
||||
getMetadata() {
|
||||
return { experimental: null };
|
||||
},
|
||||
};
|
||||
},
|
||||
transformProps(props) { return props; },
|
||||
createSettingEntry: mockCreateSettingEntry,
|
||||
postEvent() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
describe('节点模型删除测试', () => {
|
||||
it('删除叶子节点', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const originalNodeCnt = ids.length;
|
||||
expect(nodesMap.size).toBe(originalNodeCnt);
|
||||
|
||||
currentDocument?.removeNode('node_k1ow3cbn');
|
||||
// Button#1
|
||||
expect(nodesMap.size).toBe(originalNodeCnt - 1);
|
||||
|
||||
currentDocument?.removeNode(nodesMap.get('node_k1ow3cbp'));
|
||||
// Button#2
|
||||
expect(nodesMap.size).toBe(originalNodeCnt - 2);
|
||||
|
||||
currentDocument?.removeNode('unexisting_node');
|
||||
expect(nodesMap.size).toBe(originalNodeCnt - 2);
|
||||
});
|
||||
|
||||
it('删除叶子节点,带有 slot', () => {
|
||||
const formSchemaWithSlot = set(cloneDeep(formSchema),
|
||||
'children[1].children[0].children[2].children[1].props.greeting.type', 'JSSlot');
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchemaWithSlot,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const originalNodeCnt = ids.length + 2;
|
||||
expect(nodesMap.size).toBe(originalNodeCnt);
|
||||
|
||||
currentDocument?.removeNode('node_k1ow3cbp');
|
||||
// Button + Slot + Text
|
||||
expect(nodesMap.size).toBe(originalNodeCnt - 3);
|
||||
});
|
||||
|
||||
it('删除分支节点', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const originalNodeCnt = ids.length;
|
||||
expect(nodesMap.size).toBe(originalNodeCnt);
|
||||
|
||||
currentDocument?.removeNode('node_k1ow3cbo');
|
||||
// Div + 2 * Button
|
||||
expect(nodesMap.size).toBe(originalNodeCnt - 3);
|
||||
});
|
||||
|
||||
it('删除分支节点,带有 slot', () => {
|
||||
const formSchemaWithSlot = set(cloneDeep(formSchema),
|
||||
'children[1].children[0].children[2].children[1].props.greeting.type', 'JSSlot');
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchemaWithSlot,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const originalNodeCnt = ids.length + 2;
|
||||
expect(nodesMap.size).toBe(originalNodeCnt);
|
||||
|
||||
currentDocument?.removeNode('node_k1ow3cbo');
|
||||
// Div + 2 * Button + Slot + Text
|
||||
expect(nodesMap.size).toBe(originalNodeCnt - 5);
|
||||
});
|
||||
});
|
||||
134
packages/designer/tests/project/project.test.ts
Normal file
134
packages/designer/tests/project/project.test.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import set from 'lodash/set';
|
||||
import cloneDeep from 'lodash/clonedeep';
|
||||
import '../fixtures/window';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { Node } from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import { getIdsFromSchema, getNodeFromSchemaById } from '../utils';
|
||||
|
||||
const mockCreateSettingEntry = jest.fn();
|
||||
jest.mock('../../src/designer/designer', () => {
|
||||
return {
|
||||
Designer: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getComponentMeta() {
|
||||
return {
|
||||
getMetadata() {
|
||||
return { experimental: null };
|
||||
},
|
||||
};
|
||||
},
|
||||
transformProps(props) { return props; },
|
||||
createSettingEntry: mockCreateSettingEntry,
|
||||
postEvent() {},
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let designer = null;
|
||||
beforeAll(() => {
|
||||
designer = new Designer({});
|
||||
});
|
||||
|
||||
describe('schema 生成节点模型测试', () => {
|
||||
describe('block ❌ | component ❌ | slot ❌', () => {
|
||||
beforeEach(() => {
|
||||
mockCreateSettingEntry.mockClear();
|
||||
});
|
||||
|
||||
it('基本的节点模型初始化,模型导出,初始化传入 schema', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
expect(nodesMap.size).toBe(expectedNodeCnt);
|
||||
ids.forEach(id => {
|
||||
expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
|
||||
});
|
||||
|
||||
const exportSchema = currentDocument?.export(1);
|
||||
expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
|
||||
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
|
||||
});
|
||||
|
||||
it('基本的节点模型初始化,模型导出,project.open 传入 schema', () => {
|
||||
const project = new Project(designer);
|
||||
project.open(formSchema);
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
const expectedNodeCnt = ids.length;
|
||||
expect(nodesMap.size).toBe(expectedNodeCnt);
|
||||
ids.forEach(id => {
|
||||
expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
|
||||
});
|
||||
|
||||
const exportSchema = currentDocument?.export(1);
|
||||
expect(getIdsFromSchema(exportSchema).length).toBe(expectedNodeCnt);
|
||||
expect(mockCreateSettingEntry).toBeCalledTimes(expectedNodeCnt);
|
||||
});
|
||||
|
||||
it('project 卸载所有 document - unload()', () => {
|
||||
const project = new Project(designer);
|
||||
project.open(formSchema);
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument, documents } = project;
|
||||
|
||||
expect(documents).toHaveLength(1);
|
||||
expect(currentDocument).toBe(documents[0]);
|
||||
|
||||
project.unload();
|
||||
|
||||
expect(documents).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('project 卸载指定 document - removeDocument()', () => {
|
||||
const project = new Project(designer);
|
||||
project.open(formSchema);
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument, documents } = project;
|
||||
|
||||
expect(documents).toHaveLength(1);
|
||||
expect(currentDocument).toBe(documents[0]);
|
||||
|
||||
project.removeDocument(currentDocument);
|
||||
|
||||
expect(documents).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('block ❌ | component ❌ | slot ✅', () => {
|
||||
it('基本的节点模型初始化,模型导出,初始化传入 schema', () => {
|
||||
const formSchemaWithSlot = set(cloneDeep(formSchema), 'children[0].children[0].props.title.type', 'JSSlot');
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchemaWithSlot,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
expect(project).toBeTruthy();
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap } = currentDocument!;
|
||||
const ids = getIdsFromSchema(formSchema);
|
||||
// 目前每个 slot 会新增(1 + children.length)个节点
|
||||
const expectedNodeCnt = ids.length + 2;
|
||||
expect(nodesMap.size).toBe(expectedNodeCnt);
|
||||
// PageHeader
|
||||
expect(nodesMap.get('node_k1ow3cbd').slots).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('多 document 测试', () => {
|
||||
|
||||
});
|
||||
});
|
||||
77
packages/designer/tests/utils/bom.ts
Normal file
77
packages/designer/tests/utils/bom.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { getMockRenderer } from './renderer';
|
||||
|
||||
interface MockDocument extends Document {
|
||||
// open(): any;
|
||||
// write(): any;
|
||||
// close(): any;
|
||||
// addEventListener(): any;
|
||||
// removeEventListener(): any;
|
||||
triggerEventListener(): any;
|
||||
// createElement(): any;
|
||||
// appendChild(): any;
|
||||
// removeChild(): any;
|
||||
}
|
||||
|
||||
|
||||
const eventsMap : Map<string, Set<Function>> = new Map<string, Set<Function>>();
|
||||
const mockAddEventListener = jest.fn((eventName: string, cb) => {
|
||||
if (!eventsMap.has(eventName)) {
|
||||
eventsMap.set(eventName, new Set([cb]));
|
||||
return;
|
||||
}
|
||||
eventsMap.get(eventName)!.add(cb);
|
||||
});
|
||||
|
||||
const mockRemoveEventListener = jest.fn((eventName: string, cb) => {
|
||||
if (!eventsMap.has(eventName)) return;
|
||||
if (!cb) {
|
||||
eventsMap.delete(eventName);
|
||||
return;
|
||||
}
|
||||
eventsMap.get(eventName)?.delete(cb);
|
||||
});
|
||||
|
||||
const mockTriggerEventListener = jest.fn((eventName: string, data: any, context: object = {}) => {
|
||||
if (!eventsMap.has(eventName)) return;
|
||||
for (const cb of eventsMap.get(eventName)) {
|
||||
cb.call(context, data);
|
||||
}
|
||||
});
|
||||
|
||||
const mockCreateElement = jest.fn((tagName) => {
|
||||
return {
|
||||
style: {},
|
||||
appendChild() {},
|
||||
addEventListener: mockAddEventListener,
|
||||
removeEventListener: mockRemoveEventListener,
|
||||
triggerEventListener: mockTriggerEventListener,
|
||||
}
|
||||
})
|
||||
|
||||
export function getMockDocument(): MockDocument {
|
||||
return {
|
||||
open() {},
|
||||
write() {},
|
||||
close() {},
|
||||
addEventListener: mockAddEventListener,
|
||||
removeEventListener: mockRemoveEventListener,
|
||||
triggerEventListener: mockTriggerEventListener,
|
||||
createElement: mockCreateElement,
|
||||
removeChild() {},
|
||||
body: { appendChild() {}, removeChild() {} },
|
||||
};
|
||||
}
|
||||
|
||||
export function getMockWindow(doc?: MockDocument) {
|
||||
return {
|
||||
SimulatorRenderer: getMockRenderer(),
|
||||
addEventListener: mockAddEventListener,
|
||||
removeEventListener: mockRemoveEventListener,
|
||||
triggerEventListener: mockTriggerEventListener,
|
||||
document: doc || getMockDocument(),
|
||||
};
|
||||
}
|
||||
|
||||
export function clearEventsMap() {
|
||||
eventsMap.clear();
|
||||
}
|
||||
8
packages/designer/tests/utils/event.ts
Normal file
8
packages/designer/tests/utils/event.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export function getMockEvent(target, options) {
|
||||
return {
|
||||
target,
|
||||
preventDefault() {},
|
||||
stopPropagation() {},
|
||||
...options,
|
||||
};
|
||||
}
|
||||
4
packages/designer/tests/utils/index.ts
Normal file
4
packages/designer/tests/utils/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { getIdsFromSchema, getNodeFromSchemaById } from '@ali/lowcode-test-mate/es/utils';
|
||||
export { getMockDocument, getMockWindow } from './bom';
|
||||
export { getMockEvent } from './event';
|
||||
export { getMockRenderer } from './renderer';
|
||||
8
packages/designer/tests/utils/renderer.ts
Normal file
8
packages/designer/tests/utils/renderer.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export function getMockRenderer() {
|
||||
return {
|
||||
isSimulatorRenderer: true,
|
||||
run() {
|
||||
console.log('renderer run');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"./src/"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
extends: '../../.eslintrc.js',
|
||||
extends: 'eslint-config-ali/typescript/react',
|
||||
rules: {
|
||||
'react/no-multi-comp': 1,
|
||||
'no-unused-expressions': 1,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
class Monitor {
|
||||
export class Monitor {
|
||||
fn = (params: any) => {
|
||||
const { AES } = window as any;
|
||||
if (typeof AES.log === 'function') {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
extends: '../../.eslintrc.js',
|
||||
extends: 'eslint-config-ali/typescript/react',
|
||||
rules: {
|
||||
'react/no-multi-comp': 1,
|
||||
'no-unused-expressions': 1,
|
||||
'no-unused-expressions': 0,
|
||||
'implicit-arrow-linebreak': 1,
|
||||
'no-nested-ternary': 1,
|
||||
'no-mixed-operators': 1,
|
||||
@ -16,5 +16,6 @@ module.exports = {
|
||||
'react/no-deprecated': 1,
|
||||
'no-useless-escape': 1,
|
||||
'brace-style': 1,
|
||||
'@typescript-eslint/member-ordering': 0,
|
||||
}
|
||||
}
|
||||
@ -83,14 +83,27 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-editor-preset-vision
|
||||
|
||||
<a name="1.0.12"></a>
|
||||
## [1.0.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-preset-vision@1.0.11...@ali/lowcode-editor-preset-vision@1.0.12) (2020-10-20)
|
||||
<a name="0.12.1-3"></a>
|
||||
## [0.12.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-3) (2020-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 去掉 flags ([75fc3c6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/75fc3c6))
|
||||
* 处理 JSExpreesion 的 i18n 场景 ([9b87407](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9b87407))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.12.1-2"></a>
|
||||
## [0.12.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-1...v0.12.1-2) (2020-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 合并分支 ([add2f23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/add2f23))
|
||||
* fix bug ([113e409](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/113e409))
|
||||
* i18n 绑定变量后消失 ([0aafafe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0aafafe))
|
||||
|
||||
|
||||
|
||||
@ -119,14 +132,111 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-editor-preset-vision
|
||||
|
||||
<a name="1.0.8"></a>
|
||||
## [1.0.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-preset-vision@1.0.8-0...@ali/lowcode-editor-preset-vision@1.0.8) (2020-09-28)
|
||||
<a name="1.0.9-1"></a>
|
||||
## [1.0.9-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-0...v1.0.9-1) (2020-09-14)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-editor-preset-vision
|
||||
|
||||
<a name="1.0.9-0"></a>
|
||||
## 1.0.9-0 (2020-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fieldId 重复问题 ([e761b1a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e761b1a))
|
||||
* 🐛 eslint ([14803dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/14803dd))
|
||||
* 🐛 eslint ([e3ca0bd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e3ca0bd))
|
||||
* 🐛 use intl ([a22e66a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a22e66a))
|
||||
* 🐛 用 isI18nData 判断 meta title ([732bccf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/732bccf))
|
||||
* 🐛 解决点击组件时无法聚焦到点中的组件上的问题 ([852d882](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/852d882))
|
||||
* 🐛 逻辑简化 ([710f3ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/710f3ba))
|
||||
* add extraEnv ([9058ac8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9058ac8))
|
||||
* compatiable bug ([45574db](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/45574db))
|
||||
* compatiable old VE api ([45af1c5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/45af1c5))
|
||||
* compatiableReducer 递归 ([e905928](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e905928))
|
||||
* createComponent 支持所有 schema ([7f946f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7f946f5))
|
||||
* currentPage.id 返回 formUuid ([775725d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/775725d))
|
||||
* fieldId 重复 ([5d64312](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5d64312))
|
||||
* fieldId 重置bug ([31215da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/31215da))
|
||||
* formUuid 可能不在 url 中 ([8657ab8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8657ab8))
|
||||
* i18n parser & setting ([dbdd9e4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dbdd9e4))
|
||||
* modify layout props ([9baba75](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9baba75))
|
||||
* patch prototype ([f20bfaa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f20bfaa))
|
||||
* render children ([487f257](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/487f257))
|
||||
* settingField items is empty when type is not 'group' ([582c41a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/582c41a))
|
||||
* slot 兼容问题 + loop key bug fix ([bc64017](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bc64017))
|
||||
* style ([4694331](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4694331))
|
||||
* Trunk add getSetter ([b6d64c3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b6d64c3))
|
||||
* Trunk.getSetter return ReactElement ([34bf71d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/34bf71d))
|
||||
* upgradePropsReducer ([e68977f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e68977f))
|
||||
* variable init bug ([6d55bd3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d55bd3))
|
||||
* vc-filter bug fix ([31ea5d5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/31ea5d5))
|
||||
* vision API 兼容 DockPane.getDocks() ([f72fb66](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f72fb66))
|
||||
* vision prop 初始化时有依赖已初始化的 prop,需要实时添加 ([1feb46f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1feb46f))
|
||||
* vision 大包 window 指向问题 ([aa1b526](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aa1b526))
|
||||
* 不对外暴露 Node ([05957ce](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05957ce))
|
||||
* 不应该限定 parent 才做解绑操作 ([2e616e3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e616e3))
|
||||
* 使用深拷贝赋值并修改 dataSource.list 避免影响 legao 现有逻辑 ([82c5d2e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/82c5d2e))
|
||||
* 保存区块按钮渲染异常 ([33a7227](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/33a7227))
|
||||
* 修复 initial 重复、type = 'composite' 时 items 为空 ([bf79e63](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bf79e63))
|
||||
* 修复 preset-vision 版本 lifeCycles 丢失以及 slot 初始化问题 ([7cf6d24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7cf6d24))
|
||||
* 修复 slot 获取初始值异常的 bug ([63b19f1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/63b19f1))
|
||||
* 修复低代码组件设计器、区块设计器根节点为 Page 的问题,修复 topArea 样式 ([e85b542](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e85b542))
|
||||
* 修复组件面板详情加载不了的 bug ([cca3309](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cca3309))
|
||||
* 修复获取 currentPage 的逻辑 ([d8221db](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d8221db))
|
||||
* 兼容 listSetter 内部变量,修复回退 fieldId 重置问题 ([c95e618](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c95e618))
|
||||
* 兼容 rpx ([5050af7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5050af7))
|
||||
* 兼容 variable 历史数据格式 ([d666317](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d666317))
|
||||
* 兼容事件绑定 ([f4c07af](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f4c07af))
|
||||
* 兼容原来 prototype 的 componentName/view ([d542a40](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d542a40))
|
||||
* 区块组件无法删除 ([d936d2b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d936d2b))
|
||||
* 可以降级到历史的 JSBlock 格式 ([af1746b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/af1746b))
|
||||
* 右侧配置面板样式修复 ([05f62da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05f62da))
|
||||
* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad))
|
||||
* 在 renderer 层面做 function component 包装,避免影响 rax 等其他场景 ([1f920dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f920dd))
|
||||
* 增加兼容 API ([2960446](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2960446))
|
||||
* 处理 function component 无法选中的问题,本质上是没有 ref ([fa94aab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fa94aab))
|
||||
* 支持 AC 组件 ([c287bad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c287bad))
|
||||
* 支持事件 VE_EVENTS.VE_PAGE_PAGE_READY ([935ffad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/935ffad))
|
||||
* **editor-skeleton:** add canSetFixed prop to panel config ([1b57d5c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1b57d5c))
|
||||
* 支持页面回滚 ([5d7dc2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5d7dc2f))
|
||||
* 框架样式调整 ([58790c5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/58790c5))
|
||||
* 用户在动态修改 prototype 时也需要重新计算 meta ([66c21c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66c21c0))
|
||||
* 移除 isInSimulator 函数 ([6370889](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6370889))
|
||||
* 简化 onPageReady 实现逻辑 ([a36e5f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a36e5f2))
|
||||
* 补全 packageName, 否则在组件面板会被隐藏 ([88e5008](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/88e5008))
|
||||
* 调整 upgrade 和 init 的流程 ([09fc1a0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/09fc1a0))
|
||||
* 调整保存成功弹出框位置 ([5198dae](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5198dae))
|
||||
* 页面加载之后就被标记位 isModified ([2840d27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2840d27))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 prototype getTitle 支持 i18n ([18807ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18807ab))
|
||||
* complete live-editing expr & i18n ([3ac08ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3ac08ba))
|
||||
* export Monitor ([51025f0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/51025f0))
|
||||
* get layout config from legao-design ([b9103a2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b9103a2))
|
||||
* JSexpression props ([26f4fb1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26f4fb1))
|
||||
* live mode lifeCycles ([66f0c79](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66f0c79))
|
||||
* live 模式取消 mock 兼容 ([ab66fd4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ab66fd4))
|
||||
* merge live mode ([92c3039](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/92c3039))
|
||||
* register-defaults 改为可选项 ([2195797](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2195797))
|
||||
* support prop.autorun ([c0a5235](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c0a5235))
|
||||
* support subtreeModified ([7eeb51c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7eeb51c))
|
||||
* ve事件埋点 ([700e5b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/700e5b0))
|
||||
* 在 editor-preset-vision 中对 legao schema 进行向前兼容 ([7867917](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7867917))
|
||||
* 增加 defaultFixed,面板可默认固定 ([eb51b5e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eb51b5e))
|
||||
* 支持 entry 模式 ([fe1f6f1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fe1f6f1))
|
||||
* 支持多 pages 的 schema 结构 ([d9b5adb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d9b5adb))
|
||||
* 适配 webtable ([91f1702](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/91f1702))
|
||||
* 适配乐高 OneApi 数据源,将 options.params 从 Array 改为 Object ([aa135c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aa135c0))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.8-0"></a>
|
||||
## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-preset-vision@0.8.57...@ali/lowcode-editor-preset-vision@1.0.8-0) (2020-09-09)
|
||||
|
||||
|
||||
@ -10,10 +10,11 @@
|
||||
"react": "var window.React",
|
||||
"react-dom": "var window.ReactDOM",
|
||||
"prop-types": "var window.PropTypes",
|
||||
"rax": "var window.Rax",
|
||||
"@ali/visualengine": "var window.VisualEngine",
|
||||
"@ali/visualengine-utils": "var window.VisualEngineUtils",
|
||||
"monaco-editor/esm/vs/editor/editor.api":"var window.monaco",
|
||||
"monaco-editor/esm/vs/editor/editor.main.js":"var window.monaco"
|
||||
"rax": "var window.Rax",
|
||||
"monaco-editor/esm/vs/editor/editor.api": "var window.monaco",
|
||||
"monaco-editor/esm/vs/editor/editor.main.js": "var window.monaco"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
19
packages/editor-preset-vision/build.test.json
Normal file
19
packages/editor-preset-vision/build.test.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"build-plugin-component",
|
||||
{
|
||||
"filename": "editor-preset-vision",
|
||||
"library": "LowcodeEditor",
|
||||
"libraryTarget": "umd",
|
||||
"externals": {
|
||||
"react": "var window.React",
|
||||
"react-dom": "var window.ReactDOM",
|
||||
"prop-types": "var window.PropTypes",
|
||||
"rax": "var window.Rax"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@ali/lowcode-test-mate/plugin/index.ts"
|
||||
]
|
||||
}
|
||||
27
packages/editor-preset-vision/jest.config.js
Normal file
27
packages/editor-preset-vision/jest.config.js
Normal file
@ -0,0 +1,27 @@
|
||||
const esModules = [
|
||||
'@recore/obx-react',
|
||||
// '@ali/lowcode-editor-core',
|
||||
].join('|');
|
||||
|
||||
module.exports = {
|
||||
// transform: {
|
||||
// '^.+\\.[jt]sx?$': 'babel-jest',
|
||||
// // '^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
// // '^.+\\.(js|jsx)$': 'babel-jest',
|
||||
// },
|
||||
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
|
||||
transformIgnorePatterns: [
|
||||
`/node_modules/(?!${esModules})/`,
|
||||
],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
collectCoverage: false,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,tsx}',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/base/**',
|
||||
'!src/fields/**',
|
||||
'!src/prop.ts',
|
||||
'!**/node_modules/**',
|
||||
'!**/vendor/**',
|
||||
],
|
||||
};
|
||||
@ -10,8 +10,10 @@
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "build-scripts start",
|
||||
"build": "build-scripts build --skip-demo",
|
||||
"cloud-build": "build-scripts build --skip-demo"
|
||||
"cloud-build": "build-scripts build --skip-demo",
|
||||
"test": "build-scripts test --config build.test.json"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -20,6 +22,7 @@
|
||||
"@ali/lowcode-editor-skeleton": "^1.0.22",
|
||||
"@ali/lowcode-plugin-designer": "^1.0.22",
|
||||
"@ali/lowcode-plugin-outline-pane": "^1.0.21",
|
||||
"@ali/lowcode-utils": "^1.0.21",
|
||||
"@ali/ve-i18n-util": "^2.0.0",
|
||||
"@ali/ve-icons": "^4.1.9",
|
||||
"@ali/ve-less-variables": "2.0.3",
|
||||
@ -37,6 +40,7 @@
|
||||
"react-dom": "^16.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ali/lowcode-test-mate": "^1.0.1",
|
||||
"@alib/build-scripts": "^0.1.18",
|
||||
"@types/domready": "^1.0.0",
|
||||
"@types/events": "^3.0.0",
|
||||
@ -45,6 +49,7 @@
|
||||
"build-plugin-fusion": "^0.1.0",
|
||||
"build-plugin-moment-locales": "^0.1.0",
|
||||
"build-plugin-react-app": "^1.1.2",
|
||||
"prop-types": "^15.7.2",
|
||||
"tsconfig-paths-webpack-plugin": "^3.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
upgradePropConfig,
|
||||
upgradeConfigure,
|
||||
} from './upgrade-metadata';
|
||||
import { accessLibrary } from '@ali/lowcode-utils';
|
||||
|
||||
import { designer } from '../editor';
|
||||
|
||||
@ -103,8 +104,8 @@ registerMetadataTransducer(
|
||||
let top: FieldConfig[];
|
||||
let bottom: FieldConfig[];
|
||||
if (combined) {
|
||||
top = combined?.[0].items || combined;
|
||||
bottom = combined?.[combined.length - 1].items || combined;
|
||||
top = combined?.[0]?.items || combined;
|
||||
bottom = combined?.[combined.length - 1]?.items || combined;
|
||||
} else if (props) {
|
||||
top = props;
|
||||
bottom = props;
|
||||
@ -166,15 +167,6 @@ export interface OldGlobalPropConfig extends OldPropConfig {
|
||||
|
||||
const packageMaps: any = {};
|
||||
|
||||
function accessLibrary(library: string | object) {
|
||||
if (typeof library !== 'string') {
|
||||
return library;
|
||||
}
|
||||
|
||||
// TODO: enhance logic
|
||||
return (window as any)[library];
|
||||
}
|
||||
|
||||
export function setPackages(packages: Array<{ package: string; library: object | string }>) {
|
||||
packages.forEach((item) => {
|
||||
let lib: any;
|
||||
@ -236,6 +228,14 @@ class Prototype {
|
||||
return this.meta.npm?.package;
|
||||
}
|
||||
|
||||
set packageName(pkgName) {
|
||||
if (this.meta.npm) {
|
||||
this.meta.npm.package = pkgName;
|
||||
} else {
|
||||
this.meta.npm = { package: pkgName };
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容原 vision 用法
|
||||
view: ComponentType | undefined;
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ export class Trunk {
|
||||
}
|
||||
|
||||
getList(): any[] {
|
||||
const list = this.trunk.reduceRight((prev, cur) => prev.concat(cur.getList()), []);
|
||||
const list = this.trunk.filter(o => o).reduceRight((prev, cur) => prev.concat(cur.getList()), []);
|
||||
const result: Prototype[] = [];
|
||||
list.forEach((item: Prototype) => {
|
||||
if (!result.find(r => r.options.componentName === item.options.componentName)) {
|
||||
@ -98,7 +98,6 @@ export class Trunk {
|
||||
}
|
||||
|
||||
registerSetter(type: string, setter: CustomView | RegisteredSetter) {
|
||||
// console.warn('Trunk.registerSetter is deprecated');
|
||||
registerSetter(type, setter);
|
||||
}
|
||||
|
||||
|
||||
@ -136,6 +136,7 @@ export interface OldPrototypeConfig {
|
||||
};
|
||||
|
||||
isContainer?: boolean; // => configure.component.isContainer
|
||||
isAbsoluteLayoutContainer?: boolean; // => meta.experimental.isAbsoluteLayoutContainer 是否是绝对定位容器
|
||||
isModal?: boolean; // => configure.component.isModal
|
||||
isFloating?: boolean; // => configure.component.isFloating
|
||||
descriptor?: string; // => configure.component.descriptor
|
||||
@ -171,6 +172,7 @@ export interface OldPrototypeConfig {
|
||||
onResizeEnd?: (e: MouseEvent, triggerDirection: string, dragment: Node) => void;
|
||||
devMode?: string;
|
||||
schema?: ProjectSchema;
|
||||
isTopFixed?: boolean;
|
||||
}
|
||||
|
||||
export interface ISetterConfig {
|
||||
@ -592,6 +594,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
|
||||
configure,
|
||||
transducers,
|
||||
isContainer,
|
||||
isAbsoluteLayoutContainer,
|
||||
rectSelector,
|
||||
isModal,
|
||||
isFloating,
|
||||
@ -620,6 +623,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
|
||||
onResizeEnd, // onResizeEnd
|
||||
devMode,
|
||||
schema,
|
||||
isTopFixed,
|
||||
} = oldConfig;
|
||||
|
||||
const meta: any = {
|
||||
@ -678,7 +682,9 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
|
||||
component.nestingRule = nestingRule;
|
||||
|
||||
// 未考虑清楚的,放在实验性段落
|
||||
const experimental: any = {};
|
||||
const experimental: any = {
|
||||
isAbsoluteLayoutContainer,
|
||||
};
|
||||
if (context) {
|
||||
// for prototype.getContextInfo
|
||||
experimental.context = context;
|
||||
@ -724,6 +730,9 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
|
||||
if (view) {
|
||||
experimental.view = view;
|
||||
}
|
||||
if (isTopFixed) {
|
||||
experimental.isTopFixed = isTopFixed;
|
||||
}
|
||||
if (transducers) {
|
||||
// Array<{ toStatic, toNative }>
|
||||
// ? only twice
|
||||
|
||||
@ -64,16 +64,20 @@ export class Bus {
|
||||
|
||||
const bus = new Bus();
|
||||
|
||||
editor.on('hotkey.callback.call', (data) => {
|
||||
editor?.on('hotkey.callback.call', (data) => {
|
||||
bus.emit('ve.hotkey.callback.call', data);
|
||||
});
|
||||
|
||||
editor.on('history.back', (data) => {
|
||||
editor?.on('history.back', (data) => {
|
||||
bus.emit('ve.history.back', data);
|
||||
});
|
||||
|
||||
editor.on('history.forward', (data) => {
|
||||
editor?.on('history.forward', (data) => {
|
||||
bus.emit('ve.history.forward', data);
|
||||
});
|
||||
|
||||
editor?.on('node.prop.change', (data) => {
|
||||
bus.emit('node.prop.change', data);
|
||||
});
|
||||
|
||||
export default bus;
|
||||
|
||||
@ -2,18 +2,27 @@ import Env from './env';
|
||||
import { isJSSlot, isI18nData, isJSExpression } from '@ali/lowcode-types';
|
||||
import { isPlainObject } from '@ali/lowcode-utils';
|
||||
import i18nUtil from './i18n-util';
|
||||
|
||||
function isVariable(obj: any) {
|
||||
return obj && obj.type === 'variable';
|
||||
}
|
||||
import { editor } from './editor';
|
||||
import { isVariable } from './utils';
|
||||
|
||||
// FIXME: 表达式使用 mock 值,未来live 模式直接使用原始值
|
||||
// TODO: designType
|
||||
export function deepValueParser(obj?: any): any {
|
||||
if (isJSExpression(obj)) {
|
||||
if (editor.get('designMode') === 'live') {
|
||||
return obj;
|
||||
}
|
||||
obj = obj.mock;
|
||||
}
|
||||
// 兼容 ListSetter 中的变量结构
|
||||
if (isVariable(obj)) {
|
||||
if (editor.get('designMode') === 'live') {
|
||||
return {
|
||||
type: 'JSExpression',
|
||||
value: obj.variable,
|
||||
mock: obj.value,
|
||||
};
|
||||
}
|
||||
obj = obj.value;
|
||||
}
|
||||
if (!obj) {
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { isJSBlock, isJSExpression, isJSSlot, isI18nData } from '@ali/lowcode-types';
|
||||
import { isPlainObject, hasOwnProperty } from '@ali/lowcode-utils';
|
||||
import { isJSBlock, isJSExpression, isJSSlot } from '@ali/lowcode-types';
|
||||
import { isPlainObject, hasOwnProperty, cloneDeep, isI18NObject, isUseI18NSetter, convertToI18NObject, isString } from '@ali/lowcode-utils';
|
||||
import { globalContext, Editor } from '@ali/lowcode-editor-core';
|
||||
import { Designer, LiveEditing, TransformStage, Node, getConvertedExtraKey } from '@ali/lowcode-designer';
|
||||
import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
|
||||
import { toCss } from '@ali/vu-css-style';
|
||||
import logger from '@ali/vu-logger';
|
||||
import bus from './bus';
|
||||
import { VE_EVENTS } from './base/const';
|
||||
|
||||
@ -13,6 +11,17 @@ import { Skeleton, SettingsPrimaryPane, registerDefaults } from '@ali/lowcode-ed
|
||||
|
||||
import { deepValueParser } from './deep-value-parser';
|
||||
import { liveEditingRule, liveEditingSaveHander } from './vc-live-editing';
|
||||
import {
|
||||
compatibleReducer,
|
||||
upgradePageLifeCyclesReducer,
|
||||
stylePropsReducer,
|
||||
upgradePropsReducer,
|
||||
filterReducer,
|
||||
removeEmptyPropsReducer,
|
||||
initNodeReducer,
|
||||
liveLifecycleReducer,
|
||||
nodeTopFixedReducer,
|
||||
} from './props-reducers';
|
||||
|
||||
export const editor = new Editor();
|
||||
globalContext.register(editor, Editor);
|
||||
@ -27,251 +36,39 @@ editor.set(Designer, designer);
|
||||
editor.set('designer', designer);
|
||||
|
||||
designer.project.onCurrentDocumentChange((doc) => {
|
||||
doc.onRendererReady(() => {
|
||||
bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
|
||||
});
|
||||
bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY);
|
||||
editor.set('currentDocument', doc);
|
||||
});
|
||||
|
||||
interface Variable {
|
||||
type: 'variable';
|
||||
variable: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
function isVariable(obj: any): obj is Variable {
|
||||
return obj && obj.type === 'variable';
|
||||
}
|
||||
|
||||
function upgradePropsReducer(props: any) {
|
||||
if (!props || !isPlainObject(props)) {
|
||||
return props;
|
||||
}
|
||||
if (isJSBlock(props)) {
|
||||
if (props.value.componentName === 'Slot') {
|
||||
return {
|
||||
type: 'JSSlot',
|
||||
title: (props.value.props as any)?.slotTitle,
|
||||
name: (props.value.props as any)?.slotName,
|
||||
value: props.value.children,
|
||||
};
|
||||
} else {
|
||||
return props.value;
|
||||
}
|
||||
}
|
||||
if (isVariable(props)) {
|
||||
return {
|
||||
type: 'JSExpression',
|
||||
value: props.variable,
|
||||
mock: props.value,
|
||||
};
|
||||
}
|
||||
const newProps: any = {};
|
||||
Object.keys(props).forEach(key => {
|
||||
if (/^__slot__/.test(key) && props[key] === true) {
|
||||
return;
|
||||
}
|
||||
newProps[key] = upgradePropsReducer(props[key]);
|
||||
});
|
||||
return newProps;
|
||||
}
|
||||
|
||||
// 升级 Props
|
||||
designer.addPropsReducer(upgradePropsReducer, TransformStage.Upgrade);
|
||||
|
||||
function getCurrentFieldIds() {
|
||||
const fieldIds: any = [];
|
||||
const nodesMap = designer?.currentDocument?.nodesMap || new Map();
|
||||
nodesMap.forEach((curNode: any) => {
|
||||
const fieldId = nodesMap?.get(curNode.id)?.getPropValue('fieldId');
|
||||
if (fieldId) {
|
||||
fieldIds.push(fieldId);
|
||||
}
|
||||
});
|
||||
return fieldIds;
|
||||
}
|
||||
|
||||
// 节点 props 初始化
|
||||
designer.addPropsReducer((props, node) => {
|
||||
// run initials
|
||||
const newProps: any = {
|
||||
...props,
|
||||
};
|
||||
if (newProps.fieldId) {
|
||||
const fieldIds = getCurrentFieldIds();
|
||||
designer.addPropsReducer(initNodeReducer, TransformStage.Init);
|
||||
|
||||
// 全局的关闭 uniqueIdChecker 信号,在 ve-utils 中实现
|
||||
if (fieldIds.indexOf(props.fieldId) >= 0 && !(window as any).__disable_unique_id_checker__) {
|
||||
newProps.fieldId = undefined;
|
||||
}
|
||||
}
|
||||
const initials = node.componentMeta.getMetadata().experimental?.initials;
|
||||
if (initials) {
|
||||
const getRealValue = (propValue: any) => {
|
||||
if (isVariable(propValue)) {
|
||||
return propValue.value;
|
||||
}
|
||||
if (isJSExpression(propValue)) {
|
||||
return propValue.mock;
|
||||
}
|
||||
return propValue;
|
||||
};
|
||||
initials.forEach((item) => {
|
||||
// FIXME! this implements SettingTarget
|
||||
try {
|
||||
// FIXME! item.name could be 'xxx.xxx'
|
||||
const ov = newProps[item.name];
|
||||
const v = item.initial(node as any, getRealValue(ov));
|
||||
if (ov === undefined && v !== undefined) {
|
||||
newProps[item.name] = v;
|
||||
}
|
||||
} catch (e) {
|
||||
if (hasOwnProperty(props, item.name)) {
|
||||
newProps[item.name] = props[item.name];
|
||||
}
|
||||
}
|
||||
if (newProps[item.name] && !node.props.has(item.name)) {
|
||||
node.props.add(newProps[item.name], item.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
return newProps;
|
||||
}, TransformStage.Init);
|
||||
designer.addPropsReducer(liveLifecycleReducer, TransformStage.Render);
|
||||
|
||||
designer.addPropsReducer((props: any, node: Node) => {
|
||||
if (node.isRoot() && props && props.lifeCycles) {
|
||||
return {
|
||||
...props,
|
||||
lifeCycles: {},
|
||||
};
|
||||
}
|
||||
return props;
|
||||
}, TransformStage.Render);
|
||||
|
||||
function filterReducer(props: any, node: Node): any {
|
||||
const filters = node.componentMeta.getMetadata().experimental?.filters;
|
||||
if (filters && filters.length) {
|
||||
const newProps = { ...props };
|
||||
filters.forEach((item) => {
|
||||
// FIXME! item.name could be 'xxx.xxx'
|
||||
if (!hasOwnProperty(newProps, item.name)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (item.filter(node.settingEntry.getProp(item.name), props[item.name]) === false) {
|
||||
delete newProps[item.name];
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
logger.trace(e);
|
||||
}
|
||||
});
|
||||
return newProps;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
designer.addPropsReducer(filterReducer, TransformStage.Save);
|
||||
designer.addPropsReducer(filterReducer, TransformStage.Render);
|
||||
|
||||
function compatiableReducer(props: any) {
|
||||
if (!props || !isPlainObject(props)) {
|
||||
return props;
|
||||
}
|
||||
if (isJSSlot(props)) {
|
||||
return {
|
||||
type: 'JSBlock',
|
||||
value: {
|
||||
componentName: 'Slot',
|
||||
children: props.value,
|
||||
props: {
|
||||
slotTitle: props.title,
|
||||
slotName: props.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
// 为了能降级到老版本,建议在后期版本去掉以下代码
|
||||
// if (isJSExpression(props) && !props.events) {
|
||||
// return {
|
||||
// type: 'variable',
|
||||
// value: props.mock,
|
||||
// variable: props.value,
|
||||
// }
|
||||
// }
|
||||
const newProps: any = {};
|
||||
Object.entries<any>(props).forEach(([key, val]) => {
|
||||
newProps[key] = compatiableReducer(val);
|
||||
});
|
||||
return newProps;
|
||||
}
|
||||
// FIXME: Dirty fix, will remove this reducer
|
||||
designer.addPropsReducer(compatiableReducer, TransformStage.Save);
|
||||
designer.addPropsReducer(compatibleReducer, TransformStage.Save);
|
||||
// 兼容历史版本的 Page 组件
|
||||
designer.addPropsReducer((props: any, node: Node) => {
|
||||
const lifeCycleNames = ['didMount', 'willUnmount'];
|
||||
if (node.isRoot()) {
|
||||
lifeCycleNames.forEach(key => {
|
||||
if (props[key]) {
|
||||
const lifeCycles = node.props.getPropValue(getConvertedExtraKey('lifeCycles')) || {};
|
||||
lifeCycles[key] = props[key];
|
||||
node.props.setPropValue(getConvertedExtraKey('lifeCycles'), lifeCycles);
|
||||
}
|
||||
});
|
||||
}
|
||||
return props;
|
||||
}, TransformStage.Save);
|
||||
designer.addPropsReducer(upgradePageLifeCyclesReducer, TransformStage.Save);
|
||||
|
||||
// 设计器组件样式处理
|
||||
function stylePropsReducer(props: any, node: any) {
|
||||
if (props && typeof props === 'object' && props.__style__) {
|
||||
const cssId = `_style_pesudo_${ node.id.replace(/\$/g, '_')}`;
|
||||
const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`;
|
||||
const styleProp = props.__style__;
|
||||
appendStyleNode(props, styleProp, cssClass, cssId);
|
||||
}
|
||||
if (props && typeof props === 'object' && props.pageStyle) {
|
||||
const cssId = '_style_pesudo_engine-document';
|
||||
const cssClass = 'engine-document';
|
||||
const styleProp = props.pageStyle;
|
||||
appendStyleNode(props, styleProp, cssClass, cssId);
|
||||
}
|
||||
if (props && typeof props === 'object' && props.containerStyle) {
|
||||
const cssId = `_style_pesudo_${ node.id}`;
|
||||
const cssClass = `_css_pesudo_${ node.id.replace(/\$/g, '_')}`;
|
||||
const styleProp = props.containerStyle;
|
||||
appendStyleNode(props, styleProp, cssClass, cssId);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
function appendStyleNode(props: any, styleProp: any, cssClass: string, cssId: string) {
|
||||
const doc = designer.currentDocument?.simulator?.contentDocument;
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
const dom = doc.getElementById(cssId);
|
||||
if (dom) {
|
||||
dom.parentNode?.removeChild(dom);
|
||||
}
|
||||
if (typeof styleProp === 'object') {
|
||||
styleProp = toCss(styleProp);
|
||||
}
|
||||
if (typeof styleProp === 'string') {
|
||||
const s = doc.createElement('style');
|
||||
props.className = cssClass;
|
||||
s.setAttribute('type', 'text/css');
|
||||
s.setAttribute('id', cssId);
|
||||
doc.getElementsByTagName('head')[0].appendChild(s);
|
||||
|
||||
s.appendChild(doc.createTextNode(styleProp.replace(/(\d+)rpx/g, (a, b) => {
|
||||
return `${b / 2}px`;
|
||||
}).replace(/:root/g, `.${ cssClass}`)));
|
||||
}
|
||||
}
|
||||
designer.addPropsReducer(stylePropsReducer, TransformStage.Render);
|
||||
|
||||
// 国际化 & Expression 渲染时处理
|
||||
designer.addPropsReducer(deepValueParser, TransformStage.Render);
|
||||
|
||||
// Init 的时候没有拿到 dataSource, 只能在 Render 和 Save 的时候都调用一次,理论上执行时机在 Init
|
||||
// Render 和 Save 都要各调用一次,感觉也是有问题的,是不是应该在 Render 执行一次就行了?见上 filterReducer 也是一样的处理方式。
|
||||
designer.addPropsReducer(removeEmptyPropsReducer, TransformStage.Render);
|
||||
designer.addPropsReducer(removeEmptyPropsReducer, TransformStage.Save);
|
||||
|
||||
designer.addPropsReducer(nodeTopFixedReducer, TransformStage.Render);
|
||||
designer.addPropsReducer(nodeTopFixedReducer, TransformStage.Save);
|
||||
|
||||
skeleton.add({
|
||||
area: 'mainArea',
|
||||
name: 'designer',
|
||||
@ -283,6 +80,9 @@ skeleton.add({
|
||||
name: 'settingsPane',
|
||||
type: 'Panel',
|
||||
content: SettingsPrimaryPane,
|
||||
props: {
|
||||
ignoreRoot: true,
|
||||
},
|
||||
});
|
||||
skeleton.add({
|
||||
area: 'leftArea',
|
||||
|
||||
@ -80,6 +80,11 @@ export class Env {
|
||||
};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.envs = {};
|
||||
this.featureMap = {};
|
||||
}
|
||||
|
||||
getAliSchemaVersion() {
|
||||
return ALI_SCHEMA_VERSION;
|
||||
}
|
||||
|
||||
@ -108,14 +108,14 @@ export class Flags {
|
||||
return;
|
||||
}
|
||||
|
||||
const doe = document.documentElement;
|
||||
const doc = document.documentElement;
|
||||
if (this.lastFlags) {
|
||||
this.lastFlags.filter((flag: string) => this.flags.indexOf(flag) < 0).forEach((flag) => {
|
||||
doe.classList.remove(`engine-${flag}`);
|
||||
doc.classList.remove(`engine-${flag}`);
|
||||
});
|
||||
}
|
||||
this.flags.forEach((flag) => {
|
||||
doe.classList.add(`engine-${flag}`);
|
||||
doc.classList.add(`engine-${flag}`);
|
||||
});
|
||||
|
||||
this.lastFlags = this.flags.slice(0);
|
||||
|
||||
@ -5,6 +5,7 @@ import logger from '@ali/vu-logger';
|
||||
import { render } from 'react-dom';
|
||||
import I18nUtil from './i18n-util';
|
||||
import { hotkey as Hotkey, monitor } from '@ali/lowcode-editor-core';
|
||||
import { registerMetadataTransducer } from '@ali/lowcode-designer';
|
||||
import { createElement } from 'react';
|
||||
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const';
|
||||
import Bus from './bus';
|
||||
@ -14,6 +15,7 @@ import Panes from './panes';
|
||||
import Exchange from './exchange';
|
||||
import context from './context';
|
||||
import VisualManager from './base/visualManager';
|
||||
import VisualDesigner from './base/visualDesigner';
|
||||
import Trunk from './bundle/trunk';
|
||||
import Prototype from './bundle/prototype';
|
||||
import Bundle from './bundle/bundle';
|
||||
@ -22,10 +24,11 @@ import * as Field from './fields';
|
||||
import Prop from './prop';
|
||||
import Env from './env';
|
||||
import DragEngine from './drag-engine';
|
||||
// import Flags from './base/flags';
|
||||
import Viewport from './viewport';
|
||||
import Project from './project';
|
||||
|
||||
import Symbols from './symbols';
|
||||
import '@ali/lowcode-editor-setters';
|
||||
|
||||
import './vision.less';
|
||||
|
||||
@ -60,6 +63,7 @@ const ui = {
|
||||
|
||||
const modules = {
|
||||
VisualManager,
|
||||
VisualDesigner,
|
||||
I18nUtil,
|
||||
Prop,
|
||||
};
|
||||
@ -107,6 +111,8 @@ const VisualEngine = {
|
||||
Project,
|
||||
logger,
|
||||
Symbols,
|
||||
registerMetadataTransducer,
|
||||
// Flags,
|
||||
};
|
||||
|
||||
(window as any).VisualEngine = VisualEngine;
|
||||
@ -156,12 +162,18 @@ export {
|
||||
Project,
|
||||
logger,
|
||||
Symbols,
|
||||
registerMetadataTransducer,
|
||||
};
|
||||
|
||||
const version = '6.0.0(LowcodeEngine 0.9.3)';
|
||||
const version = '6.0.0 (LowcodeEngine 0.9.32)';
|
||||
|
||||
console.log(
|
||||
`%c VisionEngine %c v${version} `,
|
||||
<<<<<<< HEAD
|
||||
'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060;font-weight:bold;',
|
||||
'padding: 2px 1px; border-radius: 0 3px 3px 0; color: #fff; background: #42c02e;font-weight:bold;',
|
||||
=======
|
||||
'padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060; font-weight: bold;',
|
||||
'padding: 2px 1px; border-radius: 0 3px 3px 0; color: #fff; background: #42c02e; font-weight: bold;',
|
||||
>>>>>>> origin/refactor/vision-code-split
|
||||
);
|
||||
|
||||
@ -32,27 +32,46 @@ const pages = Object.assign(project, {
|
||||
if (!pages || !Array.isArray(pages) || pages.length === 0) {
|
||||
throw new Error('pages schema 不合法');
|
||||
}
|
||||
|
||||
let componentsTree: any;
|
||||
if (isPageDataV1(pages[0])) {
|
||||
componentsTree = [pages[0].layout];
|
||||
// todo: miniapp
|
||||
let componentsTree: any = [];
|
||||
if (window.pageConfig?.isNoCodeMiniApp) {
|
||||
// 小程序多页面
|
||||
pages.forEach((item: any) => {
|
||||
if (isPageDataV1(item)) {
|
||||
componentsTree.push(item.layout);
|
||||
} else {
|
||||
componentsTree.push(item.componentsTree[0]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
componentsTree = pages[0].componentsTree;
|
||||
if (componentsTree[0]) {
|
||||
componentsTree[0].componentName = componentsTree[0].componentName || 'Page';
|
||||
// FIXME
|
||||
if (componentsTree[0].componentName === 'Page' || componentsTree[0].componentName === 'Component') {
|
||||
componentsTree[0].methods = {};
|
||||
if (isPageDataV1(pages[0])) {
|
||||
componentsTree = [pages[0].layout];
|
||||
} else {
|
||||
// if (!pages[0].componentsTree) return;
|
||||
componentsTree = pages[0].componentsTree;
|
||||
if (componentsTree[0]) {
|
||||
componentsTree[0].componentName = componentsTree[0].componentName || 'Page';
|
||||
// FIXME
|
||||
if (componentsTree[0].componentName === 'Page' || componentsTree[0].componentName === 'Component') {
|
||||
componentsTree[0].methods = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentsTree.forEach((item: any) => {
|
||||
item.componentName = item.componentName || 'Page';
|
||||
if (item.componentName === 'Page' || item.componentName === 'Component') {
|
||||
item.methods = {};
|
||||
}
|
||||
});
|
||||
project.load(
|
||||
{
|
||||
version: '1.0.0',
|
||||
componentsMap: [],
|
||||
componentsTree,
|
||||
id: pages[0].id,
|
||||
config: project.config,
|
||||
},
|
||||
true,
|
||||
);
|
||||
@ -105,6 +124,9 @@ Object.defineProperty(pages, 'currentPage', {
|
||||
get() {
|
||||
return project.currentDocument;
|
||||
},
|
||||
set(_currentPage) {
|
||||
// do nothing
|
||||
},
|
||||
});
|
||||
|
||||
pages.onCurrentPageChange((page: DocumentModel) => {
|
||||
|
||||
@ -47,6 +47,8 @@ export interface OldPaneConfig {
|
||||
index?: number; // todo
|
||||
isAction?: boolean; // as normal dock
|
||||
fullScreen?: boolean; // todo
|
||||
canSetFixed?: boolean; // 是否可以设置固定模式
|
||||
defaultFixed?: boolean; // 是否默认固定
|
||||
}
|
||||
|
||||
function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: string } {
|
||||
@ -64,11 +66,24 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
|
||||
contentProps: props,
|
||||
index: index || props?.index,
|
||||
};
|
||||
|
||||
if (type === 'dock') {
|
||||
newConfig.type = 'PanelDock';
|
||||
newConfig.area = 'left';
|
||||
newConfig.props.description = description || title;
|
||||
const { contents, hideTitleBar, tip, width, maxWidth, height, maxHeight, menu, isAction, canSetFixed } = config;
|
||||
const {
|
||||
contents,
|
||||
hideTitleBar,
|
||||
tip,
|
||||
width,
|
||||
maxWidth,
|
||||
height,
|
||||
maxHeight,
|
||||
menu,
|
||||
isAction,
|
||||
canSetFixed,
|
||||
defaultFixed,
|
||||
} = config;
|
||||
if (menu) {
|
||||
newConfig.props.title = menu;
|
||||
}
|
||||
@ -84,10 +99,12 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
|
||||
height,
|
||||
maxHeight,
|
||||
canSetFixed,
|
||||
onInit: init,
|
||||
onDestroy: destroy,
|
||||
};
|
||||
|
||||
if (defaultFixed) {
|
||||
newConfig.panelProps.area = 'leftFixedArea';
|
||||
}
|
||||
|
||||
if (contents && Array.isArray(contents)) {
|
||||
newConfig.content = contents.map(({ title, content, tip }, index) => {
|
||||
return {
|
||||
@ -103,23 +120,21 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (type === 'action') {
|
||||
newConfig.area = 'top';
|
||||
newConfig.type = 'Dock';
|
||||
} else if (type === 'tab') {
|
||||
newConfig.area = 'right';
|
||||
newConfig.type = 'Panel';
|
||||
} else if (type === 'stage') {
|
||||
newConfig.area = 'stages';
|
||||
newConfig.type = 'Widget';
|
||||
} else {
|
||||
newConfig.props.onInit = init;
|
||||
newConfig.props.onDestroy = destroy;
|
||||
if (type === 'action') {
|
||||
newConfig.area = 'top';
|
||||
newConfig.type = 'Dock';
|
||||
} else if (type === 'tab') {
|
||||
newConfig.area = 'right';
|
||||
newConfig.type = 'Panel';
|
||||
} else if (type === 'stage') {
|
||||
newConfig.area = 'stages';
|
||||
newConfig.type = 'Widget';
|
||||
} else {
|
||||
newConfig.area = 'main';
|
||||
newConfig.type = 'Widget';
|
||||
}
|
||||
newConfig.area = 'main';
|
||||
newConfig.type = 'Widget';
|
||||
}
|
||||
newConfig.props.onInit = init;
|
||||
newConfig.props.onDestroy = destroy;
|
||||
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
export class Project {
|
||||
private schema: any;
|
||||
import { designer } from './editor';
|
||||
|
||||
constructor() {
|
||||
this.schema = {};
|
||||
}
|
||||
const { project } = designer;
|
||||
|
||||
getSchema() {
|
||||
return this.schema;
|
||||
}
|
||||
Object.assign(project, {
|
||||
getSchema(): any {
|
||||
return this.schema || {};
|
||||
},
|
||||
|
||||
setSchema(schema: any) {
|
||||
this.schema = schema;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
export default new Project();
|
||||
setConfig(config: any) {
|
||||
this.set('config', config);
|
||||
},
|
||||
});
|
||||
|
||||
export default project;
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import {
|
||||
isPlainObject,
|
||||
} from '@ali/lowcode-utils';
|
||||
import { isJSExpression, isJSSlot } from '@ali/lowcode-types';
|
||||
|
||||
export function compatibleReducer(props: any) {
|
||||
if (!props || !isPlainObject(props)) {
|
||||
return props;
|
||||
}
|
||||
// 为了能降级到老版本,建议在后期版本去掉以下代码
|
||||
if (isJSSlot(props)) {
|
||||
return {
|
||||
type: 'JSBlock',
|
||||
value: {
|
||||
componentName: 'Slot',
|
||||
children: props.value,
|
||||
props: {
|
||||
slotTitle: props.title,
|
||||
slotName: props.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (isJSExpression(props) && !props.events) {
|
||||
return {
|
||||
type: 'variable',
|
||||
value: props.mock,
|
||||
variable: props.value,
|
||||
};
|
||||
}
|
||||
const newProps: any = {};
|
||||
Object.entries<any>(props).forEach(([key, val]) => {
|
||||
newProps[key] = compatibleReducer(val);
|
||||
});
|
||||
return newProps;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user