diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..98523af22 --- /dev/null +++ b/.vscode/launch.json @@ -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"] + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..8e40ba24f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1110 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.13.1-29](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-28...v0.13.1-29) (2020-12-03) + + +### Bug Fixes + +* 修复 setDevice 的时机,从 currentDocument -> simualtor ([0f14884](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0f14884)) + + + + + +## [0.13.1-28](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-27...v0.13.1-28) (2020-12-03) + + +### Bug Fixes + +* 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)) + + + + + +## [0.13.1-27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-26...v0.13.1-27) (2020-12-02) + + +### Bug Fixes + +* 修复 registerAddon 函数 ([309920a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/309920a)) + + + + + +## [0.13.1-26](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-25...v0.13.1-26) (2020-12-02) + + +### Bug Fixes + +* rax 组件无法拖拽的问题 ([3a4d47a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3a4d47a)) + + + + + +## [0.13.1-25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-24...v0.13.1-25) (2020-12-01) + + + + +**Note:** Version bump only for package undefined + + +## [0.13.1-24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-23...v0.13.1-24) (2020-11-26) + + +### Bug Fixes + +* 优化选中页面根节点时, 直接点击组件面板插入位置 ([c1ca2c6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c1ca2c6)) + + + + + +## [0.13.1-23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-22...v0.13.1-23) (2020-11-25) + + + + +**Note:** Version bump only for package undefined + + +## [0.13.1-22](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-19...v0.13.1-22) (2020-11-25) + + +### Features + +* 支持无组件配置的设置面板形态 ([46c5bf9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/46c5bf9)) + + + + + +## [0.13.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-18...v0.13.1-19) (2020-11-24) + + + + +**Note:** Version bump only for package undefined + + +## [0.13.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-15...v0.13.1-18) (2020-11-20) + + +### Bug Fixes + +* 修复 setDevice 里获取 currentDocument 的逻辑 ([275b7aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/275b7aa)) + + + + + +## [0.13.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-15) (2020-11-18) + + +### Bug Fixes + +* build 版本号修改 ([fd71970](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fd71970)) +* 修复 project.unload 无法正常删除 document 的 bug ([5e6e91b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5e6e91b)) +* 去掉 AppHelper ([da9bb7f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/da9bb7f)) +* 解决 device 变化后不刷新视图的 bug ([11e8e02](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/11e8e02)) + + +### Features + +* 支持绝对布局容器中不显示 dragHost ([6eb9436](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6eb9436)) +* 暴露 registerMetadataTransducer 接口 ([cd12677](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cd12677)) + + + + +## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17) + + +### Features + +* 低成本方案支持绝对布局容器 ([a6067e8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a6067e8)) + + + + +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + +### Bug Fixes + +* 样式调整 ([2228ab9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2228ab9)) + + + + +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + +### Bug Fixes + +* build.json ([3594455](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3594455)) +* build.json ([48ee29d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/48ee29d)) +* build.json ([f993586](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f993586)) + + + + +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + + +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + +### Bug Fixes + +* stage-box 样式优化 ([de4074a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/de4074a)) +* 样式兼容 ([0f2dea4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0f2dea4)) +* 样式微调 ([9816859](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9816859)) + + +### Features + +* getSuitableInsertion 支持 node 参数,checkNestingDown 将 target 转换为 Node ([5425864](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5425864)) +* 使用 release/1.0.0 的 editor-setters ([80d74d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/80d74d6)) + + + + +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + +### Bug Fixes + +* lc-container-placeholder 样式修改 ([d939285](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d939285)) +* pane 宽度统一设置为 300 ([ff576b9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ff576b9)) + + + + +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + +### Bug Fixes + +* remove engine-tabpane css className ([d2fe75d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d2fe75d)) + + + + +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + +### Bug Fixes + +* update package.json ([dfb2b47](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dfb2b47)) + + + + +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + + +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + + +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + + +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + + +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + +### Bug Fixes + +* build 配置文件修改 ([91cfb56](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/91cfb56)) +* designer.componentsMap ([d8d32a2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d8d32a2)) +* preset-vision 引入默认 setter,支持物料中心组件 ([0513318](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0513318)) +* 使用 componentMeta.isModal 代替 protoType.isModal() ([b787dc4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b787dc4)) +* 使用引擎标准的 lc-container-placeholder,支持 children 属性 ([b262665](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b262665)) + + +### Features + +* skeleton 增加全局 catch ([58b8200](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/58b8200)) + + + + + +## [0.13.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-12) (2020-11-18) + + +### Bug Fixes + +* build 版本号修改 ([fd71970](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fd71970)) +* 修复 project.unload 无法正常删除 document 的 bug ([5e6e91b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5e6e91b)) +* 去掉 AppHelper ([da9bb7f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/da9bb7f)) +* 解决 device 变化后不刷新视图的 bug ([11e8e02](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/11e8e02)) + + +### Features + +* 支持绝对布局容器中不显示 dragHost ([6eb9436](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6eb9436)) +* 暴露 registerMetadataTransducer 接口 ([cd12677](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cd12677)) + + + + +## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17) + + +### Features + +* 低成本方案支持绝对布局容器 ([a6067e8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a6067e8)) + + + + +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + +### Bug Fixes + +* 样式调整 ([2228ab9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2228ab9)) + + + + +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + +### Bug Fixes + +* build.json ([3594455](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3594455)) +* build.json ([48ee29d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/48ee29d)) +* build.json ([f993586](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f993586)) + + + + +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + + +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + +### Bug Fixes + +* stage-box 样式优化 ([de4074a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/de4074a)) +* 样式兼容 ([0f2dea4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0f2dea4)) +* 样式微调 ([9816859](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9816859)) + + +### Features + +* getSuitableInsertion 支持 node 参数,checkNestingDown 将 target 转换为 Node ([5425864](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5425864)) +* 使用 release/1.0.0 的 editor-setters ([80d74d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/80d74d6)) + + + + +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + +### Bug Fixes + +* lc-container-placeholder 样式修改 ([d939285](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d939285)) +* pane 宽度统一设置为 300 ([ff576b9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ff576b9)) + + + + +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + +### Bug Fixes + +* remove engine-tabpane css className ([d2fe75d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d2fe75d)) + + + + +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + +### Bug Fixes + +* update package.json ([dfb2b47](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dfb2b47)) + + + + +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + + +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + + +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + + +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + + +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + +### Bug Fixes + +* build 配置文件修改 ([91cfb56](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/91cfb56)) +* designer.componentsMap ([d8d32a2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d8d32a2)) +* preset-vision 引入默认 setter,支持物料中心组件 ([0513318](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0513318)) +* 使用 componentMeta.isModal 代替 protoType.isModal() ([b787dc4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b787dc4)) +* 使用引擎标准的 lc-container-placeholder,支持 children 属性 ([b262665](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b262665)) + + +### Features + +* skeleton 增加全局 catch ([58b8200](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/58b8200)) + + + + + +## [0.13.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-10...v0.13.1-11) (2020-11-02) + + +### Bug Fixes + +* 解决 slot 在关闭时没有正常回收节点 ([642a404](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/642a404)) + + + + + +## [0.13.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-9...v0.13.1-10) (2020-10-26) + + + + +**Note:** Version bump only for package undefined + + +## [0.13.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-9) (2020-10-26) + + +### Bug Fixes + +* 处理 slot 开启/关闭操作中, 无法正常创建 slot 的bug ([3e86d09](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3e86d09)) + + + + + +## [0.13.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-8) (2020-10-26) + + + + +**Note:** Version bump only for package undefined + + +## [0.13.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-6...v0.13.1-7) (2020-10-23) + + +### Features + +* 兼容 didDropOut 接口 ([2655c4a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2655c4a)) + + + + + +## [0.13.1-6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-5...v0.13.1-6) (2020-10-22) + + +### Bug Fixes + +* 修复修改 componentsMap 后无法刷新视图的 bug ([a1e7f21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a1e7f21)) + + + + + +## [0.13.1-5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-4...v0.13.1-5) (2020-10-20) + + + + +**Note:** Version bump only for package undefined + + +## [0.13.1-4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-3...v0.13.1-4) (2020-10-20) + + + + +**Note:** Version bump only for package undefined + + +## [0.13.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-2...v0.13.1-3) (2020-10-19) + + +### Bug Fixes + +* 修复 JSSlot 被转成 i18n 结构 ([f2c3292](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f2c3292)) + + + + + +## [0.13.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.13.1-2) (2020-10-19) + + +### Bug Fixes + +* convertI18nObject ([66d43f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66d43f2)) +* **editor-skeleton:** fix dynamic setter support in mixed-setter ([fca10ac](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fca10ac)) + + + + + +## [0.13.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-3...v0.13.1-1) (2020-10-12) + + + + +**Note:** Version bump only for package undefined + + +## [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)) + + + + + +## [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 + +* i18n 绑定变量后消失 ([0aafafe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0aafafe)) + + + + + +## [0.12.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-9...v0.12.1-1) (2020-09-22) + + +### Bug Fixes + +* path with / ([2470363](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2470363)) + + + + + +## [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)) + + + + + +## [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 + +* JSSlot 格式也需要转换成 JSBlock ([e591aba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e591aba)) +* revert 一段错误修改的代码 & 优化代码 ([614dbf2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/614dbf2)) +* save 的时候删除空的 props ([69cda3e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/69cda3e)) +* vision兼容标准api ([394db8d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/394db8d)) +* 修复修改 勾选框、富文本编辑器、下拉选择 等组件标题报错 ([8ba26ee](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8ba26ee)) +* 删除一个 console log ([79b7042](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/79b7042)) +* 去除乐高vision兼容影响 ([9e47561](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9e47561)) + + + + + +## [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)) + + + + + +## [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)) +* should set field ([20c3b27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/20c3b27)) +* should set field - demo-server ([6cfa0aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6cfa0aa)) +* source-editor bug & exp-setter bug ([5cd88d4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5cd88d4)) + + +### Features + +* 补充一些 vision API ([933cef1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/933cef1)) + + + + + +## [1.0.9-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-1...v1.0.9-2) (2020-09-14) + + + + +**Note:** Version bump only for package undefined + + +## [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 undefined + + +## 1.0.9-0 (2020-09-14) + + +### Bug Fixes + +* fieldId 重复问题 ([e761b1a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e761b1a)) +* (location) => ({location}) ([0e75b8e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e75b8e)) +* [material-parser]fix bug of main field & remove useless debugger ([8fde0ec](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fde0ec)) +* 🐛 add history pane for vision demo ([3ce7079](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3ce7079)) +* 🐛 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)) +* 🐛 add tip on setter title ([c93c1d0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c93c1d0)) +* 🐛 after event name & TabItem parent limitation ([76fb0b3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/76fb0b3)) +* 🐛 bugs about deps ([1eabd50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1eabd50)) +* 🐛 Card component's settings ([f44e7ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f44e7ab)) +* 🐛 Cascader init status ([e4a28c4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e4a28c4)) +* 🐛 children in props ([fe0ace8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fe0ace8)) +* 🐛 codeout btn fix ([afda7d4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/afda7d4)) +* 🐛 Collapse render error ([6fed968](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6fed968)) +* 🐛 empty ([927c8f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/927c8f2)) +* 🐛 error when quick search ([801d954](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/801d954)) +* 🐛 eslint ([e3ca0bd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e3ca0bd)) +* 🐛 eslint ([14803dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/14803dd)) +* 🐛 fix bug of transforming type ([ebbe58d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ebbe58d)) +* 🐛 fix bug of unevaluated default values ([22b667c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/22b667c)) +* 🐛 fix bug of validate schema ([3f97523](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3f97523)) +* 🐛 fix Menu & MenuButton assets cfg ([3d40aa2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3d40aa2)) +* 🐛 fix remaining bugs of unevaluated default values ([7947134](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7947134)) +* 🐛 get deps info from slot ([6c3ae36](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6c3ae36)) +* 🐛 getPrototype is undefined ([95b3409](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/95b3409)) +* 🐛 group chunks by filetype family ([db144a9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/db144a9)) +* 🐛 history pane zindex ([48f3be1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/48f3be1)) +* 🐛 i18n面板不生效 ([27cd916](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/27cd916)) +* 🐛 loop bug ([8f53910](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8f53910)) +* 🐛 mainArea 画布切换,MainArea 重新初始化导致 iframe 初始化报错 ([5054d06](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5054d06)) +* 🐛 Menu Items ([5ecacef](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5ecacef)) +* 🐛 repair children before deps analyze ([737d06e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/737d06e)) +* 🐛 save and generator last page ([3e4254c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3e4254c)) +* 🐛 style setter not working ([c88ea6b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c88ea6b)) +* 🐛 support JSFunction type ([9061e4b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9061e4b)) +* 🐛 Tab & TabItem assets config ([0cc08fb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0cc08fb)) +* 🐛 Tag assets ([b460dcf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b460dcf)) +* 🐛 Tag components setting ([de941da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/de941da)) +* 🐛 Timeline asset config ([436dadd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/436dadd)) +* 🐛 title缺少icon字段,临时转接一下 ([2f9bb25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2f9bb25)) +* 🐛 update shell ([15fb964](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/15fb964)) +* 🐛 update start scripts ([6330f21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6330f21)) +* 🐛 use intl ([a22e66a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a22e66a)) +* 🐛 use JsonSetter as dataSource Setter ([553f924](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/553f924)) +* 🐛 修复主设置面板下 stagebox 的样式问题 ([d5a98c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d5a98c0)) +* 🐛 修复区块面板命名冲突的问题 ([de50ebf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/de50ebf)) +* 🐛 修复富文本高级内容弹层样式问题 ([edb480d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/edb480d)) +* 🐛 修复编辑面板 ([a0bad77](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a0bad77)) +* 🐛 增加 getAddonData api ([68b7e29](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/68b7e29)) +* 🐛 增加传入组件children的默认值[], 对之前的非健壮组件做兼容 ([af0f2df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/af0f2df)) +* 🐛 增加剪切快捷键 ([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)) +* 🐛 更改复杂类型生成工具的接口形式,减少调用复杂度 ([ce616b5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce616b5)) +* 🐛 添加 loop 和 condition 的判断 ([b521ebe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b521ebe)) +* 🐛 清理无用代码 ([015b58a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/015b58a)) +* 🐛 用 isI18nData 判断 meta title ([732bccf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/732bccf)) +* 🐛 移动快捷键 ([7c8a27c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7c8a27c)) +* 🐛 绑定动作无法打开代码面板 ([160d6f7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/160d6f7)) +* 🐛 解决点击组件时无法聚焦到点中的组件上的问题 ([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)) +* 😈 table 无法选中问题 ([34825f8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/34825f8)) +* 😊修复arraysetter删除不更新问题 ([9d8a730](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9d8a730)) +* 1. 修复dialog拖入不显示问题 2. dialog 只能在根节点下 3. 引入 modalNodeManager ([65977e7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/65977e7)) +* add component ([995785d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/995785d)) +* add extraEnv ([9058ac8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9058ac8)) +* add FaultComponent style ([77b0b2c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/77b0b2c)) +* add pages.toData method ([95d3cb3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/95d3cb3)) +* add unique key ([e48307d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e48307d)) +* border action style ([6b91535](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6b91535)) +* call consumer ([70a1472](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/70a1472)) +* 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)) +* cloneElement bug ([d5c5614](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d5c5614)) +* 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)) +* condition增加异常保护 ([8324368](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8324368)) +* CR 问题修复 ([f054cbf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f054cbf)) +* 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)) +* demo ([9142805](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9142805)) +* demo data ([b4a27fc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b4a27fc)) +* demo 中引入locode-editor-general ([1f03857](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f03857)) +* depend ([c90996d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c90996d)) +* div 不显示问题 ([1b6533c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1b6533c)) +* documentModel toData 方法 ([1ea0d73](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1ea0d73)) +* dropdown and menu schema ([ae1d125](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ae1d125)) +* editor ([ccd9162](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ccd9162)) +* enhance compile config ([2899149](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2899149)) +* export data ([41f7724](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/41f7724)) +* factory api ([237b866](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/237b866)) +* 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)) +* findDOMNodes ([7abf606](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7abf606)) +* findDOMNodes error ([6f5342d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6f5342d)) +* fix bug of build errors ([770a1b6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/770a1b6)) +* fix bug of missing ajv ([a37d655](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a37d655)) +* fix bug of missing types in material-parser ([9ce0a73](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9ce0a73)) +* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df)) +* fix function-setter bug ([dced647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dced647)) +* fix mixsetter style ([0ecce83](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0ecce83)) +* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375)) +* fix source edit bug ([047247c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/047247c)) +* force schema ([6d0a8ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d0a8ff)) +* formUuid 可能不在 url 中 ([8657ab8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8657ab8)) +* get pakcage.json ([8b99a51](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8b99a51)) +* getDocId ([34341d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/34341d6)) +* 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)) +* history API ([e411687](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e411687)) +* history.listen({location}) => history.listen(location) ([25a6390](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/25a6390)) +* i18n parser & setting ([dbdd9e4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dbdd9e4)) +* intl ([8a061ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8a061ab)) +* 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)) +* left-fixed-pane 设置宽度不生效 ([a5f0d5e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5f0d5e)) +* live editing outline colore ([791771c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/791771c)) +* merge ([ac55847](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ac55847)) +* miniapp compwrapper ref ([5ae08f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5ae08f5)) +* miniapp demo ([7c42473](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7c42473)) +* modal node locate ([9a72dd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9a72dd7)) +* modify docId ([dc95033](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dc95033)) +* 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)) +* onDocumentChange ([eb60d1f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eb60d1f)) +* onReRender ([29ea5f7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/29ea5f7)) +* panel visible time ([18ac1fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18ac1fa)) +* parse custom methods function ([87d8b86](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/87d8b86)) +* patch prototype ([f20bfaa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f20bfaa)) +* path resolve problem ([b12c0f8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b12c0f8)) +* plugin-desiger 支持从 editor 获取 device 参数 ([43bc29b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/43bc29b)) +* plugin-designer ([2dfbcd4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2dfbcd4)) +* post process file error ([389eaf7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/389eaf7)) +* prop type=UNSET 时返回 undefined ([f437f30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f437f30)) +* props.getNode 防死循环 ([444e25c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/444e25c)) +* quickSearch error ([a8009ef](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a8009ef)) +* rax finddom 方法重写 ([1d90928](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1d90928)) +* raxFindDOMNodes ([90430f3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/90430f3)) +* react simulator rendererContainer props ([6e1eac0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e1eac0)) +* remove 1.txt ([796d09d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/796d09d)) +* remove abstract identifer ([2e45266](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e45266)) +* remove console ([6c703d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6c703d8)) +* remove console ([6889123](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6889123)) +* remove debugger ([a835dc6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a835dc6)) +* remove vision dependency from plugin-undo-redo ([08b93f9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/08b93f9)) +* rename MixinSetter to MixedSetter ([0e9a740](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e9a740)) +* render children ([487f257](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/487f257)) +* render error样式 ([d601d5e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d601d5e)) +* rendererContainer ([486713a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/486713a)) +* revert ([dad21e2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dad21e2)) +* rm demo in lib ([55630d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/55630d6)) +* router change ([920e584](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/920e584)) +* router rerender ([d886abc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d886abc)) +* same name chunk case ([d6855e2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d6855e2)) +* schema should be componentsTree ([69a2a89](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/69a2a89)) +* set i18n setter value when change mixed setter ([72d81c2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/72d81c2)) +* setter 报错不影响页面渲染 ([c0a6022](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c0a6022)) +* setting pane tab active ([06d7b50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/06d7b50)) +* setting 面板样式调整 ([922b361](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/922b361)) +* 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)) +* settings pane ([27db010](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/27db010)) +* skeleton.topArea.hide() 不生效的问题 ([6d2b955](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d2b955)) +* 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)) +* 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)) +* tip direction ([f51d496](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f51d496)) +* topbar search icon ([0447801](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0447801)) +* 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)) +* try get settingfield ([56f242f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56f242f)) +* ts type ([1732e7d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1732e7d)) +* typeName 为 any 时转换出的 MixedSetter 缺少 props 的问题 ([4b9084f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4b9084f)) +* uniqueid ([8db52f0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8db52f0)) +* update package.json ([f1ec59c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f1ec59c)) +* updateProps before init ([760e6a6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/760e6a6)) +* upgradePropsReducer ([e68977f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e68977f)) +* use webpack for package ([b350a88](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b350a88)) +* using the same eslint config ([5532c94](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5532c94)) +* 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)) +* VC-Filter组件的适配问题 ([1f581b8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f581b8)) +* 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)) +* VisualEngine 仍使用 ifframe 中 window 对象 ([9d19731](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9d19731)) +* window.parent ([7e1b8ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e1b8ff)) +* 不对外暴露 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)) +* 临时解决 lowCodeComponent 性能问题 ([25b4ba2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/25b4ba2)) +* 优化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)) +* 优化画布中点击事件屏蔽,增加富文本组件的部分屏蔽 ([ec08c6c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ec08c6c)) +* 优化画布中点击事件屏蔽,增加富文本组件的部分屏蔽 ([a5b6557](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5b6557)) +* 低代码组件 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)) +* 使用深拷贝赋值并修改 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)) +* 修复 condition 代码导出错误 ([57b30cf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/57b30cf)) +* 修复 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)) +* 修复 toolbar 弹出位置异常 ([b40b9a4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40b9a4)) +* 修复bool类型对应的setter ([2df6230](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2df6230)) +* 修复js面板引用计数问题 ([fcc1a6f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fcc1a6f)) +* 修复低代码组件内部部分区域无法选中 ([f0adaa5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f0adaa5)) +* 修复低代码组件设计器、区块设计器根节点为 Page 的问题,修复 topArea 样式 ([e85b542](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e85b542)) +* 修复删除时,当前组件信息丢失问题 ([3bd1248](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3bd1248)) +* 修复判断动态 setter 的逻辑 ([d195d7f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d195d7f)) +* 修复取不到值的情况 ([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)) +* 修复无法拖动的问题 ([2b2de74](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2b2de74)) +* 修复组件面板详情加载不了的 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)) +* 修改dataSource items -> list ([46eadd1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/46eadd1)) +* 修改js面板的保存schema问题 ([0ee8892](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0ee8892)) +* 修改插件面板配置 ([f9ceda5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f9ceda5)) +* 修改移动端设备宽度 ([cd7b1e6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cd7b1e6)) +* 兼容 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)) +* 兼容modal模式 ([1092ee9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1092ee9)) +* 兼容vision体系代码面板中引用计数功能 ([8ade6d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8ade6d8)) +* 兼容事件绑定 ([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)) +* 兼容小程序面板的特殊情况 ([3c686ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3c686ab)) +* 初始就create 所有documentInstance, 否则路由跳转有问题 ([fdd6978](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fdd6978)) +* 动作面板名字 ([f734a61](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f734a61)) +* 区块模板切换之后数据不显示 ([292c1c3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/292c1c3)) +* 区块模板根节点支持 Div ([c3b796e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c3b796e)) +* 区块组件无法删除 ([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)) +* 右侧配置面板样式修复 ([05f62da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05f62da)) +* 右侧配置面板面包屑点击无效 ([353fb10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/353fb10)) +* 合并后bugfix ([c3e6b4b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c3e6b4b)) +* 在 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)) +* 增加try catch ([6f5d11c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6f5d11c)) +* 增加兼容 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)) +* 多选时设置项异常 ([8cc9d73](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8cc9d73)) +* 大纲树节点显示隐藏埋点 ([e91ab1f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e91ab1f)) +* 实现 removeDocument ([c07b447](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c07b447)) +* 导入的组件默认怎么变量绑定 ([fc398c2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fc398c2)) +* 导入的组件默认怎么变量绑定 ([194d8d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/194d8d8)) +* 快捷键增加判断 ([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)) +* 支持 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)) +* 支持低代码组件样式 ([6e64be1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e64be1)) +* 支持自定义 Block 容器 ([1c0b508](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1c0b508)) +* 支持页面回滚 ([5d7dc2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5d7dc2f)) +* 新增自定义模式 demo & 导出自定义需要的信息 ([07e2759](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/07e2759)) +* 暂时使用 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)) +* 框架样式调整 ([58790c5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/58790c5)) +* 没有 modal node 时不显示模态视图 ([555824c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/555824c)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) +* 灵犀vc组件中调用config, 补充进去 ([7171aa2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7171aa2)) +* 用户在动态修改 prototype 时也需要重新计算 meta ([66c21c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66c21c0)) +* 画布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)) +* 移除 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)) +* 组件缺失占位 ([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)) +* 补全 packageName, 否则在组件面板会被隐藏 ([88e5008](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/88e5008)) +* 解决 set('schema') 后 componentsTree 越来越多的 bug ([a171d3e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a171d3e)) +* **settings-pane:** overflow problem ([d2d8556](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d2d8556)) +* 解决点击数据源,自动隐藏的问题 ([7dcd61c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7dcd61c)) +* 调整 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)) +* 调整保存成功弹出框位置 ([5198dae](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5198dae)) +* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f)) +* 部分低代码组件渲染报错 ([093015c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/093015c)) +* **designer:** fix insertion style ([82c10d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/82c10d2)) +* **designer/node.ts:** fix hasLoop logic ([99a7288](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99a7288)) +* 钉住行为调整 ([91a390e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/91a390e)) +* 页面加载之后就被标记位 isModified ([2840d27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2840d27)) +* **editor-skeleton:** add canSetFixed prop to panel config ([1b57d5c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1b57d5c)) +* **rax-render:** hidden无效 ([08a3e36](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/08a3e36)) +* **react-renderer:** fix hasLoop logic ([577e0eb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/577e0eb)) + + +### Code Refactoring + +* 💡 refactor with react-docgen ([64c9daa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/64c9daa)) + + +### Features + +* 🎸 add component descriptions in assets ([ceb15f9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ceb15f9)) +* 🎸 add demo-server ([df35c6a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/df35c6a)) +* 🎸 add missing dependencies to editor ([54477aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/54477aa)) +* 🎸 add node type mapping config for jsx plugin ([19a51b8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/19a51b8)) +* 🎸 Box config edit ([49b49ee](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/49b49ee)) +* 🎸 Button update ([7969273](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7969273)) +* 🎸 code generator fix slot support ([e51b9cb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e51b9cb)) +* 🎸 Collapse component update ([c682cc5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c682cc5)) +* 🎸 Collapse.panel has drop in Collapse ([d4d41e4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d4d41e4)) +* 🎸 modify repo config & template config ([049e6cb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/049e6cb)) +* 🎸 pagination update ([f13b3ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f13b3ab)) +* 🎸 ployfill for vision ([41a0647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/41a0647)) +* 🎸 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)) +* 🎸 polyfill style ([c48846d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c48846d)) +* 🎸 polyfill style ([a6381d7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a6381d7)) +* 🎸 prototype getTitle 支持 i18n ([18807ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18807ab)) +* 🎸 saveload btn for demo ([f91da66](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f91da66)) +* 🎸 support parsing fusion source code ([5895cf1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5895cf1)) +* 🎸 support parsing sub components ([70f3e32](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/70f3e32)) +* 🎸 update sh ([3e23362](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3e23362)) +* 🎸 update upload component ([10abef5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/10abef5)) +* 🎸 upload update ([f81932b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f81932b)) +* 🎸 为了能更好地在设计态模拟, 将 device 透传到组件树根组件上 ([2a253fb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2a253fb)) +* 🎸 为了能更好地在设计态模拟, 将 device 透传到组件树根组件上 ([7ab7def](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7ab7def)) +* 🎸 为容器的占位元素增加一个特定的 class 方便在设计器里定制样式 ([5077141](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5077141)) +* 🎸 增加icon相关的判断函数 ([89064f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/89064f5)) +* 🎸 增加icon获取api ([f1a0823](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f1a0823)) +* 🎸 增加一个hover事件效果 ([da5dd1a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/da5dd1a)) +* 🎸 增加节点选择组件调用入口 ([e945d79](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e945d79)) +* 🎸 容器占位原生的样式从内联改成写在 CSS 文件里,方便被覆盖样式 ([a616e18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a616e18)) +* 🎸 支持设置模拟器的 viewport 的宽高和缩放级别 ([3a54241](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3a54241)) +* 🎸 旧的组件无法继续沿用,增加了一个节点选择组件 ([f042041](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f042041)) +* $ method ([cf50292](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cf50292)) +* 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 color-setter ([a149921](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a149921)) +* add eslint ignore ([28ad3e9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/28ad3e9)) +* add expression-setter AutoComplete tips ([9c62a49](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9c62a49)) +* add favicon for preview ([9c1b2d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9c1b2d6)) +* add filter reducer ([17c6ed3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/17c6ed3)) +* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0)) +* add init and ready lifecycles ([fd100c9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fd100c9)) +* add label for i18n setter in slots ([b298c18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b298c18)) +* add Monitor ([f915d19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f915d19)) +* add prettier post processor ([49ac9a3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/49ac9a3)) +* add recore project template ([267953b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/267953b)) +* add resize box ([14a55ae](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/14a55ae)) +* add root field to material parser options ([c6724e9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c6724e9)) +* add setters ([15317b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/15317b0)) +* add setters ([af62a3e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/af62a3e)) +* add style setter ([efb3e5e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/efb3e5e)) +* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84)) +* add template create tool ([e906683](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e906683)) +* add URL link for setter titles ([4678408](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4678408)) +* add xima ([ff1e17a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ff1e17a)) +* add zip publisher ([31156ed](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/31156ed)) +* 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)) +* cache lazyElement ([8f3b4e6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8f3b4e6)) +* change reducer stage ([c2e83c7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c2e83c7)) +* code generator main process ([021d6e0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/021d6e0)) +* complet dynamically render ([edf14c1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/edf14c1)) +* complet preview ([56c16ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56c16ff)) +* complet Trunk ([fcd0af8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fcd0af8)) +* complete component protocol json schema & validate method ([3df360d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3df360d)) +* complete live-editing expr & i18n ([3ac08ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3ac08ba)) +* current DocuemntInstance add refresh method ([b18a0d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b18a0d2)) +* demo schema & complex children type ([a5ee6bd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5ee6bd)) +* demo 构造 componentsMap ([f445ffe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f445ffe)) +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7)) +* 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)) +* export Monitor ([51025f0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/51025f0)) +* export navigator ([ef99ec2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ef99ec2)) +* export publisher ([4a53faa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4a53faa)) +* extend deviceClassName ([0e96074](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e96074)) +* fix gaps ([32af3d3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/32af3d3)) +* for box resizing ([77e325f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/77e325f)) +* for box resizing ([cb2854d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cb2854d)) +* get layout config from legao-design ([b9103a2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b9103a2)) +* 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)) +* immigrate aimake materialin ([44ac85f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/44ac85f)) +* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e66168)) +* init ([b0de4f3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b0de4f3)) +* init rax-render ([7167767](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7167767)) +* 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)) +* JSexpression props ([26f4fb1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26f4fb1)) +* left pane style ([c149f64](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c149f64)) +* left pane title style; setting pane style ([66e8c25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66e8c25)) +* lint command ([fae976c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fae976c)) +* 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)) +* load assets for preview ([5376469](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5376469)) +* merge live mode ([92c3039](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/92c3039)) +* mixin-setter get all setter ([a5eb62d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5eb62d)) +* mixin-setter get all setter ([eaa84d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eaa84d2)) +* panel增加自动埋点 ([afc7758](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/afc7758)) +* plugin preview ([18f149e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18f149e)) +* prepare publish for code-generator ([93ff5c2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/93ff5c2)) +* preview ([abeb2ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/abeb2ba)) +* project builder fix & publish demo to disk ([26983b3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26983b3)) +* rax render ([6ce0093](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6ce0093)) +* rax render ([95bf331](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/95bf331)) +* rax render ([038d74e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/038d74e)) +* rax-render 兼容 ([877d3fc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/877d3fc)) +* rax-render 拦截逻辑 & request 调用 webtable(mock) ([42108f6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/42108f6)) +* ReactProvider ([0e50a20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e50a20)) +* recore solution ([3bfe758](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3bfe758)) +* register-defaults 改为可选项 ([2195797](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2195797)) +* remove -p tslint.json for test ([6d013e1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d013e1)) +* remove useless codes & modify generator ([dcd1b33](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dcd1b33)) +* rewrite demo & export plugins and utils ([6cf7c3d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6cf7c3d)) +* run vision polyfill ([33750b7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/33750b7)) +* save display JSON result ([5afd388](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5afd388)) +* select add setters ([c84e3a7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c84e3a7)) +* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807)) +* show value state ([bd49e50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd49e50)) +* style setter 国际化 ([4619ee3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4619ee3)) +* support components ([d72c0d1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d72c0d1)) +* support float pane fixed ([40d8260](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/40d8260)) +* support global inline editing ([4f7179b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4f7179b)) +* support localizing ([e1faa84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e1faa84)) +* support multiple exported components ([db1b6de](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/db1b6de)) +* 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)) +* support typescript & dynamic parsing in material parser ([6168ef5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6168ef5)) +* tree 组件修改 ([7efa52f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7efa52f)) +* use new ComponentPane ([56ae5ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56ae5ad)) +* ve事件埋点 ([700e5b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/700e5b0)) +* window._table ([e6cce31](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e6cce31)) +* 主设置面板里深层次界面通过 stagebox 进行过渡 ([783e945](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/783e945)) +* 修复状态切换失效 ([2e3f60d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e3f60d)) +* 修改rax-render ([14ad77c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/14ad77c)) +* 在 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)) +* 增加 node replaceWith 方法 ([d44f95b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d44f95b)) +* 增加color-setter,json-setter ([93e76ce](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/93e76ce)) +* 增加loading扩展点 ([3a1e900](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3a1e900)) +* 增加miniapp外壳 ([bccce8c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bccce8c)) +* 增加出码按钮 ([6f7b066](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6f7b066)) +* 大纲树埋点 ([fa24821](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fa24821)) +* 大纲树展开折叠埋点 ([d9828f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d9828f2)) +* 大纲树支持模态视图 ([3785e1c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3785e1c)) +* 容器组件支持传入 placeholder 和对应样式 ([0c4de43](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0c4de43)) +* 导出的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)) +* 抽离AppHelper ([1f6d131](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f6d131)) +* 接入乐高组件面板 ([e40b1f3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e40b1f3)) +* 支持 entry 模式 ([fe1f6f1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fe1f6f1)) +* 支持body和背景样式 ([661d98d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/661d98d)) +* 支持低代码组件设计态实时改变 ([c5a817b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c5a817b)) +* 支持多 pages 的 schema 结构 ([d9b5adb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d9b5adb)) +* 支持编译渲染 ([0a42151](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0a42151)) +* 支持配置layouts属性 ([8464235](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8464235)) +* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6)) +* 新增simulatorurl,可以设置cdn使用simulator ([1f45b05](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f45b05)) +* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7)) +* **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)) +* **rax-provider:** init ([cb0f382](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cb0f382)) +* **vision-polyfill:** add context ([f724487](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f724487)) +* **vision-polyfill:** add context as portal ([bd12730](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd12730)) +* **vision-polyfill:** support polyfill of vision package ([204fdfe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/204fdfe)) +* 新增分隔符物料配置 ([af39c17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/af39c17)) +* 新增用于小程序跳过 variable 检测设置 hotvalue 的方法 ([ef799eb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ef799eb)) +* 编辑器 hooks 能力实现 ([f3ac23b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f3ac23b)) +* 自动埋点 ([fecf34d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fecf34d)) +* 适配 webtable ([91f1702](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/91f1702)) +* 适配TreeNode节点 ([8c36928](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8c36928)) +* 适配乐高 OneApi 数据源,将 options.params 从 Array 改为 Object ([aa135c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aa135c0)) +* 透出loading ([e96934a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e96934a)) +* 透出错误边界捕捉不到的错误 ([f224abf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f224abf)) + + +### Reverts + +* 去掉多余注释 ([2495afa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2495afa)) + + +### BREAKING CHANGES + +* 🧨 use react-docgen to replace parser diff --git a/index.ts b/index.ts new file mode 100644 index 000000000..362b5d8b6 --- /dev/null +++ b/index.ts @@ -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; diff --git a/lerna.json b/lerna.json index a74ab4dc0..a07f53c35 100644 --- a/lerna.json +++ b/lerna.json @@ -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, diff --git a/package.json b/package.json index 3ca7e714d..6524eea59 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/designer/.eslintrc.js b/packages/designer/.eslintrc.js index c6bbe2868..cf6223647 100644 --- a/packages/designer/.eslintrc.js +++ b/packages/designer/.eslintrc.js @@ -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 } } \ No newline at end of file diff --git a/packages/designer/CHANGELOG.md b/packages/designer/CHANGELOG.md index ca0da0407..d1e4ae771 100644 --- a/packages/designer/CHANGELOG.md +++ b/packages/designer/CHANGELOG.md @@ -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)) + + + + + +## [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)) + + + + + +## [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)) + + + + + +## [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)) + + + + + +## [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)) + + + + + +## [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)) diff --git a/packages/designer/build.json b/packages/designer/build.json index 49a393b6b..bd5cf18dd 100644 --- a/packages/designer/build.json +++ b/packages/designer/build.json @@ -1,7 +1,5 @@ { "plugins": [ - [ - "build-plugin-component" - ] + "build-plugin-component" ] } diff --git a/packages/designer/build.test.json b/packages/designer/build.test.json new file mode 100644 index 000000000..228c07e7f --- /dev/null +++ b/packages/designer/build.test.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "build-plugin-component", + "@ali/lowcode-test-mate/plugin/index.ts" + ] +} diff --git a/packages/designer/jest.config.js b/packages/designer/jest.config.js new file mode 100644 index 000000000..d58fe368f --- /dev/null +++ b/packages/designer/jest.config.js @@ -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/**', + ], +}; diff --git a/packages/designer/package.json b/packages/designer/package.json index 73eca18d3..b7658ad3b 100644 --- a/packages/designer/package.json +++ b/packages/designer/package.json @@ -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" diff --git a/packages/designer/src/builtin-simulator/bem-tools/border-detecting.tsx b/packages/designer/src/builtin-simulator/bem-tools/border-detecting.tsx index d757e7eb8..1f1735801 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/border-detecting.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/border-detecting.tsx @@ -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; } diff --git a/packages/designer/src/builtin-simulator/bem-tools/border-resizing.tsx b/packages/designer/src/builtin-simulator/bem-tools/border-resizing.tsx index c24934f42..7b47b709b 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/border-resizing.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/border-resizing.tsx @@ -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); } diff --git a/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx b/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx index ae82e67b1..919bcf876 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx @@ -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; diff --git a/packages/designer/src/builtin-simulator/bem-tools/drag-resize-engine.ts b/packages/designer/src/builtin-simulator/bem-tools/drag-resize-engine.ts index 7586bd17f..c2b1c0fb0 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/drag-resize-engine.ts +++ b/packages/designer/src/builtin-simulator/bem-tools/drag-resize-engine.ts @@ -38,6 +38,8 @@ export default class DragResizeEngine { private dragResizing = false; + private designer: Designer; + constructor(designer: Designer) { this.designer = designer; this.emitter = new EventEmitter(); diff --git a/packages/designer/src/builtin-simulator/bem-tools/index.tsx b/packages/designer/src/builtin-simulator/bem-tools/index.tsx index c8c3ff909..d7bfdf720 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/index.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/index.tsx @@ -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 (
diff --git a/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx b/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx index 6ed6ef93d..482ff81bc 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx @@ -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
; } diff --git a/packages/designer/src/builtin-simulator/host-view.tsx b/packages/designer/src/builtin-simulator/host-view.tsx index 4c3dda73e..2f32bf54e 100644 --- a/packages/designer/src/builtin-simulator/host-view.tsx +++ b/packages/designer/src/builtin-simulator/host-view.tsx @@ -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 { 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); } diff --git a/packages/designer/src/builtin-simulator/host.less b/packages/designer/src/builtin-simulator/host.less index c51cd5d67..f763c732f 100644 --- a/packages/designer/src/builtin-simulator/host.less +++ b/packages/designer/src/builtin-simulator/host.less @@ -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; diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index b6da3333d..906eaabb6 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -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 { 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 void; firstRun: boolean }) => void) { + return autorun(fn as any, true); + } + purge(): void { // todo } @@ -313,9 +335,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost { // 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 { 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 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 { - 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 @@ -525,15 +566,22 @@ export class BuiltinSimulatorHost implements ISimulatorHost(); - - setInstance(id: string, instances: ComponentInstance[] | null) { + @obx private instancesMap: { + [docId: string]: Map; + } = {}; + 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 | undefined; @@ -1104,11 +1161,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost(); if (isDragNodeObject(dragObject)) { const { nodes } = dragObject; @@ -1128,63 +1179,70 @@ export class BuiltinSimulatorHost implements ISimulatorHost, 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, selector: string): Element } return firstQueried; } - -interface DropContainer { - container: ParentalNode; - instance: ComponentInstance; -} diff --git a/packages/designer/src/builtin-simulator/node-selector/index.less b/packages/designer/src/builtin-simulator/node-selector/index.less index c630e0914..8239b41ca 100644 --- a/packages/designer/src/builtin-simulator/node-selector/index.less +++ b/packages/designer/src/builtin-simulator/node-selector/index.less @@ -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; + } } } diff --git a/packages/designer/src/builtin-simulator/renderer.ts b/packages/designer/src/builtin-simulator/renderer.ts index cf814ba48..f3b3de79b 100644 --- a/packages/designer/src/builtin-simulator/renderer.ts +++ b/packages/designer/src/builtin-simulator/renderer.ts @@ -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 | null; findDOMNodes(instance: ComponentInstance): Array | null; getClientRects(element: Element | Text): DOMRect[]; diff --git a/packages/designer/src/builtin-simulator/utils/parse-metadata.ts b/packages/designer/src/builtin-simulator/utils/parse-metadata.ts index 3bcf9d6d4..96c92943a 100644 --- a/packages/designer/src/builtin-simulator/utils/parse-metadata.ts +++ b/packages/designer/src/builtin-simulator/utils/parse-metadata.ts @@ -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) { diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index c14133262..b0b02f772 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -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; diff --git a/packages/designer/src/designer/builtin-hotkey.ts b/packages/designer/src/designer/builtin-hotkey.ts index bc8a698df..07fa4ad56 100644 --- a/packages/designer/src/designer/builtin-hotkey.ts +++ b/packages/designer/src/designer/builtin-hotkey.ts @@ -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) { diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index 157a1ee5e..197bcfe67 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -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; } } diff --git a/packages/designer/src/designer/detecting.ts b/packages/designer/src/designer/detecting.ts index 273bad503..889d7e817 100644 --- a/packages/designer/src/designer/detecting.ts +++ b/packages/designer/src/designer/detecting.ts @@ -33,7 +33,7 @@ export class Detecting { } } - leave(document: DocumentModel) { + leave(document: DocumentModel | undefined) { if (this.current && this.current.document === document) { this._current = null; } diff --git a/packages/designer/src/designer/drag-ghost/index.tsx b/packages/designer/src/designer/drag-ghost/index.tsx index d54dd9bb0..021dee612 100644 --- a/packages/designer/src/designer/drag-ghost/index.tsx +++ b/packages/designer/src/designer/drag-ghost/index.tsx @@ -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 (
{ - // 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 diff --git a/packages/designer/src/designer/setting/setting-field.ts b/packages/designer/src/designer/setting/setting-field.ts index 9ed826c06..4bb8ea1ee 100644 --- a/packages/designer/src/designer/setting/setting-field.ts +++ b/packages/designer/src/designer/setting/setting-field.ts @@ -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; diff --git a/packages/designer/src/designer/setting/setting-prop-entry.ts b/packages/designer/src/designer/setting/setting-prop-entry.ts index c219a3091..b849ca908 100644 --- a/packages/designer/src/designer/setting/setting-prop-entry.ts +++ b/packages/designer/src/designer/setting/setting-prop-entry.ts @@ -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; } diff --git a/packages/designer/src/designer/setting/setting-top-entry.ts b/packages/designer/src/designer/setting/setting-top-entry.ts index 507fc053c..405c76faa 100644 --- a/packages/designer/src/designer/setting/setting-top-entry.ts +++ b/packages/designer/src/designer/setting/setting-top-entry.ts @@ -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); diff --git a/packages/designer/src/designer/setting/utils.ts b/packages/designer/src/designer/setting/utils.ts index ec2c71bf8..4bbe8e299 100644 --- a/packages/designer/src/designer/setting/utils.ts +++ b/packages/designer/src/designer/setting/utils.ts @@ -31,6 +31,10 @@ function combineTransducer(transducer, arr, context) { } export class Transducer { + setterTransducer: any; + + context: any; + constructor(context, config) { let { setter } = config; diff --git a/packages/designer/src/document/document-model.ts b/packages/designer/src/document/document-model.ts index b6fc52719..438626849 100644 --- a/packages/designer/src/document/document-model.ts +++ b/packages/designer/src/document/document-model.ts @@ -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 extends undefined ? NodeType extends { @@ -56,6 +56,10 @@ export class DocumentModel { private _nodesMap = new Map(); + readonly project: Project; + + readonly designer: Designer; + @obx.val private nodes = new Set(); 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 { @@ -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 { diff --git a/packages/designer/src/document/history.ts b/packages/designer/src/document/history.ts index 972fced01..e4aa09e1a 100644 --- a/packages/designer/src/document/history.ts +++ b/packages/designer/src/document/history.ts @@ -9,7 +9,7 @@ export interface Serialization { unserialize(data: T): NodeSchema; } -let currentSerializion: Serialization = { +let currentSerialization: Serialization = { serialize(data: NodeSchema): string { return JSON.stringify(data); }, @@ -18,8 +18,8 @@ let currentSerializion: Serialization = { }, }; -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'); } } diff --git a/packages/designer/src/document/index.ts b/packages/designer/src/document/index.ts index 66c7dc65f..88601de6a 100644 --- a/packages/designer/src/document/index.ts +++ b/packages/designer/src/document/index.ts @@ -1,4 +1,3 @@ -export * from './document-view'; export * from './document-model'; export * from './node'; export * from './selection'; diff --git a/packages/designer/src/document/node/exclusive-group.ts b/packages/designer/src/document/node/exclusive-group.ts index 2f5a98cb2..87d2bc506 100644 --- a/packages/designer/src/document/node/exclusive-group.ts +++ b/packages/designer/src/document/node/exclusive-group.ts @@ -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); diff --git a/packages/designer/src/document/node/modal-nodes-manager.ts b/packages/designer/src/document/node/modal-nodes-manager.ts index b5cb96963..e2c459521 100644 --- a/packages/designer/src/document/node/modal-nodes-manager.ts +++ b/packages/designer/src/document/node/modal-nodes-manager.ts @@ -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); diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index b4ff17316..b9cea8eab 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -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'; diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 82b471d9d..98db49a05 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -158,24 +158,25 @@ export class Node { 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 { } } + 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 { 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 { 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 { 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; diff --git a/packages/designer/src/document/node/props/prop.ts b/packages/designer/src/document/node/props/prop.ts index 18a90353b..1eb65c1f6 100644 --- a/packages/designer/src/document/node/props/prop.ts +++ b/packages/designer/src/document/node/props/prop.ts @@ -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; } diff --git a/packages/designer/src/document/node/props/props.ts b/packages/designer/src/document/node/props/props.ts index fa73e0190..6a2ee2b22 100644 --- a/packages/designer/src/document/node/props/props.ts +++ b/packages/designer/src/document/node/props/props.ts @@ -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; } diff --git a/packages/designer/src/project/project-view.tsx b/packages/designer/src/project/project-view.tsx index 649358b62..5f2c05ddb 100644 --- a/packages/designer/src/project/project-view.tsx +++ b/packages/designer/src/project/project-view.tsx @@ -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 (
- {opens.length > 0 ? ( - opens.map((doc) => ) - ) : ( -
{intl('No opened document')}
- )} +
+ +
); } diff --git a/packages/designer/src/project/project.less b/packages/designer/src/project/project.less index fcfba65c8..a83e983e2 100644 --- a/packages/designer/src/project/project.less +++ b/packages/designer/src/project/project.less @@ -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%; - } } } diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index 619854399..a85cfa7cb 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -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(); + + 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); }; } - // 通知标记删除,需要告知服务端 - // 项目角度编辑不是全量打开所有文档,是按需加载,哪个更新就通知更新谁, - // 哪个删除就 } diff --git a/packages/designer/src/simulator.ts b/packages/designer/src/simulator.ts index 665f64a50..de95ae382 100644 --- a/packages/designer/src/simulator.ts +++ b/packages/designer/src/simulator.ts @@ -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

extends ISensor { findDOMNodes(instance: ComponentInstance, selector?: string): Array | null; + getDropContainer(e: LocateEvent): DropContainer | null; + /** * 销毁 */ @@ -154,6 +161,7 @@ export function isSimulatorHost(obj: any): obj is ISimulatorHost { } export interface NodeInstance { + docId: string; nodeId: string; instance: T; node?: Node | null; diff --git a/packages/designer/tests/__mocks__/document-model.ts b/packages/designer/tests/__mocks__/document-model.ts new file mode 100644 index 000000000..982bb4c1b --- /dev/null +++ b/packages/designer/tests/__mocks__/document-model.ts @@ -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; + } +} \ No newline at end of file diff --git a/packages/designer/tests/__mocks__/node.ts b/packages/designer/tests/__mocks__/node.ts new file mode 100644 index 000000000..4f225766b --- /dev/null +++ b/packages/designer/tests/__mocks__/node.ts @@ -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; + } +} \ No newline at end of file diff --git a/packages/designer/tests/bugs/misc.ts b/packages/designer/tests/bugs/misc.ts new file mode 100644 index 000000000..e101d868f --- /dev/null +++ b/packages/designer/tests/bugs/misc.ts @@ -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); +}); \ No newline at end of file diff --git a/packages/designer/tests/builtin-simulator/host-view.test.tsx b/packages/designer/tests/builtin-simulator/host-view.test.tsx new file mode 100644 index 000000000..53d2225e0 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/host-view.test.tsx @@ -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(); + }) +}); diff --git a/packages/designer/tests/builtin-simulator/host.test.tsx b/packages/designer/tests/builtin-simulator/host.test.tsx new file mode 100644 index 000000000..fdeb0943a --- /dev/null +++ b/packages/designer/tests/builtin-simulator/host.test.tsx @@ -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, + ); + }) +}); diff --git a/packages/designer/tests/builtin-simulator/parse-metadata.test.ts b/packages/designer/tests/builtin-simulator/parse-metadata.test.ts new file mode 100644 index 000000000..50a1ba005 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/parse-metadata.test.ts @@ -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' }); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/builtin-simulator/path.test.ts b/packages/designer/tests/builtin-simulator/path.test.ts new file mode 100644 index 000000000..6a15edbcf --- /dev/null +++ b/packages/designer/tests/builtin-simulator/path.test.ts @@ -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'); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/builtin-simulator/renderer.test.tsx b/packages/designer/tests/builtin-simulator/renderer.test.tsx new file mode 100644 index 000000000..6cead4122 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/renderer.test.tsx @@ -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; + }) +}); diff --git a/packages/designer/tests/builtin-simulator/throttle.test.ts b/packages/designer/tests/builtin-simulator/throttle.test.ts new file mode 100644 index 000000000..fd2d7ce33 --- /dev/null +++ b/packages/designer/tests/builtin-simulator/throttle.test.ts @@ -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); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/designer/builtin-hotkey.test.ts b/packages/designer/tests/designer/builtin-hotkey.test.ts new file mode 100644 index 000000000..cc010f85c --- /dev/null +++ b/packages/designer/tests/designer/builtin-hotkey.test.ts @@ -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; + }); +}); diff --git a/packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts b/packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts new file mode 100644 index 000000000..8ddf63f0b --- /dev/null +++ b/packages/designer/tests/designer/setting-entry/setting-prop-entry.test.ts @@ -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', + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts b/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts new file mode 100644 index 000000000..e7e5dab24 --- /dev/null +++ b/packages/designer/tests/designer/setting-entry/setting-top-entry.test.ts @@ -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 强行调用,是否需要做健壮性保护? + }); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/document/document-model/document-model.test.ts b/packages/designer/tests/document/document-model/document-model.test.ts new file mode 100644 index 000000000..94eda1871 --- /dev/null +++ b/packages/designer/tests/document/document-model/document-model.test.ts @@ -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); + }); +}); diff --git a/packages/designer/tests/document/document-model/node.test.ts b/packages/designer/tests/document/document-model/node.test.ts new file mode 100644 index 000000000..29ea34123 --- /dev/null +++ b/packages/designer/tests/document/document-model/node.test.ts @@ -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); + }); +}); diff --git a/packages/designer/tests/document/selection.test.ts b/packages/designer/tests/document/selection.test.ts new file mode 100644 index 000000000..4d81e935e --- /dev/null +++ b/packages/designer/tests/document/selection.test.ts @@ -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(); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/fixtures/component-metadata/div.ts b/packages/designer/tests/fixtures/component-metadata/div.ts new file mode 100644 index 000000000..395cb58bb --- /dev/null +++ b/packages/designer/tests/fixtures/component-metadata/div.ts @@ -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: [], + }, +}; diff --git a/packages/designer/tests/fixtures/disable-raf.ts b/packages/designer/tests/fixtures/disable-raf.ts new file mode 100644 index 000000000..14b710125 --- /dev/null +++ b/packages/designer/tests/fixtures/disable-raf.ts @@ -0,0 +1,3 @@ +Object.defineProperty(window, 'requestAnimationFrame', { + value: null, +}) \ No newline at end of file diff --git a/packages/designer/tests/fixtures/prototype/div-meta.ts b/packages/designer/tests/fixtures/prototype/div-meta.ts new file mode 100644 index 000000000..a2b410494 --- /dev/null +++ b/packages/designer/tests/fixtures/prototype/div-meta.ts @@ -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: [], + }, +}; diff --git a/packages/designer/tests/fixtures/schema/form.ts b/packages/designer/tests/fixtures/schema/form.ts new file mode 100644 index 000000000..903b21756 --- /dev/null +++ b/packages/designer/tests/fixtures/schema/form.ts @@ -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, + }, + ], +}; diff --git a/packages/designer/tests/fixtures/schema/setting.ts b/packages/designer/tests/fixtures/schema/setting.ts new file mode 100644 index 000000000..a0d45d0d9 --- /dev/null +++ b/packages/designer/tests/fixtures/schema/setting.ts @@ -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', + } + ], +}; diff --git a/packages/designer/tests/fixtures/unhandled-rejection.ts b/packages/designer/tests/fixtures/unhandled-rejection.ts new file mode 100644 index 000000000..d69ffa08b --- /dev/null +++ b/packages/designer/tests/fixtures/unhandled-rejection.ts @@ -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; +} \ No newline at end of file diff --git a/packages/designer/tests/fixtures/window.ts b/packages/designer/tests/fixtures/window.ts new file mode 100644 index 000000000..d772d1ef4 --- /dev/null +++ b/packages/designer/tests/fixtures/window.ts @@ -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: {}, +}); \ No newline at end of file diff --git a/packages/designer/tests/meta/component-meta.test.ts b/packages/designer/tests/meta/component-meta.test.ts new file mode 100644 index 000000000..f3cdfd2a6 --- /dev/null +++ b/packages/designer/tests/meta/component-meta.test.ts @@ -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'); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/node/node.add.test.ts b/packages/designer/tests/node/node.add.test.ts new file mode 100644 index 000000000..9da7a3473 --- /dev/null +++ b/packages/designer/tests/node/node.add.test.ts @@ -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); + }); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/node/node.dragdrop.test.ts b/packages/designer/tests/node/node.dragdrop.test.ts new file mode 100644 index 000000000..be15bdfa7 --- /dev/null +++ b/packages/designer/tests/node/node.dragdrop.test.ts @@ -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); + }); + + + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/node/node.modify.test.ts b/packages/designer/tests/node/node.modify.test.ts new file mode 100644 index 000000000..e064d273f --- /dev/null +++ b/packages/designer/tests/node/node.modify.test.ts @@ -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); + }); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/node/node.remove.test.ts b/packages/designer/tests/node/node.remove.test.ts new file mode 100644 index 000000000..f68d57ca1 --- /dev/null +++ b/packages/designer/tests/node/node.remove.test.ts @@ -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); + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/project/project.test.ts b/packages/designer/tests/project/project.test.ts new file mode 100644 index 000000000..31ece4526 --- /dev/null +++ b/packages/designer/tests/project/project.test.ts @@ -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 测试', () => { + + }); +}); \ No newline at end of file diff --git a/packages/designer/tests/utils/bom.ts b/packages/designer/tests/utils/bom.ts new file mode 100644 index 000000000..fca5f73fc --- /dev/null +++ b/packages/designer/tests/utils/bom.ts @@ -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> = new Map>(); +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(); +} \ No newline at end of file diff --git a/packages/designer/tests/utils/event.ts b/packages/designer/tests/utils/event.ts new file mode 100644 index 000000000..59f7b016e --- /dev/null +++ b/packages/designer/tests/utils/event.ts @@ -0,0 +1,8 @@ +export function getMockEvent(target, options) { + return { + target, + preventDefault() {}, + stopPropagation() {}, + ...options, + }; +} \ No newline at end of file diff --git a/packages/designer/tests/utils/index.ts b/packages/designer/tests/utils/index.ts new file mode 100644 index 000000000..74eb13265 --- /dev/null +++ b/packages/designer/tests/utils/index.ts @@ -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'; \ No newline at end of file diff --git a/packages/designer/tests/utils/renderer.ts b/packages/designer/tests/utils/renderer.ts new file mode 100644 index 000000000..256a8c651 --- /dev/null +++ b/packages/designer/tests/utils/renderer.ts @@ -0,0 +1,8 @@ +export function getMockRenderer() { + return { + isSimulatorRenderer: true, + run() { + console.log('renderer run'); + } + } +} \ No newline at end of file diff --git a/packages/designer/tsconfig.json b/packages/designer/tsconfig.json deleted file mode 100644 index 4a965ec62..000000000 --- a/packages/designer/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "lib" - }, - "include": [ - "./src/" - ] -} - diff --git a/packages/editor-core/.eslintrc.js b/packages/editor-core/.eslintrc.js index c4749b311..f8bcac9a2 100644 --- a/packages/editor-core/.eslintrc.js +++ b/packages/editor-core/.eslintrc.js @@ -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, diff --git a/packages/editor-core/src/utils/monitor.ts b/packages/editor-core/src/utils/monitor.ts index 66ec0f39b..80313a55b 100644 --- a/packages/editor-core/src/utils/monitor.ts +++ b/packages/editor-core/src/utils/monitor.ts @@ -1,4 +1,4 @@ -class Monitor { +export class Monitor { fn = (params: any) => { const { AES } = window as any; if (typeof AES.log === 'function') { diff --git a/packages/editor-preset-vision/.eslintrc.js b/packages/editor-preset-vision/.eslintrc.js index f3461d8f8..4c845e2de 100644 --- a/packages/editor-preset-vision/.eslintrc.js +++ b/packages/editor-preset-vision/.eslintrc.js @@ -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, } } \ No newline at end of file diff --git a/packages/editor-preset-vision/CHANGELOG.md b/packages/editor-preset-vision/CHANGELOG.md index 44be5911c..9d26c2744 100644 --- a/packages/editor-preset-vision/CHANGELOG.md +++ b/packages/editor-preset-vision/CHANGELOG.md @@ -83,14 +83,27 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @ali/lowcode-editor-preset-vision - -## [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) + +## [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)) + + + + + +## [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 - -## [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) + +## [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 + +## 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)) + + + + ## [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) diff --git a/packages/editor-preset-vision/build.json b/packages/editor-preset-vision/build.json index ce6117bb0..ffd529288 100644 --- a/packages/editor-preset-vision/build.json +++ b/packages/editor-preset-vision/build.json @@ -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" } } ], diff --git a/packages/editor-preset-vision/build.test.json b/packages/editor-preset-vision/build.test.json new file mode 100644 index 000000000..4bde6acd1 --- /dev/null +++ b/packages/editor-preset-vision/build.test.json @@ -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" + ] +} diff --git a/packages/editor-preset-vision/jest.config.js b/packages/editor-preset-vision/jest.config.js new file mode 100644 index 000000000..31a6eab6e --- /dev/null +++ b/packages/editor-preset-vision/jest.config.js @@ -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/**', + ], +}; diff --git a/packages/editor-preset-vision/package.json b/packages/editor-preset-vision/package.json index 732f96c9d..618cdf232 100644 --- a/packages/editor-preset-vision/package.json +++ b/packages/editor-preset-vision/package.json @@ -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": { diff --git a/packages/editor-preset-vision/src/bundle/prototype.ts b/packages/editor-preset-vision/src/bundle/prototype.ts index b91554240..eb93cb184 100644 --- a/packages/editor-preset-vision/src/bundle/prototype.ts +++ b/packages/editor-preset-vision/src/bundle/prototype.ts @@ -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; diff --git a/packages/editor-preset-vision/src/bundle/trunk.ts b/packages/editor-preset-vision/src/bundle/trunk.ts index 5144275ad..c84475659 100644 --- a/packages/editor-preset-vision/src/bundle/trunk.ts +++ b/packages/editor-preset-vision/src/bundle/trunk.ts @@ -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); } diff --git a/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts b/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts index b0931d6ca..084037dfc 100644 --- a/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts +++ b/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts @@ -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 diff --git a/packages/editor-preset-vision/src/bus.ts b/packages/editor-preset-vision/src/bus.ts index dd6b8386a..1a13ef263 100644 --- a/packages/editor-preset-vision/src/bus.ts +++ b/packages/editor-preset-vision/src/bus.ts @@ -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; diff --git a/packages/editor-preset-vision/src/deep-value-parser.ts b/packages/editor-preset-vision/src/deep-value-parser.ts index e5270264a..5d15d9c0a 100644 --- a/packages/editor-preset-vision/src/deep-value-parser.ts +++ b/packages/editor-preset-vision/src/deep-value-parser.ts @@ -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) { diff --git a/packages/editor-preset-vision/src/editor.ts b/packages/editor-preset-vision/src/editor.ts index 96ecf7229..9f17555c2 100644 --- a/packages/editor-preset-vision/src/editor.ts +++ b/packages/editor-preset-vision/src/editor.ts @@ -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(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', diff --git a/packages/editor-preset-vision/src/env.ts b/packages/editor-preset-vision/src/env.ts index 4e267ee24..786c1f74a 100644 --- a/packages/editor-preset-vision/src/env.ts +++ b/packages/editor-preset-vision/src/env.ts @@ -80,6 +80,11 @@ export class Env { }; } + clear() { + this.envs = {}; + this.featureMap = {}; + } + getAliSchemaVersion() { return ALI_SCHEMA_VERSION; } diff --git a/packages/editor-preset-vision/src/flags.ts b/packages/editor-preset-vision/src/flags.ts index 5c5aefec3..2a68db857 100644 --- a/packages/editor-preset-vision/src/flags.ts +++ b/packages/editor-preset-vision/src/flags.ts @@ -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); diff --git a/packages/editor-preset-vision/src/index.ts b/packages/editor-preset-vision/src/index.ts index 30a3ddef1..f27e95045 100644 --- a/packages/editor-preset-vision/src/index.ts +++ b/packages/editor-preset-vision/src/index.ts @@ -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 ); diff --git a/packages/editor-preset-vision/src/pages.ts b/packages/editor-preset-vision/src/pages.ts index 5617e882c..95dfa0e64 100644 --- a/packages/editor-preset-vision/src/pages.ts +++ b/packages/editor-preset-vision/src/pages.ts @@ -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) => { diff --git a/packages/editor-preset-vision/src/panes.ts b/packages/editor-preset-vision/src/panes.ts index 8c0776cab..840211188 100644 --- a/packages/editor-preset-vision/src/panes.ts +++ b/packages/editor-preset-vision/src/panes.ts @@ -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; } diff --git a/packages/editor-preset-vision/src/project.ts b/packages/editor-preset-vision/src/project.ts index 9239a9497..2d978c748 100644 --- a/packages/editor-preset-vision/src/project.ts +++ b/packages/editor-preset-vision/src/project.ts @@ -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; diff --git a/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts b/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts new file mode 100644 index 000000000..65186ea81 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/downgrade-schema-reducer.ts @@ -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(props).forEach(([key, val]) => { + newProps[key] = compatibleReducer(val); + }); + return newProps; +} diff --git a/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts b/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts new file mode 100644 index 000000000..928e8dd82 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/filter-reducer.ts @@ -0,0 +1,25 @@ +import logger from '@ali/vu-logger'; +import { hasOwnProperty } from '@ali/lowcode-utils'; + +export 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; +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/props-reducers/index.ts b/packages/editor-preset-vision/src/props-reducers/index.ts new file mode 100644 index 000000000..148d115b7 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/index.ts @@ -0,0 +1,8 @@ +export * from './downgrade-schema-reducer'; +export * from './filter-reducer'; +export * from './init-node-reducer'; +export * from './live-lifecycle-reducer'; +export * from './remove-empty-prop-reducer'; +export * from './style-reducer'; +export * from './upgrade-reducer'; +export * from './node-top-fixed-reducer'; diff --git a/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts new file mode 100644 index 000000000..031482f46 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/init-node-reducer.ts @@ -0,0 +1,68 @@ +import { + hasOwnProperty, + isI18NObject, + isUseI18NSetter, + convertToI18NObject, + isString, +} from '@ali/lowcode-utils'; +import { isJSExpression, isJSBlock, isJSSlot } from '@ali/lowcode-types'; +import { isVariable, getCurrentFieldIds } from '../utils'; + +export function initNodeReducer(props, node) { + // run initials + const newProps: any = { + ...props, + }; + if (newProps.fieldId) { + const fieldIds = getCurrentFieldIds(); + + // 全局的关闭 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; + } + // 兼容 props 中的属性为 i18n 类型,但是仅提供了一个字符串值,非变量绑定 + if ( + isUseI18NSetter(node.componentMeta.prototype, item.name) && + !isI18NObject(ov) && + !isJSExpression(ov) && + !isJSBlock(ov) && + !isJSSlot(ov) && + !isVariable(ov) && + (isString(v) || isI18NObject(v)) + ) { + newProps[item.name] = convertToI18NObject(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, false, { skipSetSlot: true }); + } + }); + } + return newProps; +} diff --git a/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts b/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts new file mode 100644 index 000000000..a626c3b30 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/live-lifecycle-reducer.ts @@ -0,0 +1,27 @@ +import { globalContext, Editor } from '@ali/lowcode-editor-core'; +import { Node } from '@ali/lowcode-designer'; + +export function liveLifecycleReducer(props: any, node: Node) { + const editor = globalContext.get(Editor); + // live 模式下解析 lifeCycles + if (node.isRoot() && props && props.lifeCycles) { + if (editor.get('designMode') === 'live') { + const lifeCycleMap = { + didMount: 'componentDidMount', + willUnmount: 'componentWillUnMount', + }; + const lifeCycles = props.lifeCycles; + Object.keys(lifeCycleMap).forEach(key => { + if (lifeCycles[key]) { + lifeCycles[lifeCycleMap[key]] = lifeCycles[key]; + } + }); + return props; + } + return { + ...props, + lifeCycles: {}, + }; + } + return props; +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/props-reducers/node-top-fixed-reducer.ts b/packages/editor-preset-vision/src/props-reducers/node-top-fixed-reducer.ts new file mode 100644 index 000000000..194496a9c --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/node-top-fixed-reducer.ts @@ -0,0 +1,12 @@ +import { Node } from '@ali/lowcode-designer'; + +export function nodeTopFixedReducer(props: any, node: Node) { + if (node.componentMeta.isTopFixed) { + return { + ...props, + // experimental prop value + __isTopFixed__: true, + }; + } + return props; +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts b/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts new file mode 100644 index 000000000..305f0bdb7 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/remove-empty-prop-reducer.ts @@ -0,0 +1,23 @@ +import { + cloneDeep, +} from '@ali/lowcode-utils'; + +// 清除空的 props value +export function removeEmptyPropsReducer(props: any, node: Node) { + if (node.isRoot() && props.dataSource && Array.isArray(props.dataSource.online)) { + const online = cloneDeep(props.dataSource.online); + online.forEach((item: any) => { + const newParam: any = {}; + if (Array.isArray(item?.options?.params)) { + item.options.params.forEach((element: any) => { + if (element.name) { + newParam[element.name] = element.value; + } + }); + item.options.params = newParam; + } + }); + props.dataSource.list = online; + } + return props; +} diff --git a/packages/editor-preset-vision/src/props-reducers/style-reducer.ts b/packages/editor-preset-vision/src/props-reducers/style-reducer.ts new file mode 100644 index 000000000..214d8deb1 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/style-reducer.ts @@ -0,0 +1,50 @@ +import { globalContext, Editor } from '@ali/lowcode-editor-core'; +import { toCss } from '@ali/vu-css-style'; + +export 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 editor = globalContext.get(Editor); + const designer = editor.get('designer'); + 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}`))); + } +} \ No newline at end of file diff --git a/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts b/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts new file mode 100644 index 000000000..abe93a454 --- /dev/null +++ b/packages/editor-preset-vision/src/props-reducers/upgrade-reducer.ts @@ -0,0 +1,53 @@ +import { getConvertedExtraKey } from '@ali/lowcode-designer'; +import { + isPlainObject, +} from '@ali/lowcode-utils'; +import { isJSBlock } from '@ali/lowcode-types'; +import { isVariable } from '../utils'; + +export 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; +} + +export function upgradePageLifeCyclesReducer(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; +} diff --git a/packages/editor-preset-vision/src/utils/index.ts b/packages/editor-preset-vision/src/utils/index.ts new file mode 100644 index 000000000..ec3d7ea31 --- /dev/null +++ b/packages/editor-preset-vision/src/utils/index.ts @@ -0,0 +1,25 @@ +import { globalContext, Editor } from '@ali/lowcode-editor-core'; + +interface Variable { + type: 'variable'; + variable: string; + value: any; +} + +export function isVariable(obj: any): obj is Variable { + return obj && obj.type === 'variable'; +} + +export function getCurrentFieldIds() { + const editor = globalContext.get(Editor); + const designer = editor.get('designer'); + 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; +} diff --git a/packages/editor-preset-vision/src/viewport.ts b/packages/editor-preset-vision/src/viewport.ts index da943bed1..c21b29f02 100644 --- a/packages/editor-preset-vision/src/viewport.ts +++ b/packages/editor-preset-vision/src/viewport.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import Flags from './flags'; -import { designer } from './editor'; +import { editor } from './editor'; const domReady = require('domready'); @@ -58,7 +58,6 @@ class StyleResource { this.inited = true; const { type, content } = this.config; - let styleElement: any; if (type === 'URL') { styleElement = document.createElement('link'); @@ -192,10 +191,11 @@ export class Viewport { return this.preview; } - setDevice(device = 'pc') { + async setDevice(device = 'pc') { if (this.getDevice() !== device) { this.device = device; - designer.currentDocument?.simulator?.set('device', device === 'mobile' ? 'mobile' : 'default'); + const simulator = await editor.onceGot('simulator'); + simulator?.set('device', device === 'mobile' ? 'mobile' : 'default'); // Flags.setSimulator(device); // this.applyMediaCSS(); this.emitter.emit('devicechange', device); diff --git a/packages/editor-preset-vision/src/vision.less b/packages/editor-preset-vision/src/vision.less index 08be75898..fdf1c737b 100644 --- a/packages/editor-preset-vision/src/vision.less +++ b/packages/editor-preset-vision/src/vision.less @@ -103,9 +103,9 @@ html.engine-blur #engine { height: 16px!important; } -.lc-left-float-pane { - font-size: 14px; -} +// .lc-left-float-pane { +// font-size: 14px; +// } html.engine-preview-mode { .lc-left-area, .lc-right-area { @@ -114,7 +114,7 @@ html.engine-preview-mode { } .ve-popups .ve-message { - right: 290px; + right: calc(var(--right-area-width, 300px) + 10px); .ve-message-content { display: flex; @@ -125,4 +125,4 @@ html.engine-preview-mode { .lc-setter-mixed { flex: 1 -} \ No newline at end of file +} diff --git a/packages/editor-preset-vision/tests/bundle/bundle.test.ts b/packages/editor-preset-vision/tests/bundle/bundle.test.ts new file mode 100644 index 000000000..31174d481 --- /dev/null +++ b/packages/editor-preset-vision/tests/bundle/bundle.test.ts @@ -0,0 +1,116 @@ +import { Component } from 'react'; +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import divPrototypeConfig from '../fixtures/prototype/div-vision'; +import trunk from '../../src/bundle/trunk'; +import Prototype from '../../src/bundle/prototype'; +import Bundle from '../../src/bundle/bundle'; +import { Editor } from '@ali/lowcode-editor-core'; + +jest.mock('../../src/bundle/trunk', () => { + // mockComponentPrototype = jest.fn(); + // return { + // mockComponentPrototype: jest.fn().mockImplementation(() => { + // return proto; + // }), + // } + // return jest.fn().mockImplementation(() => { + // return {playSoundFile: fakePlaySoundFile}; + // }); + // return jest.fn().mockImplementation(() => { + // return { mockComponentPrototype }; + // }); + return { + __esModule: true, + default: { + mockComponentPrototype: jest.fn(), + }, + }; +}); + +function wrap(name, thing) { + return { + name, + componentName: name, + category: '布局', + module: thing, + }; +} + +const proto1 = new Prototype(divPrototypeConfig); +const protoConfig2 = cloneDeep(divPrototypeConfig); +set(protoConfig2, 'componentName', 'Div2'); +const proto2 = new Prototype(protoConfig2); + +const protoConfig3 = cloneDeep(divPrototypeConfig); +set(protoConfig3, 'componentName', 'Div3'); +const proto3 = new Prototype(protoConfig3); + +const protoConfig4 = cloneDeep(divPrototypeConfig); +set(protoConfig4, 'componentName', 'Div4'); +const proto4 = new Prototype(protoConfig4); + +const protoConfig5 = cloneDeep(divPrototypeConfig); +set(protoConfig5, 'componentName', 'Div5'); +const proto5 = new Prototype(protoConfig5); + +function getComponentProtos() { + return [ + wrap('Div', proto1), + // wrap('Div2', proto2), + // wrap('Div3', proto3), + wrap('DivPortal', [proto2, proto3]), + ]; +} + +class Div extends Component {} +Div.displayName = 'Div'; +class Div2 extends Component {} +Div2.displayName = 'Div2'; +class Div3 extends Component {} +Div3.displayName = 'Div3'; +class Div4 extends Component {} +Div4.displayName = 'Div4'; +class Div5 extends Component {} +Div5.displayName = 'Div5'; + +function getComponentViews() { + return [ + wrap('Div', Div), + // wrap('Div2', Div2), + // wrap('Div3', Div3), + wrap('DivPortal', [Div2, Div3]), + ]; +} + +describe('Bundle', () => { + it('构造函数', () => { + const protos = getComponentProtos(); + const views = getComponentViews(); + const bundle = new Bundle(protos, views); + expect(bundle.getList()).toHaveLength(3); + expect(bundle.get('Div')).toBe(proto1); + expect(bundle.get('Div2')).toBe(proto2); + expect(bundle.get('Div3')).toBe(proto3); + bundle.addComponentBundle([proto4, Div4]); + expect(bundle.getList()).toHaveLength(4); + expect(bundle.get('Div4')).toBe(proto4); + bundle.replacePrototype('Div4', proto3); + expect(proto3.getView()).toBe(Div4); + + bundle.removeComponentBundle('Div2'); + expect(bundle.getList()).toHaveLength(3); + expect(bundle.get('Div2')).toBeUndefined; + + expect(bundle.getFromMeta('Div')).toBe(proto1); + bundle.getFromMeta('Div5'); + expect(bundle.getList()).toHaveLength(4); + }); + it('静态方法 create', () => { + const protos = getComponentProtos(); + const views = getComponentViews(); + const bundle = Bundle.create(protos, views); + expect(bundle).toBeTruthy(); + }); +}); diff --git a/packages/editor-preset-vision/tests/bundle/prototype.test.ts b/packages/editor-preset-vision/tests/bundle/prototype.test.ts new file mode 100644 index 000000000..4776ebb1e --- /dev/null +++ b/packages/editor-preset-vision/tests/bundle/prototype.test.ts @@ -0,0 +1,219 @@ +import { Component } from 'react'; +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 divPrototypeConfig from '../fixtures/prototype/div-vision'; +import divFullPrototypeConfig from '../fixtures/prototype/div-vision-full'; +import divPrototypeMeta from '../fixtures/prototype/div-meta'; +// import VisualEngine from '../../src'; +import { designer } from '../../src/editor'; +import Prototype, { isPrototype } from '../../src/bundle/prototype'; +import { Editor } from '@ali/lowcode-editor-core'; +// import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; + +describe('Prototype', () => { + it('构造函数 - OldPrototypeConfig', () => { + const proto = new Prototype(divPrototypeConfig); + expect(isPrototype(proto)).toBeTruthy; + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe( + 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + ); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - 全量 OldPrototypeConfig', () => { + const proto = new Prototype(divFullPrototypeConfig); + expect(isPrototype(proto)).toBeTruthy; + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe( + 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + ); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - ComponentMetadata', () => { + const proto = new Prototype(divPrototypeMeta); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe( + 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + ); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - ComponentMeta', () => { + const meta = designer.createComponentMeta(divPrototypeMeta); + const proto = new Prototype(meta); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe( + 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + ); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - 静态函数 create', () => { + const proto = Prototype.create(divPrototypeConfig); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getCategory()).toBe('布局'); + expect(proto.getDocUrl()).toBe( + 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + ); + expect(proto.getIcon()).toBeUndefined; + expect(proto.getTitle()).toBe('Div'); + expect(proto.isPrototype).toBeTruthy; + expect(proto.isContainer()).toBeTruthy; + expect(proto.isModal()).toBeFalsy; + }); + it('构造函数 - lookup: true', () => { + const proto = Prototype.create(divPrototypeConfig); + const proto2 = Prototype.create(divPrototypeConfig, {}, true); + expect(proto).toBe(proto2); + }); + describe('类成员函数', () => { + let proto: Prototype = null; + beforeEach(() => { + proto = new Prototype(divPrototypeConfig); + }); + afterEach(() => { + proto = null; + }); + it('各种函数', () => { + expect(proto.componentName).toBe('Div'); + expect(proto.getComponentName()).toBe('Div'); + expect(proto.getId()).toBe('Div'); + expect(proto.getContextInfo('anything')).toBeUndefined; + expect(proto.getPropConfigs()).toBe(divPrototypeConfig); + expect(proto.getConfig()).toBe(divPrototypeConfig); + expect(proto.getConfig('componentName')).toBe('Div'); + expect(proto.getConfig('configure')).toBe(divPrototypeConfig.configure); + expect(proto.getConfig('docUrl')).toBe( + 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + ); + expect(proto.getConfig('title')).toBe('容器'); + expect(proto.getConfig('isContainer')).toBeTruthy; + + class MockView extends Component {} + + expect(proto.getView()).toBeUndefined; + proto.setView(MockView); + expect(proto.getView()).toBe(MockView); + expect(proto.meta.getMetadata().experimental?.view).toBe(MockView); + + expect(proto.getPackageName()).toBeUndefined; + proto.setPackageName('@ali/vc-div'); + expect(proto.getPackageName()).toBe('@ali/vc-div'); + + expect(proto.getConfig('category')).toBe('布局'); + proto.setCategory('布局 new'); + expect(proto.getConfig('category')).toBe('布局 new'); + + expect(proto.getConfigure()).toHaveLength(3); + expect(proto.getConfigure()[0].name).toBe('#props'); + expect(proto.getConfigure()[1].name).toBe('#styles'); + expect(proto.getConfigure()[2].name).toBe('#advanced'); + + expect(proto.getRectSelector()).toBeUndefined; + expect(proto.isAutoGenerated()).toBeFalsy; + }); + }); + + describe('类成员函数', () => { + it('addGlobalPropsConfigure', () => { + Prototype.addGlobalPropsConfigure({ + name: 'globalInsertProp1', + }); + const proto1 = new Prototype(divPrototypeConfig); + expect(proto1.getConfigure()[2].items).toHaveLength(4); + expect(proto1.getConfigure()[2].items[3].name).toBe('globalInsertProp1'); + Prototype.addGlobalPropsConfigure({ + name: 'globalInsertProp2', + }); + const proto2 = new Prototype(divPrototypeConfig); + expect(proto2.getConfigure()[2].items).toHaveLength(5); + expect(proto1.getConfigure()[2].items[4].name).toBe('globalInsertProp2'); + + Prototype.addGlobalPropsConfigure({ + name: 'globalInsertProp3', + position: 'top', + }); + + const proto3 = new Prototype(divPrototypeConfig); + expect(proto3.getConfigure()[0].items).toHaveLength(3); + expect(proto1.getConfigure()[0].items[0].name).toBe('globalInsertProp3'); + }); + + it('removeGlobalPropsConfigure', () => { + Prototype.removeGlobalPropsConfigure('globalInsertProp1'); + Prototype.removeGlobalPropsConfigure('globalInsertProp2'); + Prototype.removeGlobalPropsConfigure('globalInsertProp3'); + const proto2 = new Prototype(divPrototypeConfig); + expect(proto2.getConfigure()[0].items).toHaveLength(2); + expect(proto2.getConfigure()[2].items).toHaveLength(3); + }); + + it('overridePropsConfigure', () => { + Prototype.addGlobalPropsConfigure({ + name: 'globalInsertProp1', + title: 'globalInsertPropTitle', + position: 'top', + }); + const proto1 = new Prototype(divPrototypeConfig); + expect(proto1.getConfigure()[0].items).toHaveLength(3); + expect(proto1.getConfigure()[0].items[0].name).toBe('globalInsertProp1'); + expect(proto1.getConfigure()[0].items[0].title).toBe('globalInsertPropTitle'); + + Prototype.overridePropsConfigure('Div', [ + { + name: 'globalInsertProp1', + title: 'globalInsertPropTitleChanged', + }, + ]); + const proto2 = new Prototype(divPrototypeConfig); + expect(proto2.getConfigure()[0].name).toBe('globalInsertProp1'); + expect(proto2.getConfigure()[0].title).toBe('globalInsertPropTitleChanged'); + + Prototype.overridePropsConfigure('Div', { + globalInsertProp1: { + name: 'globalInsertProp1', + title: 'globalInsertPropTitleChanged new', + position: 'top', + }, + }); + const proto3 = new Prototype(divPrototypeConfig); + expect(proto3.getConfigure()[0].items[0].name).toBe('globalInsertProp1'); + expect(proto3.getConfigure()[0].items[0].title).toBe('globalInsertPropTitleChanged new'); + }); + + it('addGlobalExtraActions', () => { + function haha() { return 'heihei'; } + Prototype.addGlobalExtraActions(haha); + const proto1 = new Prototype(divPrototypeConfig); + expect(proto1.meta.availableActions).toHaveLength(4); + expect(proto1.meta.availableActions[3].name).toBe('haha'); + }); + }); +}); diff --git a/packages/editor-preset-vision/tests/bundle/trunk.test.ts b/packages/editor-preset-vision/tests/bundle/trunk.test.ts new file mode 100644 index 000000000..d4bca021f --- /dev/null +++ b/packages/editor-preset-vision/tests/bundle/trunk.test.ts @@ -0,0 +1,111 @@ +import { Component } from 'react'; +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import divPrototypeConfig from '../fixtures/prototype/div-vision'; +import Prototype from '../../src/bundle/prototype'; +import Bundle from '../../src/bundle/bundle'; +import trunk from '../../src/bundle/trunk'; +import lg from '@ali/vu-logger'; + +const proto1 = new Prototype(divPrototypeConfig); +const protoConfig2 = cloneDeep(divPrototypeConfig); +set(protoConfig2, 'componentName', 'Div2'); +const proto2 = new Prototype(protoConfig2); + +const protoConfig3 = cloneDeep(divPrototypeConfig); +set(protoConfig3, 'componentName', 'Div3'); +const proto3 = new Prototype(protoConfig3); + +const mockComponentPrototype = jest.fn(); +jest.mock('../../src/bundle/bundle', () => { + // return { + // mockComponentPrototype: jest.fn().mockImplementation(() => { + // return proto; + // }), + // } + // return jest.fn().mockImplementation(() => { + // return {playSoundFile: fakePlaySoundFile}; + // }); + return jest.fn().mockImplementation(() => { + return { + get: () => {}, + getList: () => { return []; }, + getFromMeta: () => {}, + }; + }); +}); + +const mockError = jest.fn(); +jest.mock('@ali/vu-logger'); +lg.error = mockError; + +function wrap(name, thing) { + return { + name, + componentName: name, + category: '布局', + module: thing, + }; +} + +function getComponentProtos() { + return [ + wrap('Div', proto1), + // wrap('Div2', proto2), + // wrap('Div3', proto3), + wrap('DivPortal', [proto2, proto3]), + ]; +} + +class Div extends Component {} +Div.displayName = 'Div'; +class Div2 extends Component {} +Div2.displayName = 'Div2'; +class Div3 extends Component {} +Div3.displayName = 'Div3'; + +function getComponentViews() { + return [ + wrap('Div', Div), + // wrap('Div2', Div2), + // wrap('Div3', Div3), + wrap('DivPortal', [Div2, Div3]), + ]; +} + +describe('Trunk', () => { + it('构造函数', () => { + const warn = console.warn = jest.fn(); + const trunkChangeHandler = jest.fn(); + const off = trunk.onTrunkChange(trunkChangeHandler); + trunk.addBundle(new Bundle([proto1], [Div])); + trunk.addBundle(new Bundle([proto2], [Div2])); + expect(trunkChangeHandler).toHaveBeenCalledTimes(2); + off(); + trunk.addBundle(new Bundle([proto3], [Div3])); + expect(trunkChangeHandler).toHaveBeenCalledTimes(2); + trunk.getList(); + trunk.getPrototype('Div'); + trunk.getPrototypeById('Div'); + trunk.getPrototypeView('Div'); + trunk.listByCategory(); + expect(trunk.mockComponentPrototype(new Bundle([proto3], [Div3]))).toBeUndefined; + expect(mockError).toHaveBeenCalled(); + trunk.registerComponentPrototypeMocker({ mockPrototype: jest.fn().mockImplementation(() => { return proto3; }) }); + expect(trunk.mockComponentPrototype(new Bundle([proto3], [Div3]))).toBe(proto3); + const hahaSetter = () => 'haha'; + trunk.registerSetter('haha', hahaSetter); + expect(trunk.getSetter('haha')).toBe(hahaSetter); + trunk.getRecents(5); + trunk.setPackages(); + expect(warn).toHaveBeenCalledTimes(1); + trunk.beforeLoadBundle(); + expect(warn).toHaveBeenCalledTimes(2); + trunk.afterLoadBundle(); + expect(warn).toHaveBeenCalledTimes(3); + trunk.getBundle(); + expect(warn).toHaveBeenCalledTimes(4); + expect(trunk.isReady()).toBeTruthy; + }); +}); diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts new file mode 100644 index 000000000..a2b410494 --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-meta.ts @@ -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: [], + }, +}; diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts new file mode 100644 index 000000000..756c37649 --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision-full.ts @@ -0,0 +1,293 @@ +export default { + title: '容器', + componentName: 'Div', + docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + category: '布局', + isContainer: true, + canOperating: false, + extraActions: [], + canContain: 'Form', + canDropTo: 'Div', + canDropIn: 'Div', + canResizing: true, + canDraging: false, + context: {}, + initialChildren() {}, + didDropIn() {}, + didDropOut() {}, + subtreeModified() {}, + onResize() {}, + onResizeStart() {}, + onResizeEnd() {}, + canUseCondition: true, + canLoop: true, + snippets: [ + { + screenshot: 'https://img.alicdn.com/tfs/TB1CHN3u4z1gK0jSZSgXXavwpXa-112-64.png', + label: '普通型', + schema: { + componentName: 'Div', + props: {}, + }, + }, + ], + configure: [ + { + name: 'myName', + title: '我的名字', + display: 'tab', + initialValue: 'NORMAL', + defaultValue: 'NORMAL', + collapsed: true, + supportVariable: true, + accessor(field, val) {}, + mutator(field, val) {}, + disabled() { + return true; + }, + useVariableChange() {}, + allowTextInput: true, + liveTextEditing: true, + setter: [ + { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + ], + }, + { + name: 'mySlotName', + slotName: 'mySlotName', + slotTitle: '我的 Slot 名字', + display: 'tab', + initialValue: 'NORMAL', + defaultValue: 'NORMAL', + collapsed: true, + supportVariable: true, + accessor(field, val) {}, + mutator(field, val) {}, + disabled() { + return true; + }, + setter: { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + }, + { + name: 'behavior', + title: '默认状态', + display: 'inline', + initialValue: 'NORMAL', + supportVariable: true, + setter: { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + }, + { + name: '__style__', + title: '样式设置', + display: 'accordion', + collapsed: false, + initialValue: {}, + tip: { + url: 'https://lark.alipay.com/legao/help/design-tool-style', + content: '点击 ? 查看样式设置器用法指南', + }, + setter: { + key: null, + ref: null, + props: { + advanced: true, + }, + _owner: null, + }, + }, + { + type: 'group', + title: '高级', + display: 'accordion', + items: [ + { + name: 'fieldId', + title: '唯一标识', + display: 'block', + tip: + '组件的唯一标识符,不能够与其它组件重名,不能够为空,且只能够使用以字母开头的,下划线以及数字的组合。', + setter: { + key: null, + ref: null, + props: { + placeholder: '请输入唯一标识', + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'useFieldIdAsDomId', + title: '将唯一标识用作 DOM ID', + display: 'block', + tip: + '开启这个配置项后,会在当前组件的 HTML 元素上加入 id="当前组件的 fieldId",一般用于做 utils 的绑定,不常用', + initialValue: false, + setter: { + key: null, + ref: null, + props: {}, + _owner: null, + }, + }, + { + name: 'customClassName', + title: '自定义样式类', + display: 'block', + supportVariable: true, + initialValue: '', + setter: { + key: null, + ref: null, + props: { + placeholder: null, + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'events', + title: '动作设置', + tip: { + url: 'https://lark.alipay.com/legao/legao/events-call', + content: '点击 ? 查看如何设置组件的事件响应动作', + }, + display: 'accordion', + initialValue: { + 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, + }, + }, + { + name: 'onClick', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseEnter', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseLeave', + display: 'none', + initialValue: { + ignored: true, + }, + }, + ], + }, + ], +}; diff --git a/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts new file mode 100644 index 000000000..c4ae4374f --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/prototype/div-vision.ts @@ -0,0 +1,175 @@ +export default { + title: '容器', + componentName: 'Div', + docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md', + category: '布局', + isContainer: true, + configure: [ + { + name: 'behavior', + title: '默认状态', + display: 'inline', + initialValue: 'NORMAL', + supportVariable: true, + setter: { + key: null, + ref: null, + props: { + options: [ + { + title: '普通', + value: 'NORMAL', + }, + { + title: '隐藏', + value: 'HIDDEN', + }, + ], + loose: false, + cancelable: false, + }, + _owner: null, + }, + }, + { + name: '__style__', + title: '样式设置', + display: 'accordion', + collapsed: false, + initialValue: {}, + tip: { + url: 'https://lark.alipay.com/legao/help/design-tool-style', + content: '点击 ? 查看样式设置器用法指南', + }, + setter: { + key: null, + ref: null, + props: { + advanced: true, + }, + _owner: null, + }, + }, + { + type: 'group', + title: '高级', + display: 'accordion', + items: [ + { + name: 'fieldId', + title: '唯一标识', + display: 'block', + tip: + '组件的唯一标识符,不能够与其它组件重名,不能够为空,且只能够使用以字母开头的,下划线以及数字的组合。', + setter: { + key: null, + ref: null, + props: { + placeholder: '请输入唯一标识', + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'useFieldIdAsDomId', + title: '将唯一标识用作 DOM ID', + display: 'block', + tip: + '开启这个配置项后,会在当前组件的 HTML 元素上加入 id="当前组件的 fieldId",一般用于做 utils 的绑定,不常用', + initialValue: false, + setter: { + key: null, + ref: null, + props: {}, + _owner: null, + }, + }, + { + name: 'customClassName', + title: '自定义样式类', + display: 'block', + supportVariable: true, + initialValue: '', + setter: { + key: null, + ref: null, + props: { + placeholder: null, + multiline: false, + rows: 10, + required: false, + pattern: null, + maxLength: null, + }, + _owner: null, + }, + }, + { + name: 'events', + title: '动作设置', + tip: { + url: 'https://lark.alipay.com/legao/legao/events-call', + content: '点击 ? 查看如何设置组件的事件响应动作', + }, + display: 'accordion', + initialValue: { + 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, + }, + }, + { + name: 'onClick', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseEnter', + display: 'none', + initialValue: { + ignored: true, + }, + }, + { + name: 'onMouseLeave', + display: 'none', + initialValue: { + ignored: true, + }, + }, + ], + }, + ], +}; diff --git a/packages/editor-preset-vision/tests/fixtures/schema/form.ts b/packages/editor-preset-vision/tests/fixtures/schema/form.ts new file mode 100644 index 000000000..5492a9ffb --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/schema/form.ts @@ -0,0 +1,955 @@ +export default { + componentName: 'Page', + id: 'node_k1ow3cb9', + props: { + extensions: { + 启用页头: true, + }, + pageStyle: { + backgroundColor: '#f2f3f5', + }, + containerStyle: {}, + className: 'page_kh05zf9c', + 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_kh05zf9d {\n margin-bottom: 12px;\n}.card_kh05zf9e {\n margin-bottom: 12px;\n}.button_kh05zf9f {\n margin-right: 16px;\n width: 80px\n}.button_kh05zf9g {\n width: 80px;\n}.div_kh05zf9h {\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: '', + content: '', + __slot__logo: false, + __slot__crumb: false, + crumb: '', + tab: '', + logo: '', + action: '', + __slot__tab: false, + __style__: {}, + __slot__content: false, + fieldId: 'pageHeader_k1ow3h1i', + subTitle: '', + }, + condition: true, + }, + ], + }, + { + componentName: 'RootContent', + id: 'node_k1ow3cbb', + props: { + contentBgColor: 'transparent', + contentPadding: '0', + contentMargin: '20', + }, + condition: true, + children: [ + { + componentName: 'Form', + id: 'form', + props: { + size: 'medium', + labelAlign: 'top', + autoValidate: true, + scrollToFirstError: true, + autoUnmount: true, + behavior: 'NORMAL', + dataSource: { + type: 'variable', + variable: 'state.formData', + }, + __style__: {}, + fieldId: 'form', + fieldOptions: {}, + }, + 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_kh05zf9d', + 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: '4:8', + 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: '', + zh_CN: '', + }, + maxLength: 200, + }, + 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: '', + zh_CN: '', + }, + maxLength: 200, + }, + 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: '', + zh_CN: '', + }, + maxLength: 200, + }, + 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: '', + zh_CN: '', + }, + maxLength: 200, + }, + 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: '', + zh_CN: '', + }, + useDetailValue: false, + 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_kh05zf9e', + 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: '', + zh_CN: '', + }, + maxLength: 200, + }, + 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: '', + zh_CN: '', + }, + maxLength: 200, + }, + 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: '', + zh_CN: '', + }, + maxLength: 200, + }, + condition: true, + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + componentName: 'Div', + id: 'node_k1ow3cbo', + props: { + className: 'div_kh05zf9h', + 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_kh05zf9f', + 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_kh05zf9g', + 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', + }, + condition: true, + }, + ], + }, + ], + }, + ], + }, + { + componentName: 'RootFooter', + id: 'node_k1ow3cbc', + props: {}, + condition: true, + }, + ], +}; diff --git a/packages/editor-preset-vision/tests/fixtures/window.ts b/packages/editor-preset-vision/tests/fixtures/window.ts new file mode 100644 index 000000000..b5886ad6d --- /dev/null +++ b/packages/editor-preset-vision/tests/fixtures/window.ts @@ -0,0 +1,8 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +React.PropTypes = PropTypes; +window.React = React; + +document.documentElement.requestFullscreen = () => {}; +document.exitFullscreen = () => {}; \ No newline at end of file diff --git a/packages/editor-preset-vision/tests/master/__snapshots__/deep-value-parser.test.ts.snap b/packages/editor-preset-vision/tests/master/__snapshots__/deep-value-parser.test.ts.snap new file mode 100644 index 000000000..0dd46763f --- /dev/null +++ b/packages/editor-preset-vision/tests/master/__snapshots__/deep-value-parser.test.ts.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`deepValueParser 测试 designMode: design 1`] = ` +Object { + "a": "111", + "arr": Array [ + "111", + "111", + ], + "b": "222", + "c": "中文", + "slot": Object { + "type": "JSSlot", + "value": Array [ + Object { + "componentName": "Div", + "props": Object {}, + }, + ], + }, +} +`; + +exports[`deepValueParser 测试 designMode: live 1`] = ` +Object { + "a": Object { + "mock": "111", + "type": "JSExpression", + "value": "state.a", + }, + "arr": Array [ + Object { + "mock": "111", + "type": "JSExpression", + "value": "state.a", + }, + Object { + "mock": "111", + "type": "JSExpression", + "value": "state.b", + }, + ], + "b": Object { + "mock": "222", + "type": "JSExpression", + "value": "state.b", + }, + "c": "中文", +} +`; diff --git a/packages/editor-preset-vision/tests/master/bus.test.ts b/packages/editor-preset-vision/tests/master/bus.test.ts new file mode 100644 index 000000000..9567e1726 --- /dev/null +++ b/packages/editor-preset-vision/tests/master/bus.test.ts @@ -0,0 +1,240 @@ +import '../fixtures/window'; +import bus from '../../src/bus'; +import { editor } from '../../src/editor'; + +describe('bus 测试', () => { + afterEach(() => { + bus.unsub('evt1'); + }); + it('sub / pub 测试', () => { + const mockFn1 = jest.fn(); + const off1 = bus.sub('evt1', mockFn1); + + const evtData = { a: 1 }; + bus.pub('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + + off1(); + + bus.pub('evt1', evtData); + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + }); + + it('on / emit 测试', () => { + const mockFn1 = jest.fn(); + const off1 = bus.on('evt1', mockFn1); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + + off1(); + + bus.emit('evt1', evtData); + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + }); + + it('once / emit 测试', () => { + const mockFn1 = jest.fn(); + bus.once('evt1', mockFn1); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + + bus.emit('evt1', evtData); + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + }); + + it('once / emit 测试,调用解绑函数', () => { + const mockFn1 = jest.fn(); + const off1 = bus.once('evt1', mockFn1); + + off1(); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).not.toHaveBeenCalled(); + }); + + it('removeListener 测试', () => { + const mockFn1 = jest.fn(); + bus.on('evt1', mockFn1); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + + bus.removeListener('evt1', mockFn1); + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + }); + + it('unsub 测试 - 只有一个 handler', () => { + const mockFn1 = jest.fn(); + bus.on('evt1', mockFn1); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + + bus.unsub('evt1', mockFn1); + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + }); + + it('unsub 测试 - 只 unsub 一个 handler', () => { + const mockFn1 = jest.fn(); + const mockFn2 = jest.fn(); + bus.on('evt1', mockFn1); + bus.on('evt1', mockFn2); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(1); + expect(mockFn2).toHaveBeenCalledWith(evtData); + + bus.unsub('evt1', mockFn1); + const evtData2 = { a: 2 }; + bus.emit('evt1', evtData2); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(2); + expect(mockFn2).toHaveBeenLastCalledWith(evtData2); + }); + + it('unsub 测试 - 多个 handler', () => { + const mockFn1 = jest.fn(); + const mockFn2 = jest.fn(); + bus.on('evt1', mockFn1); + bus.on('evt1', mockFn2); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(1); + expect(mockFn2).toHaveBeenCalledWith(evtData); + + bus.unsub('evt1'); + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(1); + expect(mockFn2).toHaveBeenCalledWith(evtData); + }); + + it('off 测试 - 只有一个 handler', () => { + const mockFn1 = jest.fn(); + bus.on('evt1', mockFn1); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + + bus.off('evt1', mockFn1); + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + }); + + it('off 测试 - 只 off 一个 handler', () => { + const mockFn1 = jest.fn(); + const mockFn2 = jest.fn(); + bus.on('evt1', mockFn1); + bus.on('evt1', mockFn2); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(1); + expect(mockFn2).toHaveBeenCalledWith(evtData); + + bus.off('evt1', mockFn1); + const evtData2 = { a: 2 }; + bus.emit('evt1', evtData2); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(2); + expect(mockFn2).toHaveBeenLastCalledWith(evtData2); + }); + + it('off 测试 - 多个 handler', () => { + const mockFn1 = jest.fn(); + const mockFn2 = jest.fn(); + bus.on('evt1', mockFn1); + bus.on('evt1', mockFn2); + + const evtData = { a: 1 }; + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(1); + expect(mockFn2).toHaveBeenCalledWith(evtData); + + bus.off('evt1'); + bus.emit('evt1', evtData); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData); + expect(mockFn2).toHaveBeenCalledTimes(1); + expect(mockFn2).toHaveBeenCalledWith(evtData); + }); + + it('简单测试(dummy)', () => { + bus.getEmitter(); + }); + + describe('editor 事件转发', () => { + const fwdEvtMap = { + 've.hotkey.callback.call': 'hotkey.callback.call', + 've.history.back': 'history.back', + 've.history.forward': 'history.forward', + 'node.prop.change': 'node.prop.change', + }; + + Object.keys(fwdEvtMap).forEach(veEventName => { + it(`${veEventName} 测试`, () => { + const mockFn1 = jest.fn(); + const evtData1 = { a: 1 }; + bus.on(veEventName, mockFn1); + + editor.emit(fwdEvtMap[veEventName], evtData1); + + expect(mockFn1).toHaveBeenCalledTimes(1); + expect(mockFn1).toHaveBeenCalledWith(evtData1); + }); + }); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/context.test.ts b/packages/editor-preset-vision/tests/master/context.test.ts new file mode 100644 index 000000000..362e4103d --- /dev/null +++ b/packages/editor-preset-vision/tests/master/context.test.ts @@ -0,0 +1,62 @@ +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +// import { Node } from '../../src/document/node/node'; +// import { Designer } from '../../src/designer/designer'; +import { VisualEngineContext } from '../../src/context'; +import { autorun } from '@ali/lowcode-editor-core'; + +describe('VisualEngineContext 测试', () => { + it('registerManager | getManager', () => { + const ctx = new VisualEngineContext(); + + ctx.registerManager({ + mgr1: {}, + }); + ctx.registerManager('mgr2', {}); + expect(ctx.getManager('mgr1')).toEqual({}); + }); + + it('registerModule | getModule', () => { + const ctx = new VisualEngineContext(); + + ctx.registerModule({ + mod1: {}, + }); + ctx.registerModule('mod2', {}); + expect(ctx.getModule('mod1')).toEqual({}); + }); + + it('use | getPlugin', () => { + const ctx = new VisualEngineContext(); + + ctx.use('plugin1', { plugin: 1 }); + ctx.registerManager({ + mgr1: { manager: 1 }, + }); + ctx.registerModule({ + mod1: { mod: 1 }, + }); + expect(ctx.getPlugin('plugin1')).toEqual({ plugin: 1 }); + expect(ctx.getPlugin('mgr1')).toEqual({ manager: 1 }); + expect(ctx.getPlugin('mod1')).toEqual({ mod: 1 }); + expect(ctx.getPlugin()).toBeUndefined; + + ctx.use('ve.settingField.variableSetter', {}); + }); + + it('registerTreePane | getModule', () => { + const ctx = new VisualEngineContext(); + + ctx.registerTreePane({ pane: 1 }, { core: 2 }); + expect(ctx.getModule('TreePane')).toEqual({ pane: 1 }); + expect(ctx.getModule('TreeCore')).toEqual({ core: 2 }); + }); + + it('registerDynamicSetterProvider', () => { + const ctx = new VisualEngineContext(); + + ctx.registerDynamicSetterProvider({}); + expect(ctx.getPlugin('ve.plugin.setterProvider')).toEqual({}); + ctx.registerDynamicSetterProvider(); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/deep-value-parser.test.ts b/packages/editor-preset-vision/tests/master/deep-value-parser.test.ts new file mode 100644 index 000000000..8d2c27b80 --- /dev/null +++ b/packages/editor-preset-vision/tests/master/deep-value-parser.test.ts @@ -0,0 +1,84 @@ +import '../fixtures/window'; +import { deepValueParser } from '../../src/deep-value-parser'; +import { editor } from '../../src/editor'; + +describe('deepValueParser 测试', () => { + it('null & undefined', () => { + expect(deepValueParser()).toBeNull; + expect(deepValueParser()).toBeUndefined; + }); + + it('designMode: design', () => { + expect(deepValueParser({ + a: { + type: 'variable', + variable: 'state.a', + value: '111', + }, + b: { + type: 'JSExpression', + value: 'state.b', + mock: '222', + }, + c: { + type: 'i18n', + use: 'zh_CN', + zh_CN: '中文', + en_US: 'eng', + }, + slot: { + type: 'JSSlot', + value: [{ + componentName: 'Div', + props: {}, + }], + }, + arr: [ + { + type: 'variable', + variable: 'state.a', + value: '111', + }, + { + type: 'variable', + variable: 'state.b', + value: '111', + }, + ], + })).toMatchSnapshot(); + }); + + it('designMode: live', () => { + editor.set('designMode', 'live'); + expect(deepValueParser({ + a: { + type: 'variable', + variable: 'state.a', + value: '111', + }, + b: { + type: 'JSExpression', + value: 'state.b', + mock: '222', + }, + c: { + type: 'i18n', + use: 'zh_CN', + zh_CN: '中文', + en_US: 'eng', + }, + arr: [ + { + type: 'variable', + variable: 'state.a', + value: '111', + }, + { + type: 'variable', + variable: 'state.b', + value: '111', + }, + ], + })).toMatchSnapshot(); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/drag-engine.test.ts b/packages/editor-preset-vision/tests/master/drag-engine.test.ts new file mode 100644 index 000000000..438e30fac --- /dev/null +++ b/packages/editor-preset-vision/tests/master/drag-engine.test.ts @@ -0,0 +1,182 @@ +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +// import { Node } from '../../src/document/node/node'; +// import { Editor } from '@ali/lowcode-editor-core'; +// import { Designer } from '@ali/lowcode-designer'; +import { designer } from '../../src/editor'; +import DragEngine from '../../src/drag-engine'; +import formSchema from '../fixtures/schema/form'; + +// const editor = new Editor(); +// const designer = new Designer({ editor }); +designer.project.open(formSchema); + +const mockBoostPrototype = jest.fn((e: MouseEvent) => { + return { + isPrototype: true, + getComponentName() { + return 'Div'; + }, + }; +}); + +const mockBoostNode = jest.fn((e: MouseEvent) => { + return designer.currentDocument?.getNode('node_k1ow3cbo'); +}); + +const mockBoostNodeData = jest.fn((e: MouseEvent) => { + return { + type: 'NodeData', + componentName: 'Div', + }; +}); + +const mockBoostNull = jest.fn((e: MouseEvent) => { + return null; +}); + +const mockDragstart = jest.fn(); +const mockDrag = jest.fn(); +const mockDragend = jest.fn(); + +describe('drag-engine 测试', () => { + it('prototype', async () => { + DragEngine.from(document, mockBoostPrototype); + + DragEngine.onDragstart(mockDragstart); + DragEngine.onDrag(mockDrag); + DragEngine.onDragend(mockDragend); + + const mousedownEvt = new MouseEvent('mousedown'); + document.dispatchEvent(mousedownEvt); + designer.dragon.emitter.emit('dragstart', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')], + }, + originalEvent: mousedownEvt, + }); + + // await new Promise(resolve => resolve(setTimeout, 500)); + + expect(mockDragstart).toHaveBeenCalled(); + + designer.dragon.emitter.emit('drag', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')] + }, + originalEvent: mousedownEvt, + }); + + expect(mockDrag).toHaveBeenCalled(); + expect(DragEngine.inDragging()).toBeTruthy; + + designer.dragon.emitter.emit('dragend', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')] + }, + originalEvent: mousedownEvt, + }); + + expect(mockDragend).toHaveBeenCalled(); + }); + + it('Node', async () => { + DragEngine.from(document, mockBoostNode); + + DragEngine.onDragstart(mockDragstart); + DragEngine.onDrag(mockDrag); + DragEngine.onDragend(mockDragend); + + const mousedownEvt = new MouseEvent('mousedown'); + document.dispatchEvent(mousedownEvt); + designer.dragon.emitter.emit('dragstart', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')], + }, + originalEvent: mousedownEvt, + }); + + // await new Promise(resolve => resolve(setTimeout, 500)); + + expect(mockDragstart).toHaveBeenCalled(); + + designer.dragon.emitter.emit('drag', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')] + }, + originalEvent: mousedownEvt, + }); + + expect(mockDrag).toHaveBeenCalled(); + + designer.dragon.emitter.emit('dragend', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')] + }, + originalEvent: mousedownEvt, + }); + + expect(mockDragend).toHaveBeenCalled(); + }); + + it('NodeData', async () => { + DragEngine.from(document, mockBoostNodeData); + + DragEngine.onDragstart(mockDragstart); + DragEngine.onDrag(mockDrag); + DragEngine.onDragend(mockDragend); + + const mousedownEvt = new MouseEvent('mousedown'); + document.dispatchEvent(mousedownEvt); + designer.dragon.emitter.emit('dragstart', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')], + }, + originalEvent: mousedownEvt, + }); + + // await new Promise(resolve => resolve(setTimeout, 500)); + + expect(mockDragstart).toHaveBeenCalled(); + + designer.dragon.emitter.emit('drag', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')] + }, + originalEvent: mousedownEvt, + }); + + expect(mockDrag).toHaveBeenCalled(); + + designer.dragon.emitter.emit('dragend', { + dragObject: { + type: 'nodedata', + data: { + componentName: 'Div', + }, + }, + originalEvent: mousedownEvt, + }); + + expect(mockDragend).toHaveBeenCalled(); + }); + + it('null', async () => { + DragEngine.from(document, mockBoostNull); + + DragEngine.onDragstart(mockDragstart); + DragEngine.onDrag(mockDrag); + DragEngine.onDragend(mockDragend); + + const mousedownEvt = new MouseEvent('mousedown'); + document.dispatchEvent(mousedownEvt); + designer.dragon.emitter.emit('dragstart', { + dragObject: { + nodes: [designer.currentDocument?.getNode('node_k1ow3cbo')], + }, + originalEvent: mousedownEvt, + }); + + expect(mockDragstart).toHaveBeenCalled(); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/env.test.ts b/packages/editor-preset-vision/tests/master/env.test.ts new file mode 100644 index 000000000..6f280295c --- /dev/null +++ b/packages/editor-preset-vision/tests/master/env.test.ts @@ -0,0 +1,111 @@ +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +// import { Node } from '../../src/document/node/node'; +// import { Designer } from '../../src/designer/designer'; +import env from '../../src/env'; +import { autorun } from '@ali/lowcode-editor-core'; + +describe('env 测试', () => { + describe('常规 API 测试', () => { + it('setEnv / getEnv / setEnvMap / set / get', () => { + expect(env.getEnv('xxx')).toBeUndefined; + + const mockFn1 = jest.fn(); + const off1 = env.onEnvChange(mockFn1); + + const envData = { a: 1 }; + env.setEnv('xxx', envData); + expect(env.getEnv('xxx')).toEqual(envData); + expect(env.get('xxx')).toEqual(envData); + expect(mockFn1).toHaveBeenCalled(); + expect(mockFn1).toHaveBeenCalledWith(env.envs, 'xxx', envData); + mockFn1.mockClear(); + + // 设置相同的值 + env.setEnv('xxx', envData); + expect(env.getEnv('xxx')).toEqual(envData); + expect(env.get('xxx')).toEqual(envData); + expect(mockFn1).not.toHaveBeenCalled(); + mockFn1.mockClear(); + + // 设置另一个 envName + const envData2 = { b: 1 }; + env.set('yyy', envData2); + expect(env.getEnv('yyy')).toEqual(envData2); + expect(env.get('yyy')).toEqual(envData2); + expect(mockFn1).toHaveBeenCalled(); + expect(mockFn1).toHaveBeenCalledWith(env.envs, 'yyy', envData2); + mockFn1.mockClear(); + + env.setEnvMap({ + zzz: { a: 1, b: 1 }, + }); + expect(env.getEnv('xxx')).toBeUndefined; + expect(env.getEnv('yyy')).toBeUndefined; + expect(env.getEnv('zzz')).toEqual({ a: 1, b: 1 }); + expect(mockFn1).toHaveBeenCalled(); + expect(mockFn1).toHaveBeenCalledWith(env.envs); + mockFn1.mockClear(); + + // 解绑事件 + off1(); + env.setEnvMap({ + zzz: { a: 1, b: 1 }, + }); + expect(mockFn1).not.toHaveBeenCalled(); + mockFn1.mockClear(); + }); + + it('setLocale / getLocale', () => { + expect(env.getLocale()).toBe('zh_CN'); + env.setLocale('en_US'); + expect(env.getLocale()).toBe('en_US'); + }); + + it('setExpertMode / isExpertMode', () => { + expect(env.isExpertMode()).toBeFalsy; + env.setExpertMode('truthy value'); + expect(env.isExpertMode()).toBeTruthy; + }); + + it('getSupportFeatures / setSupportFeatures / supports', () => { + expect(env.getSupportFeatures()).toEqual({}); + env.setSupportFeatures({ + mobile: true, + pc: true, + }); + expect(env.getSupportFeatures()).toEqual({ + mobile: true, + pc: true, + }); + expect(env.supports('mobile')).toBeTruthy; + expect(env.supports('pc')).toBeTruthy; + expect(env.supports('iot')).toBeFalsy; + }); + + it('getAliSchemaVersion', () => { + expect(env.getAliSchemaVersion()).toBe('1.0.0'); + }); + + it('envs obx 测试', async () => { + const mockFn = jest.fn(); + env.clear(); + + autorun(() => { + mockFn(env.envs); + env.envs; + }); + + await new Promise(resolve => setTimeout(resolve, 16)); + + expect(mockFn).toHaveBeenCalledTimes(1); + expect(mockFn).toHaveBeenLastCalledWith({}); + + env.setEnv('abc', { a: 1 }); + + await new Promise(resolve => setTimeout(resolve, 16)); + expect(mockFn).toHaveBeenCalledTimes(2); + expect(mockFn).toHaveBeenLastCalledWith({ abc: { a: 1 } }); + }); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/flags.test.ts b/packages/editor-preset-vision/tests/master/flags.test.ts new file mode 100644 index 000000000..c5c3301e6 --- /dev/null +++ b/packages/editor-preset-vision/tests/master/flags.test.ts @@ -0,0 +1,71 @@ +import '../fixtures/window'; +import flagsCtrl from '../../src/flags'; +import domready from 'domready'; + +jest.mock('domready', () => { + return (fn) => fn(); +}); +// domready.mockImplementation((fn) => fn()); + +describe('flags 测试', () => { + it('flags', () => { + const mockFlagsChange = jest.fn(); + flagsCtrl.flags = []; + const off = flagsCtrl.onFlagsChange(mockFlagsChange); + flagsCtrl.add('a'); + expect(mockFlagsChange).toHaveBeenCalledTimes(1); + off(); + flagsCtrl.add('b'); + expect(mockFlagsChange).toHaveBeenCalledTimes(1); + + + expect(flagsCtrl.getFlags()).toEqual(['a', 'b']); + + flagsCtrl.flags = []; + flagsCtrl.setDragMode(true); + expect(flagsCtrl.getFlags()).toEqual(['drag-mode']); + flagsCtrl.setDragMode(false); + expect(flagsCtrl.getFlags()).toEqual([]); + + flagsCtrl.setPreviewMode(true); + expect(flagsCtrl.getFlags()).toEqual(['preview-mode']); + flagsCtrl.setPreviewMode(false); + expect(flagsCtrl.getFlags()).toEqual(['design-mode']); + + flagsCtrl.flags = []; + flagsCtrl.setHideSlate(true); + expect(flagsCtrl.getFlags()).toEqual(['hide-slate']); + flagsCtrl.setHideSlate(false); + expect(flagsCtrl.getFlags()).toEqual([]); + + flagsCtrl.flags = []; + flagsCtrl.setSlateFixedMode(true); + expect(flagsCtrl.getFlags()).toEqual(['slate-fixed']); + flagsCtrl.setHideSlate(true); + expect(flagsCtrl.getFlags()).toEqual(['slate-fixed']); + flagsCtrl.setSlateFixedMode(false); + expect(flagsCtrl.getFlags()).toEqual([]); + + flagsCtrl.flags = []; + flagsCtrl.setSlateFullMode(true); + expect(flagsCtrl.getFlags()).toEqual(['slate-full-screen']); + flagsCtrl.setSlateFullMode(false); + expect(flagsCtrl.getFlags()).toEqual([]); + + expect([].slice.apply(document.documentElement.classList)).toEqual(flagsCtrl.getFlags()); + + flagsCtrl.flags = []; + // setWithShell + flagsCtrl.setWithShell('shellA'); + expect(flagsCtrl.getFlags()).toEqual(['with-iphone6shell']); + flagsCtrl.setWithShell('iPhone6'); + expect(flagsCtrl.getFlags()).toEqual(['with-iphone6shell']); + + flagsCtrl.flags = []; + // setSimulator + flagsCtrl.setSimulator('simA'); + expect(flagsCtrl.getFlags()).toEqual(['simulator-simA']); + flagsCtrl.setSimulator('simB'); + expect(flagsCtrl.getFlags()).toEqual(['simulator-simB']); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/panes.test.ts b/packages/editor-preset-vision/tests/master/panes.test.ts new file mode 100644 index 000000000..1a36eb271 --- /dev/null +++ b/packages/editor-preset-vision/tests/master/panes.test.ts @@ -0,0 +1,176 @@ +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +// import { Node } from '../../src/document/node/node'; +// import { Designer } from '../../src/designer/designer'; +import panes from '../../src/panes'; +import { autorun } from '@ali/lowcode-editor-core'; + +describe('panes 测试', () => { + it('add: type dock | PanelDock', () => { + const mockDockShow = jest.fn(); + const mockDockHide = jest.fn(); + const { DockPane } = panes; + const offDockShow = DockPane.onDockShow(mockDockShow); + const offDockHide = DockPane.onDockHide(mockDockHide); + + const pane1 = panes.add({ + name: 'trunk', + type: 'dock', + width: 300, + description: '组件库', + contents: [ + { + title: '普通组件', + tip: '普通组件', + content: () => 'haha', + }, + ], + menu: '组件库', + defaultFixed: true, + }); + + const pane2 = panes.add({ + name: 'trunk2', + type: 'dock', + width: 300, + description: '组件库', + contents: [ + { + title: '普通组件', + tip: '普通组件', + content: () => 'haha', + }, + ], + menu: '组件库', + defaultFixed: true, + }); + + const pane3 = panes.add({ + name: 'trunk3', + type: 'dock', + isAction: true, + }); + + // DockPane.container.items.map(item => console.log(item.name)) + // 2 trunks + 1 outline-pane + expect(DockPane.container.items.length).toBe(4); + + DockPane.activeDock(pane1); + // expect(mockDockShow).toHaveBeenCalledTimes(1); + // expect(mockDockShow).toHaveBeenLastCalledWith(pane1); + expect(DockPane.container.items[1].visible).toBeTruthy; + + DockPane.activeDock(pane2); + expect(DockPane.container.items[2].visible).toBeTruthy; + // expect(mockDockShow).toHaveBeenCalledTimes(2); + // expect(mockDockShow).toHaveBeenLastCalledWith(pane2); + // expect(mockDockHide).toHaveBeenCalledTimes(1); + // expect(mockDockHide).toHaveBeenLastCalledWith(pane1); + + DockPane.activeDock(); + DockPane.activeDock({ name: 'unexisting' }); + + offDockShow(); + offDockHide(); + + // DockPane.activeDock(pane1); + // expect(mockDockShow).toHaveBeenCalledTimes(2); + // expect(mockDockHide).toHaveBeenCalledTimes(1); + + expect(typeof DockPane.getDocks).toBe('function'); + DockPane.getDocks(); + expect(typeof DockPane.setFixed).toBe('function'); + DockPane.setFixed(); + }); + + it('add: type action', () => { + panes.add({ + name: 'trunk', + type: 'action', + init() {}, + destroy() {}, + }); + + const { ActionPane } = panes; + expect(typeof ActionPane.getActions).toBe('function'); + ActionPane.getActions(); + expect(typeof ActionPane.setActions).toBe('function'); + ActionPane.setActions(); + expect(ActionPane.getActions()).toBe(ActionPane.actions); + }); + + it('add: type action - extraConfig', () => { + panes.add({ + name: 'trunk', + type: 'action', + init() {}, + destroy() {}, + }, {}); + }); + + it('add: type action - function', () => { + panes.add(() => ({ + name: 'trunk', + type: 'action', + init() {}, + destroy() {}, + })); + }); + + it('add: type tab', () => { + panes.add({ + name: 'trunk', + type: 'tab', + }); + const { TabPane } = panes; + expect(typeof TabPane.setFloat).toBe('function'); + TabPane.setFloat(); + }); + + it('add: type stage', () => { + panes.add({ + id: 'stage1', + type: 'stage', + }); + panes.add({ + type: 'stage', + }); + + const { Stages } = panes; + expect(typeof Stages.getStage).toBe('function'); + Stages.getStage(); + expect(typeof Stages.createStage).toBe('function'); + Stages.createStage({ + id: 'stage1', + type: 'stage', + }); + Stages.createStage({ + type: 'stage', + }); + }); + + it('add: type stage - id', () => { + panes.add({ + id: 'trunk', + name: 'trunk', + type: 'stage', + }); + }); + + it('add: type widget', () => { + panes.add({ + name: 'trunk', + type: 'widget', + }); + }); + + it('add: type null', () => { + panes.add({ + name: 'trunk', + }); + + const { toolbar } = panes; + expect(typeof toolbar.setContents).toBe('function'); + toolbar.setContents(); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/symbols.test.ts b/packages/editor-preset-vision/tests/master/symbols.test.ts new file mode 100644 index 000000000..35463ad8a --- /dev/null +++ b/packages/editor-preset-vision/tests/master/symbols.test.ts @@ -0,0 +1,15 @@ +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 symbols from '../../src/symbols'; + +describe('symbols 测试', () => { + it('API', () => { + symbols.create('abc'); + symbols.create('abc'); + symbols.get('abc'); + }); +}); diff --git a/packages/editor-preset-vision/tests/master/viewport.test.ts b/packages/editor-preset-vision/tests/master/viewport.test.ts new file mode 100644 index 000000000..2fb61c05a --- /dev/null +++ b/packages/editor-preset-vision/tests/master/viewport.test.ts @@ -0,0 +1,189 @@ +import '../fixtures/window'; +import { Editor, globalContext } from '@ali/lowcode-editor-core'; +import { editor } from '../../src/editor'; +import { Viewport } from '../../src/viewport'; +import domready from 'domready'; + +// const editor = globalContext.get(Editor); + +jest.mock('domready', () => { + return (fn) => fn(); +}); + +// 貌似 jsdom 没有响应 fullscreen 变更事件,先这么 mock 吧 +const mockSetFullscreen = flag => { document.fullscreen = flag; }; + +describe('viewport 测试', () => { + mockSetFullscreen(true); + + it('getDevice / setDevice / getViewport / onDeviceChange / onViewportChange', async () => { + const viewport = new Viewport(); + const mockDeviceChange = jest.fn(); + const mockViewportChange = jest.fn(); + const offDevice = viewport.onDeviceChange(mockDeviceChange); + const offViewport = viewport.onViewportChange(mockViewportChange); + expect(viewport.getDevice()).toBe('pc'); + expect(viewport.getViewport()).toBe('design-pc'); + editor.set('currentDocument', { simulator: { set() {} } }); + + await viewport.setDevice('mobile'); + expect(viewport.getDevice()).toBe('mobile'); + expect(viewport.getViewport()).toBe('design-mobile'); + expect(mockDeviceChange).toHaveBeenCalledTimes(1); + expect(mockViewportChange).toHaveBeenCalledTimes(1); + + offDevice(); + offViewport(); + await viewport.setDevice('pc'); + expect(mockDeviceChange).toHaveBeenCalledTimes(1); + expect(mockViewportChange).toHaveBeenCalledTimes(1); + }); + + it('setPreview / isPreview / togglePreivew / getViewport / onViewportChange', () => { + const viewport = new Viewport(); + const mockViewportChange = jest.fn(); + const mockPreivewChange = jest.fn(); + const off = viewport.onViewportChange(mockViewportChange); + const offPreview = viewport.onPreview(mockPreivewChange); + viewport.setPreview(true); + expect(viewport.isPreview).toBeTruthy; + expect(viewport.getViewport()).toBe('preview-pc'); + expect(mockViewportChange).toHaveBeenCalledTimes(1); + expect(mockPreivewChange).toHaveBeenCalledTimes(1); + viewport.setPreview(false); + expect(viewport.isPreview).toBeFalsy; + expect(viewport.getViewport()).toBe('design-pc'); + expect(mockViewportChange).toHaveBeenCalledTimes(2); + expect(mockPreivewChange).toHaveBeenCalledTimes(2); + viewport.togglePreview(); + expect(viewport.getViewport()).toBe('preview-pc'); + expect(mockViewportChange).toHaveBeenCalledTimes(3); + expect(mockPreivewChange).toHaveBeenCalledTimes(3); + viewport.togglePreview(); + expect(viewport.getViewport()).toBe('design-pc'); + expect(mockViewportChange).toHaveBeenCalledTimes(4); + expect(mockPreivewChange).toHaveBeenCalledTimes(4); + + off(); + offPreview(); + viewport.togglePreview(); + expect(mockViewportChange).toHaveBeenCalledTimes(4); + expect(mockPreivewChange).toHaveBeenCalledTimes(4); + }); + + it('setFocusTarget / returnFocus / setFocus / isFocus / onFocusChange', () => { + const viewport = new Viewport(); + const mockFocusChange = jest.fn(); + const off = viewport.onFocusChange(mockFocusChange); + viewport.setFocusTarget(document.createElement('div')); + viewport.returnFocus(); + + viewport.setFocus(true); + expect(viewport.isFocus()).toBeTruthy(); + expect(mockFocusChange).toHaveBeenCalledTimes(1); + expect(mockFocusChange).toHaveBeenLastCalledWith(true); + viewport.setFocus(false); + expect(viewport.isFocus()).toBeFalsy(); + expect(mockFocusChange).toHaveBeenCalledTimes(2); + expect(mockFocusChange).toHaveBeenLastCalledWith(false); + + off(); + viewport.setFocus(false); + expect(mockFocusChange).toHaveBeenCalledTimes(2); + }); + + it('isFullscreen / toggleFullscreen / setFullscreen / onFullscreenChange', () => { + const viewport = new Viewport(); + const mockFullscreenChange = jest.fn(); + const off = viewport.onFullscreenChange(mockFullscreenChange); + + mockSetFullscreen(false); + viewport.setFullscreen(true); + mockSetFullscreen(true); + expect(viewport.isFullscreen()).toBeTruthy; + // expect(mockFullscreenChange).toHaveBeenCalledTimes(1); + viewport.setFullscreen(true); + // expect(mockFullscreenChange).toHaveBeenCalledTimes(1); + + mockSetFullscreen(true); + viewport.setFullscreen(false); + mockSetFullscreen(false); + expect(viewport.isFullscreen()).toBeFalsy; + // expect(mockFullscreenChange).toHaveBeenCalledTimes(2); + viewport.setFullscreen(false); + // expect(mockFullscreenChange).toHaveBeenCalledTimes(2); + + mockSetFullscreen(true); + viewport.toggleFullscreen(); + mockSetFullscreen(false); + // expect(mockFullscreenChange).toHaveBeenCalledTimes(3); + viewport.toggleFullscreen(); + // expect(mockFullscreenChange).toHaveBeenCalledTimes(4); + + off(); + viewport.toggleFullscreen(); + // expect(mockFullscreenChange).toHaveBeenCalledTimes(4); + }); + + it('setWithShell', () => { + const viewport = new Viewport(); + viewport.setWithShell(); + }); + + it('onSlateFixedChange', () => { + const viewport = new Viewport(); + const mockSlateFixedChange = jest.fn(); + const off = viewport.onSlateFixedChange(mockSlateFixedChange); + + viewport.emitter.emit('slatefixed'); + expect(mockSlateFixedChange).toHaveBeenCalledTimes(1); + off(); + viewport.emitter.emit('slatefixed'); + expect(mockSlateFixedChange).toHaveBeenCalledTimes(1); + }); + + it('setGlobalCSS', () => { + const viewport = new Viewport(); + viewport.setGlobalCSS([{ + media: '*', + type: 'URL', + content: '//path/to.css', + }, { + media: 'ALL', + type: 'text', + content: 'body {font-size: 50px;}', + }, { + media: '', + type: 'text', + content: 'body {font-size: 50px;}', + }, { + media: 'mobile', + type: 'text', + content: 'body {font-size: 50px;}', + }]); + + viewport.cssResourceSet[0].apply(); + viewport.cssResourceSet[0].init(); + viewport.cssResourceSet[1].apply(); + viewport.cssResourceSet[1].apply(); + viewport.cssResourceSet[1].unmount(); + + viewport.setGlobalCSS([{ + media: '*', + type: 'URL', + content: '//path/to.css', + }, { + media: 'ALL', + type: 'text', + content: 'body {font-size: 50px;}', + }, { + media: '', + type: 'text', + content: 'body {font-size: 50px;}', + }, { + media: 'mobile', + type: 'text', + content: 'body {font-size: 50px;}', + }]); + }); +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/downgrade-schema.test.ts b/packages/editor-preset-vision/tests/props-reducers/downgrade-schema.test.ts new file mode 100644 index 000000000..ac7f3f45e --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/downgrade-schema.test.ts @@ -0,0 +1,96 @@ +import '../fixtures/window'; +import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer'; +import { Editor } from '@ali/lowcode-editor-core'; +import { + compatibleReducer, +} from '../../src/props-reducers/downgrade-schema-reducer'; +import formSchema from '../fixtures/schema/form'; + +describe('compatibleReducer 测试', () => { + it('compatibleReducer 测试', () => { + const downgradedProps = { + a: { + type: 'JSBlock', + value: { + componentName: 'Slot', + props: { + slotTitle: '标题', + slotName: 'title', + }, + children: [], + }, + }, + c: { + c1: { + type: 'JSBlock', + value: { + componentName: 'Slot', + props: { + slotTitle: '标题', + slotName: 'title', + }, + }, + }, + }, + d: { + type: 'variable', + variable: 'state.a', + value: '111', + }, + e: { + e1: { + type: 'variable', + variable: 'state.b', + value: '222', + }, + e2: { + type: 'JSExpression', + value: 'state.b', + mock: '222', + events: {}, + }, + }, + }; + + expect(compatibleReducer({ + a: { + type: 'JSSlot', + title: '标题', + name: 'title', + value: [], + }, + c: { + c1: { + type: 'JSSlot', + title: '标题', + name: 'title', + value: undefined, + }, + }, + d: { + type: 'JSExpression', + value: 'state.a', + mock: '111', + }, + e: { + e1: { + type: 'JSExpression', + value: 'state.b', + mock: '222', + }, + e2: { + type: 'JSExpression', + value: 'state.b', + mock: '222', + events: {}, + }, + }, + })).toEqual(downgradedProps); + }); + + it('空值', () => { + expect(compatibleReducer(null)).toBeNull; + expect(compatibleReducer(undefined)).toBeUndefined; + expect(compatibleReducer(111)).toBe(111); + }); +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/filter.test.ts b/packages/editor-preset-vision/tests/props-reducers/filter.test.ts new file mode 100644 index 000000000..691cfc086 --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/filter.test.ts @@ -0,0 +1,81 @@ +import '../fixtures/window'; +import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer'; +import { Editor } from '@ali/lowcode-editor-core'; +import { filterReducer } from '../../src/props-reducers/filter-reducer'; +import formSchema from '../fixtures/schema/form'; + +describe('filterReducer 测试', () => { + it('filterReducer 测试 - 有 filters', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + filters: [ + { + name: 'shouldBeFitlered', + filter: () => false, + }, + { + name: 'keeped', + filter: () => true, + }, + { + name: 'throwErr', + filter: () => { throw new Error('xxx'); }, + }, + { + name: 'zzz', + filter: () => true, + }, + ], + }, + }; + }, + }, + settingEntry: { + getProp(propName) { + return { name: propName }; + }, + }, + }; + expect(filterReducer({ + shouldBeFitlered: 111, + keeped: 222, + noCorresponingFilter: 222, + throwErr: 111, + }, mockNode)).toEqual({ + keeped: 222, + noCorresponingFilter: 222, + throwErr: 111, + }); + }); + + it('filterReducer 测试 - 无 filters', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + filters: [], + }, + }; + }, + }, + settingEntry: { + getProp(propName) { + return { name: propName }; + }, + }, + }; + expect(filterReducer({ + shouldBeFitlered: 111, + keeped: 222, + noCorresponingFilter: 222, + }, mockNode)).toEqual({ + shouldBeFitlered: 111, + keeped: 222, + noCorresponingFilter: 222, + }); + }); +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/init-node.test.ts b/packages/editor-preset-vision/tests/props-reducers/init-node.test.ts new file mode 100644 index 000000000..874576a70 --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/init-node.test.ts @@ -0,0 +1,488 @@ +import '../fixtures/window'; +import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer'; +import { Editor, globalContext } from '@ali/lowcode-editor-core'; +import { initNodeReducer } from '../../src/props-reducers/init-node-reducer'; +import formSchema from '../fixtures/schema/form'; + +describe('initNodeReducer 测试', () => { + it('initNodeReducer 测试 - 有 initials', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + initials: [ + { + name: 'propA', + initial: () => '111', + }, + { + name: 'propB', + initial: () => '111', + }, + { + name: 'propC', + initial: () => { + throw new Error('111'); + }, + }, + { + name: 'propD', + initial: () => '111', + }, + { + name: 'propE', + initial: () => '111', + }, + { + name: 'propF', + initial: () => '111', + }, + ], + }, + }; + }, + prototype: { + options: { + configure: [ + { + name: 'propF', + setter: { + type: { + displayName: 'I18nSetter', + }, + }, + }, + ], + }, + }, + }, + settingEntry: { + getProp(propName) { + return { name: propName }; + }, + }, + props: { + has() { + return false; + }, + add() {}, + }, + }; + expect( + initNodeReducer( + { + propA: '111', + propC: '222', + propD: { + type: 'JSExpression', + mock: '111', + }, + propE: { + type: 'variable', + value: '111', + }, + }, + mockNode, + ), + ).toEqual({ + propA: '111', + propB: '111', + propC: '222', + propD: { + type: 'JSExpression', + mock: '111', + }, + propE: { + type: 'variable', + value: '111', + }, + propF: { + type: 'i18n', + use: 'zh_CN', + zh_CN: '111', + }, + }); + }); + + it('filterReducer 测试 - 无 initials', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: {}, + }; + }, + }, + settingEntry: { + getProp(propName) { + return { name: propName }; + }, + }, + }; + expect( + initNodeReducer( + { + propA: 111, + }, + mockNode, + ), + ).toEqual({ + propA: 111, + }); + }); + + describe('i18n', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + initials: [ + { + name: 'propF', + initial: () => 111, + }, + ], + }, + }; + }, + }, + prototype: { + options: { + configure: [ + { + name: 'propF', + setter: { + type: { + displayName: 'I18nSetter', + }, + }, + }, + ], + }, + }, + props: { + has() { + return false; + }, + add() {}, + }, + }; + + it('isI18NObject(ov): true', () => { + expect( + initNodeReducer( + { + propF: { + type: 'i18n', + zh_CN: '222', + }, + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'i18n', + zh_CN: '222', + }, + }); + }); + + it('isJSExpression(ov): true', () => { + expect( + initNodeReducer( + { + propF: { + type: 'JSExpression', + value: 'state.a', + }, + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'JSExpression', + value: 'state.a', + }, + }); + }); + + it('isJSBlock(ov): true', () => { + expect( + initNodeReducer( + { + propF: { + type: 'JSBlock', + value: 'state.a', + }, + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'JSBlock', + value: 'state.a', + }, + }); + }); + + it('isJSSlot(ov): true', () => { + expect( + initNodeReducer( + { + propF: { + type: 'JSSlot', + value: 'state.a', + }, + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'JSSlot', + value: 'state.a', + }, + }); + }); + + it('isVariable(ov): true', () => { + expect( + initNodeReducer( + { + propF: { + type: 'variable', + value: 'state.a', + }, + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'variable', + value: 'state.a', + }, + }); + }); + + it('isI18NObject(v): false', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + initials: [ + { + name: 'propF', + initial: () => 111, + }, + ], + }, + }; + }, + prototype: { + options: { + configure: [ + { + name: 'propF', + setter: { + type: { + displayName: 'I18nSetter', + }, + }, + }, + ], + }, + }, + }, + props: { + has() { + return false; + }, + add() {}, + }, + }; + expect( + initNodeReducer( + { + propF: { + type: 'variable', + value: 'state.a', + }, + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'variable', + value: 'state.a', + }, + }); + }); + + it('isI18NObject(v): false', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + initials: [{ + name: 'propF', + initial: () => 111, + }], + }, + }; + }, + }, + prototype: { + options: { + configure: [ + { + name: 'propF', + setter: { + type: { + displayName: 'I18nSetter', + }, + }, + }, + ], + }, + }, + props: { + has() { + return false; + }, + add() {}, + }, + }; + expect( + initNodeReducer( + { + propF: { + type: 'variable', + value: 'state.a', + }, + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'variable', + value: 'state.a', + }, + }); + }); + }); + + it('成功使用兼容后的 i18n 对象', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + initials: [{ + name: 'propF', + initial: () => { + return { + type: 'i18n', + use: 'zh_CN', + zh_CN: '111', + }; + }, + }], + }, + }; + }, + prototype: { + options: { + configure: [ + { + name: 'propF', + setter: { + type: { + displayName: 'I18nSetter', + }, + }, + }, + ], + }, + }, + }, + props: { + has() { + return false; + }, + add() {}, + }, + }; + expect( + initNodeReducer( + { + propF: '111', + }, + mockNode, + ), + ).toEqual({ + propF: { + type: 'i18n', + use: 'zh_CN', + zh_CN: '111', + }, + }); + }); + + describe('fieldId', () => { + const mockNode = { + componentMeta: { + getMetadata() { + return { + experimental: { + initials: [ + { + name: 'propA', + initial: () => '111', + }, + ], + }, + }; + }, + }, + settingEntry: { + getProp(propName) { + return { name: propName }; + }, + }, + props: { + has() { + return false; + }, + add() {}, + }, + }; + const editor = new Editor(); + globalContext.register(editor, Editor); + const designer = new Designer({ editor }); + editor.set('designer', designer); + designer.project.open(formSchema); + it('fieldId - 已存在', () => { + expect(initNodeReducer({ + propA: '111', + fieldId: 'form', + }, mockNode)).toEqual({ + propA: '111', + fieldId: undefined, + }); + }); + + it('fieldId - 已存在,但有全局关闭标识', () => { + window.__disable_unique_id_checker__ = true; + expect(initNodeReducer({ + propA: '111', + fieldId: 'form', + }, mockNode)).toEqual({ + propA: '111', + fieldId: 'form', + }); + }); + }); +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/live-lifecycle.test.ts b/packages/editor-preset-vision/tests/props-reducers/live-lifecycle.test.ts new file mode 100644 index 000000000..cd396da5e --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/live-lifecycle.test.ts @@ -0,0 +1,78 @@ +import '../fixtures/window'; +import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer'; +import { Editor, globalContext } from '@ali/lowcode-editor-core'; +import { liveLifecycleReducer } from '../../src/props-reducers/live-lifecycle-reducer'; +import formSchema from '../fixtures/schema/form'; + +const editor = new Editor(); +globalContext.register(editor, Editor); + +it('liveLifecycleReducer 测试 - live', () => { + const mockDidMount = jest.fn(); + const mockWillUnmount = jest.fn(); + editor.set('designMode', 'live'); + const newProps = liveLifecycleReducer( + { + lifeCycles: { + didMount: mockDidMount, + willUnmount: mockWillUnmount, + }, + }, + { + isRoot() { + return true; + }, + }, + ); + + const { lifeCycles } = newProps; + expect(typeof lifeCycles.componentDidMount).toBe('function'); + expect(typeof lifeCycles.componentWillUnMount).toBe('function'); + + lifeCycles.didMount(); + lifeCycles.willUnmount(); + + expect(mockDidMount).toHaveBeenCalled(); + expect(mockWillUnmount).toHaveBeenCalled(); +}); + +it('liveLifecycleReducer 测试 - design', () => { + const mockDidMount = jest.fn(); + const mockWillUnmount = jest.fn(); + editor.set('designMode', 'design'); + const newProps = liveLifecycleReducer( + { + lifeCycles: { + didMount: mockDidMount, + willUnmount: mockWillUnmount, + }, + }, + { + isRoot() { + return true; + }, + }, + ); + + const { lifeCycles } = newProps; + expect(lifeCycles).toEqual({}); +}); + +it('liveLifecycleReducer 测试', () => { + const mockDidMount = jest.fn(); + const mockWillUnmount = jest.fn(); + editor.set('designMode', 'design'); + const newProps = liveLifecycleReducer( + { + propA: '111', + }, + { + isRoot() { + return true; + }, + }, + ); + + const { lifeCycles } = newProps; + expect(lifeCycles).toBeUndefined; +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/node-top-fixed.test.ts b/packages/editor-preset-vision/tests/props-reducers/node-top-fixed.test.ts new file mode 100644 index 000000000..b1becca60 --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/node-top-fixed.test.ts @@ -0,0 +1,28 @@ +import '../fixtures/window'; +import { nodeTopFixedReducer } from '../../src/props-reducers/node-top-fixed-reducer'; +import formSchema from '../fixtures/schema/form'; + +it('nodeTopFixedReducer 测试', () => { + expect( + nodeTopFixedReducer( + { + propA: '111', + }, + { componentMeta: { isTopFixed: true } }, + ), + ).toEqual({ + propA: '111', + __isTopFixed__: true, + }); + + expect( + nodeTopFixedReducer( + { + propA: '111', + }, + { componentMeta: { } }, + ), + ).toEqual({ + propA: '111', + }); +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/remove-empty-prop.test.ts b/packages/editor-preset-vision/tests/props-reducers/remove-empty-prop.test.ts new file mode 100644 index 000000000..2e5fb8627 --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/remove-empty-prop.test.ts @@ -0,0 +1,62 @@ +import '../fixtures/window'; +import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer'; +import { Editor, globalContext } from '@ali/lowcode-editor-core'; +import { removeEmptyPropsReducer } from '../../src/props-reducers/remove-empty-prop-reducer'; +import formSchema from '../fixtures/schema/form'; + +it('removeEmptyPropsReducer 测试', () => { + const newProps = removeEmptyPropsReducer( + { + propA: '111', + dataSource: { + online: [ + { + options: { + params: [ + { + name: 'propA', + value: '111', + }, + { + value: '111', + }, + ], + }, + }, + ], + }, + }, + { + isRoot() { + return true; + }, + }, + ); + + expect(newProps).toEqual({ + propA: '111', + dataSource: { + online: [ + { + options: { + params: [{ + name: 'propA', + value: '111', + }, { + value: '111', + }], + }, + }, + ], + list: [ + { + options: { + params: { + propA: '111', + }, + }, + }, + ], + }, + }); +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/style-props.test.ts b/packages/editor-preset-vision/tests/props-reducers/style-props.test.ts new file mode 100644 index 000000000..d79fbac75 --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/style-props.test.ts @@ -0,0 +1,121 @@ +import '../fixtures/window'; +import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer'; +import { Editor, globalContext } from '@ali/lowcode-editor-core'; +import { stylePropsReducer } from '../../src/props-reducers/style-reducer'; +import formSchema from '../fixtures/schema/form'; + +const editor: Editor = new Editor(); +globalContext.register(editor, Editor); + +beforeEach(() => { + // const designer = new Designer({ editor }); + editor.set('designer', { + currentDocument: { + simulator: { + contentDocument: document, + }, + }, + }); +}); +// designer.project.open(formSchema); + +describe('stylePropsReducer 测试', () => { + it('无 style 相关属性', () => { + expect(stylePropsReducer({ propA: 1 })).toEqual({ propA: 1 }); + }); + + it('__style__', () => { + const props = { + __style__: { + 'font-size': '50px', + }, + }; + const mockNode = { id: 'id1' }; + expect(stylePropsReducer(props, mockNode)).toEqual({ + className: '_css_pesudo_id1', + __style__: { + 'font-size': '50px', + }, + }); + expect(document.querySelector('#_style_pesudo_id1')).textContent = + '._css_pesudo_id1 { font-size: 50px; }'; + }); + + it('__style__ - 无 contentDocument', () => { + editor.set('designer', { + currentDocument: { + simulator: { + contentDocument: undefined, + }, + }, + }); + const props = { + __style__: { + 'font-size': '50px', + }, + }; + const mockNode = { id: 'id11' }; + expect(stylePropsReducer(props, mockNode)).toEqual({ + __style__: { + 'font-size': '50px', + }, + }); + expect(document.querySelector('#_style_pesudo_id11')).toBeNull; + }); + + it('__style__ - css id 已存在', () => { + const s = document.createElement('style'); + s.setAttribute('type', 'text/css'); + s.setAttribute('id', '_style_pesudo_id2'); + document.getElementsByTagName('head')[0].appendChild(s); + s.appendChild(document.createTextNode('body {}')); + const props = { + __style__: { + 'font-size': '50px', + }, + }; + const mockNode = { id: 'id2' }; + expect(stylePropsReducer(props, mockNode)).toEqual({ + className: '_css_pesudo_id2', + __style__: { + 'font-size': '50px', + }, + }); + expect(document.querySelector('#_style_pesudo_id2')).textContent = + '._css_pesudo_id2 { font-size: 50px; }'; + }); + + it('containerStyle', () => { + const props = { + containerStyle: { + 'font-size': '50px', + }, + }; + const mockNode = { id: 'id3' }; + expect(stylePropsReducer(props, mockNode)).toEqual({ + className: '_css_pesudo_id3', + containerStyle: { + 'font-size': '50px', + }, + }); + expect(document.querySelector('#_style_pesudo_id3')).textContent = + '._css_pesudo_id3 { font-size: 50px; }'; + }); + + it('pageStyle', () => { + const props = { + pageStyle: { + 'font-size': '50rpx', + }, + }; + const mockNode = { id: 'id4' }; + expect(stylePropsReducer(props, mockNode)).toEqual({ + className: 'engine-document', + pageStyle: { + 'font-size': '50rpx', + }, + }); + expect(document.querySelector('#_style_pesudo_id4')).textContent = + '._css_pesudo_id4 { font-size: 50px; }'; + }); +}); diff --git a/packages/editor-preset-vision/tests/props-reducers/upgrade-schema.test copy.ts b/packages/editor-preset-vision/tests/props-reducers/upgrade-schema.test copy.ts new file mode 100644 index 000000000..b16c9b127 --- /dev/null +++ b/packages/editor-preset-vision/tests/props-reducers/upgrade-schema.test copy.ts @@ -0,0 +1,107 @@ +import '../fixtures/window'; +import { Node, Designer, getConvertedExtraKey } from '@ali/lowcode-designer'; +import { Editor } from '@ali/lowcode-editor-core'; +import { + upgradePropsReducer, + upgradePageLifeCyclesReducer, +} from '../../src/props-reducers/upgrade-reducer'; +import formSchema from '../fixtures/schema/form'; + +describe('upgradePropsReducer 测试', () => { + it('upgradePropsReducer 测试', () => { + const props = { + a: { + type: 'JSBlock', + value: { + componentName: 'Slot', + props: { + slotTitle: '标题', + slotName: 'title', + }, + children: [], + }, + }, + b: { + type: 'JSBlock', + value: { + componentName: 'Div', + props: {}, + }, + }, + c: { + c1: { + type: 'JSBlock', + value: { + componentName: 'Slot', + props: { + slotTitle: '标题', + slotName: 'title', + }, + }, + }, + }, + d: { + type: 'variable', + variable: 'state.a', + value: '111', + }, + __slot__haha: true, + }; + + expect(upgradePropsReducer(props)).toEqual({ + a: { + type: 'JSSlot', + title: '标题', + name: 'title', + value: [], + }, + b: { + componentName: 'Div', + props: {}, + }, + c: { + c1: { + type: 'JSSlot', + title: '标题', + name: 'title', + value: undefined, + }, + }, + d: { + type: 'JSExpression', + value: 'state.a', + mock: '111', + }, + }); + }); + + it('空值', () => { + expect(upgradePropsReducer(null)).toBeNull; + expect(upgradePropsReducer(undefined)).toBeUndefined; + }); +}); + +const editor = new Editor(); +const designer = new Designer({ editor }); +designer.project.open(formSchema); + +it('upgradePageLifeCyclesReducer 测试', () => { + const rootNode = designer.currentDocument?.rootNode; + const mockDidMount = jest.fn(); + const mockWillUnmount = jest.fn(); + upgradePageLifeCyclesReducer({ + didMount: mockDidMount, + willUnmount: mockWillUnmount, + }, rootNode); + + const lifeCycles = rootNode?.getPropValue(getConvertedExtraKey('lifeCycles')); + + expect(typeof lifeCycles.didMount).toBe('function'); + expect(typeof lifeCycles.willUnmount).toBe('function'); + + lifeCycles.didMount(); + lifeCycles.willUnmount(); + + expect(mockDidMount).toHaveBeenCalled(); + expect(mockWillUnmount).toHaveBeenCalled(); +}); diff --git a/packages/editor-preset-vision/tests/utils/index.ts b/packages/editor-preset-vision/tests/utils/index.ts new file mode 100644 index 000000000..70fce0af2 --- /dev/null +++ b/packages/editor-preset-vision/tests/utils/index.ts @@ -0,0 +1 @@ +export { getIdsFromSchema, getNodeFromSchemaById } from '@ali/lowcode-test-mate/es/utils'; diff --git a/packages/editor-preset-vision/tests/vision-api/api-export.test.ts b/packages/editor-preset-vision/tests/vision-api/api-export.test.ts new file mode 100644 index 000000000..8da131447 --- /dev/null +++ b/packages/editor-preset-vision/tests/vision-api/api-export.test.ts @@ -0,0 +1,87 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +// import { Project } from '../../src/project/project'; +import formSchema from '../fixtures/schema/form'; +import VisualEngine, { + designer, + editor, + skeleton, + /** + * VE.Popup + */ + Popup, + /** + * VE Utils + */ + utils, + I18nUtil, + Hotkey, + Env, + monitor, + /* pub/sub 集线器 */ + Bus, + /* 事件 */ + EVENTS, + /* 修饰方法 */ + HOOKS, + Exchange, + context, + /** + * VE.init + * + * Initialized the whole VisualEngine UI + */ + init, + ui, + Panes, + modules, + Trunk, + Prototype, + Bundle, + Pages, + DragEngine, + Viewport, + Version, + Project, + logger, + Symbols, +} from '../../src'; +import { Editor } from '@ali/lowcode-editor-core'; + +describe('API 多种导出场景测试', () => { + it('window.VisualEngine 和 npm 导出 API 测试', () => { + expect(VisualEngine).toBe(window.VisualEngine); + }); + + it('npm 导出 API 对比测试', () => { + expect(VisualEngine.designer).toBe(designer); + expect(VisualEngine.editor).toBe(editor); + expect(VisualEngine.skeleton).toBe(skeleton); + expect(VisualEngine.Popup).toBe(Popup); + expect(VisualEngine.utils).toBe(utils); + expect(VisualEngine.I18nUtil).toBe(I18nUtil); + expect(VisualEngine.Hotkey).toBe(Hotkey); + expect(VisualEngine.Env).toBe(Env); + expect(VisualEngine.monitor).toBe(monitor); + expect(VisualEngine.Bus).toBe(Bus); + expect(VisualEngine.EVENTS).toBe(EVENTS); + expect(VisualEngine.HOOKS).toBe(HOOKS); + expect(VisualEngine.Exchange).toBe(Exchange); + expect(VisualEngine.context).toBe(context); + expect(VisualEngine.init).toBe(init); + expect(VisualEngine.ui).toBe(ui); + expect(VisualEngine.Panes).toBe(Panes); + expect(VisualEngine.modules).toBe(modules); + expect(VisualEngine.Trunk).toBe(Trunk); + expect(VisualEngine.Prototype).toBe(Prototype); + expect(VisualEngine.Bundle).toBe(Bundle); + expect(VisualEngine.DragEngine).toBe(DragEngine); + expect(VisualEngine.Pages).toBe(Pages); + expect(VisualEngine.Viewport).toBe(Viewport); + expect(VisualEngine.Version).toBe(Version); + expect(VisualEngine.Project).toBe(Project); + expect(VisualEngine.logger).toBe(logger); + expect(VisualEngine.Symbols).toBe(Symbols); + }); +}); \ No newline at end of file diff --git a/packages/editor-preset-vision/tests/vision-api/exchange.test.ts b/packages/editor-preset-vision/tests/vision-api/exchange.test.ts new file mode 100644 index 000000000..16c93b313 --- /dev/null +++ b/packages/editor-preset-vision/tests/vision-api/exchange.test.ts @@ -0,0 +1,23 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import formSchema from '../fixtures/schema/form'; +import VisualEngine from '../../src'; + +describe('VisualEngine.Exchange 相关 API 测试', () => { + it('select / getSelected', () => { + const doc = VisualEngine.Pages.addPage(formSchema); + VisualEngine.Exchange.select(doc?.getNode('form')); + expect(VisualEngine.Exchange.getSelected()?.componentName).toBe('Form'); + expect(VisualEngine.Exchange.getSelected()?.id).toBe('form'); + + // clear selection + VisualEngine.Exchange.select(); + expect(VisualEngine.Exchange.getSelected()).toBeUndefined; + }); + + it('onIntoView', () => { + expect(typeof VisualEngine.Exchange.onIntoView).toBe('function'); + VisualEngine.Exchange.onIntoView(); + }); +}); diff --git a/packages/editor-preset-vision/tests/vision-api/pages.test.ts b/packages/editor-preset-vision/tests/vision-api/pages.test.ts new file mode 100644 index 000000000..87b04db0a --- /dev/null +++ b/packages/editor-preset-vision/tests/vision-api/pages.test.ts @@ -0,0 +1,170 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import formSchema from '../fixtures/schema/form'; +import VisualEngine, { Prototype } from '../../src'; +import { Editor } from '@ali/lowcode-editor-core'; +import { getIdsFromSchema, getNodeFromSchemaById } from '../utils'; +import divPrototypeConfig from '../fixtures/prototype/div-vision'; + +const pageSchema = { componentsTree: [formSchema] }; + +describe('VisualEngine.Pages 相关 API 测试', () => { + afterEach(() => { + VisualEngine.Pages.unload(); + }); + describe('addPage 系列', () => { + it('基本的节点模型初始化,初始化传入 schema', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + expect(doc.nodesMap.size).toBe(expectedNodeCnt); + }); + it('基本的节点模型初始化,初始化传入 schema,带有 slot', () => { + const formSchemaWithSlot = set(cloneDeep(formSchema), 'children[0].children[0].props.title', { + type: 'JSBlock', + value: { + componentName: 'Slot', + children: [ + { + componentName: 'Text', + id: 'node_k1ow3cbf', + props: { + showTitle: false, + behavior: 'NORMAL', + content: { + type: 'variable', + value: { + use: 'zh_CN', + en_US: 'Title', + zh_CN: '个人信息', + type: 'i18n', + }, + variable: 'state.title', + }, + __style__: {}, + fieldId: 'text_k1ow3h1j', + maxLine: 0, + }, + condition: true, + }, + ], + props: { + slotTitle: '标题区域', + slotName: 'title', + }, + }, + }); + const doc = VisualEngine.Pages.addPage({ componentsTree: [formSchemaWithSlot] })!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + // slot 会多出(1 + N)个节点 + expect(doc.nodesMap.size).toBe(expectedNodeCnt + 2); + }); + it('基本的节点模型初始化,初始化传入 schema,构造 prototype', () => { + const proto = new Prototype(divPrototypeConfig); + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + expect(doc.nodesMap.size).toBe(expectedNodeCnt); + }); + it('导出 schema', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + const exportedData = doc.toData(); + expect(exportedData).toHaveProperty('componentsMap'); + expect(exportedData).toHaveProperty('componentsTree'); + expect(exportedData.componentsTree).toHaveLength(1); + const exportedSchema = exportedData.componentsTree[0]; + expect(getIdsFromSchema(exportedSchema).length).toBe(expectedNodeCnt); + }); + }); + describe('removePage 系列', () => { + it('removePage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + expect(VisualEngine.Pages.documents).toHaveLength(1); + VisualEngine.Pages.removePage(doc); + expect(VisualEngine.Pages.documents).toHaveLength(0); + }); + }); + describe('getPage 系列', () => { + it('getPage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + expect(VisualEngine.Pages.getPage(0)).toBe(doc); + expect(VisualEngine.Pages.getPage((_doc) => _doc.rootNode.id === 'page')).toBe(doc2); + }); + }); + describe('setPages 系列', () => { + it('setPages componentsTree 只有一个元素', () => { + VisualEngine.Pages.setPages([pageSchema]); + const { currentDocument } = VisualEngine.Pages; + const ids = getIdsFromSchema(formSchema); + const expectedNodeCnt = ids.length; + const exportedData = currentDocument.toData(); + expect(exportedData).toHaveProperty('componentsMap'); + expect(exportedData).toHaveProperty('componentsTree'); + expect(exportedData.componentsTree).toHaveLength(1); + const exportedSchema = exportedData.componentsTree[0]; + expect(getIdsFromSchema(exportedSchema).length).toBe(expectedNodeCnt); + }); + }); + describe('setCurrentPage / getCurrentPage / currentPage / currentDocument 系列', () => { + it('getCurrentPage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema)!; + expect(doc).toBeTruthy(); + expect(doc).toBe(VisualEngine.Pages.getCurrentPage()); + expect(doc).toBe(VisualEngine.Pages.currentDocument); + expect(doc).toBe(VisualEngine.Pages.currentPage); + }); + it('setCurrentPage', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + expect(doc).toBe(VisualEngine.Pages.currentDocument); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + expect(doc2).toBe(VisualEngine.Pages.currentDocument); + VisualEngine.Pages.setCurrentPage(doc); + expect(doc).toBe(VisualEngine.Pages.currentDocument); + }); + }); + describe('onCurrentPageChange 系列', () => { + it('多次切换', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + const docChangeHandler = jest.fn(); + VisualEngine.Pages.onCurrentDocumentChange(docChangeHandler); + VisualEngine.Pages.setCurrentPage(doc); + expect(docChangeHandler).toHaveBeenCalledTimes(1); + expect(docChangeHandler).toHaveBeenLastCalledWith(doc); + + VisualEngine.Pages.setCurrentPage(doc2); + expect(docChangeHandler).toHaveBeenCalledTimes(2); + expect(docChangeHandler).toHaveBeenLastCalledWith(doc2); + }); + }); + describe('toData 系列', () => { + it('基本的节点模型初始化,模型导出,初始化传入 schema', () => { + const doc = VisualEngine.Pages.addPage(pageSchema); + const anotherFormSchema = set(cloneDeep(formSchema), 'id', 'page'); + const doc2 = VisualEngine.Pages.addPage({ componentsTree: [anotherFormSchema] }); + const dataList = VisualEngine.Pages.toData(); + expect(dataList.length).toBe(2); + expect(dataList[0]).toHaveProperty('componentsMap'); + expect(dataList[0]).toHaveProperty('componentsTree'); + expect(dataList[0].componentsTree).toHaveLength(1); + expect(dataList[0].componentsTree[0].id).toBe('node_k1ow3cb9'); + expect(dataList[1]).toHaveProperty('componentsMap'); + expect(dataList[1]).toHaveProperty('componentsTree'); + expect(dataList[1].componentsTree).toHaveLength(1); + expect(dataList[1].componentsTree[0].id).toBe('page'); + }); + }); +}); diff --git a/packages/editor-preset-vision/tests/vision-api/project.test.ts b/packages/editor-preset-vision/tests/vision-api/project.test.ts new file mode 100644 index 000000000..bec071d51 --- /dev/null +++ b/packages/editor-preset-vision/tests/vision-api/project.test.ts @@ -0,0 +1,26 @@ +import set from 'lodash/set'; +import cloneDeep from 'lodash/clonedeep'; +import '../fixtures/window'; +import formSchema from '../fixtures/schema/form'; +import { Project } from '../../src'; + +describe('VisualEngine.Project 相关 API 测试', () => { + it('getSchema / setSchema 系列', () => { + Project.setSchema({ + componentsMap: {}, + componentsTree: [formSchema], + }); + expect(Project.getSchema()).toEqual({ + componentsMap: {}, + componentsTree: [formSchema], + }); + + }); + + it('setConfig', () => { + Project.setConfig({ haha: 1 }); + expect(Project.get('config')).toEqual({ + haha: 1, + }); + }); +}); diff --git a/packages/editor-setters/CHANGELOG.md b/packages/editor-setters/CHANGELOG.md new file mode 100644 index 000000000..3a70ca656 --- /dev/null +++ b/packages/editor-setters/CHANGELOG.md @@ -0,0 +1,856 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.13.1-29](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-28...v0.13.1-29) (2020-12-03) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-28](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-27...v0.13.1-28) (2020-12-03) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-26...v0.13.1-27) (2020-12-02) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-26](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-25...v0.13.1-26) (2020-12-02) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-24...v0.13.1-25) (2020-12-01) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-23...v0.13.1-24) (2020-11-26) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-22...v0.13.1-23) (2020-11-25) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-22](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-19...v0.13.1-22) (2020-11-25) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-18...v0.13.1-19) (2020-11-24) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-15...v0.13.1-18) (2020-11-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-15) (2020-11-18) + + + + +## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17) + + + + +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + + + +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + + + +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + + +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + +### Features + +* 使用 release/1.0.0 的 editor-setters ([80d74d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/80d74d6)) + + + + +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + + + +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + + + +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + + + +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + + +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + + +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + + +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + + +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + + + + +## [0.13.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-12) (2020-11-18) + + + + +## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17) + + + + +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + + + +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + + + +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + + +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + +### Features + +* 使用 release/1.0.0 的 editor-setters ([80d74d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/80d74d6)) + + + + +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + + + +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + + + +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + + + +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + + +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + + +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + + +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + + +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + + + + +## [0.13.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-10...v0.13.1-11) (2020-11-02) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-9...v0.13.1-10) (2020-10-26) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-9) (2020-10-26) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-8) (2020-10-26) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-6...v0.13.1-7) (2020-10-23) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-5...v0.13.1-6) (2020-10-22) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-4...v0.13.1-5) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-3...v0.13.1-4) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-2...v0.13.1-3) (2020-10-19) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.13.1-2) (2020-10-19) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.13.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-3...v0.13.1-1) (2020-10-12) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.12.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-9...v0.12.1-1) (2020-09-22) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [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 + +* source-editor bug & exp-setter bug ([5cd88d4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5cd88d4)) + + + + + +## [1.0.9-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-1...v1.0.9-2) (2020-09-14) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [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-setters + + +## 1.0.9-0 (2020-09-14) + + +### Bug Fixes + +* fix function-setter bug ([dced647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dced647)) +* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df)) +* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375)) +* rename MixinSetter to MixedSetter ([0e9a740](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e9a740)) +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) +* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f)) + + +### Features + +* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0)) +* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84)) +* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807)) +* tree 组件修改 ([7efa52f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7efa52f)) +* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6)) +* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7)) + + + + + +## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.21...@ali/lowcode-editor-setters@1.0.8-0) (2020-09-09) + + +### Bug Fixes + +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) +* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f)) +* fix function-setter bug ([dced647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dced647)) +* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df)) +* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375)) + + +### Features + +* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7)) +* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6)) +* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0)) +* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84)) +* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807)) +* tree 组件修改 ([7efa52f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7efa52f)) + + + + + +## [1.0.7-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.6-0...@ali/lowcode-editor-setters@1.0.7-0) (2020-09-02) + +**Note:** Version bump only for package @ali/lowcode-editor-setters + +## [1.0.6-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.19...@ali/lowcode-editor-setters@1.0.6-0) (2020-09-02) + + +## [0.9.21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.20...@ali/lowcode-editor-setters@0.9.21) (2020-09-03) + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.19...@ali/lowcode-editor-setters@0.9.20) (2020-09-03) + + + +### Bug Fixes + +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) +* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f)) +* fix function-setter bug ([dced647](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dced647)) +* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df)) +* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375)) + + +### Features + +* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7)) +* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6)) +* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0)) +* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84)) +* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807)) +* tree 组件修改 ([7efa52f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7efa52f)) + + + + + +## [1.0.5-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.4-0...@ali/lowcode-editor-setters@1.0.5-0) (2020-08-20) + + +## [0.9.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.18...@ali/lowcode-editor-setters@0.9.19) (2020-08-27) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.17...@ali/lowcode-editor-setters@0.9.18) (2020-08-24) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.16...@ali/lowcode-editor-setters@0.9.17) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [1.0.4-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.3-0...@ali/lowcode-editor-setters@1.0.4-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [1.0.3-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.2-0...@ali/lowcode-editor-setters@1.0.3-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [1.0.2-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@1.0.1-0...@ali/lowcode-editor-setters@1.0.2-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [1.0.1-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.16...@ali/lowcode-editor-setters@1.0.1-0) (2020-08-20) + + +### Bug Fixes + +* fix function-setter bug ([8fd77df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fd77df)) + + +### Features + +* add function setter ([114b6b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/114b6b0)) +* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84)) +* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807)) +* 新增functionSetter ([9359ac6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9359ac6)) + + + + + +# [1.0.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.14.0...@ali/lowcode-editor-setters@1.0.0) (2020-08-17) + +## [0.9.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.15...@ali/lowcode-editor-setters@0.9.16) (2020-08-19) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +# [0.14.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.13.0...@ali/lowcode-editor-setters@0.14.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +# [0.13.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.11.0...@ali/lowcode-editor-setters@0.13.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +# [0.12.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.11.0...@ali/lowcode-editor-setters@0.12.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +# [0.11.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.10.0...@ali/lowcode-editor-setters@0.11.0) (2020-08-16) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +# [0.10.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.14...@ali/lowcode-editor-setters@0.10.0) (2020-08-14) + + +### Features + +* add style-setters ([99b1d84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/99b1d84)) + + + + + +## [0.9.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.13...@ali/lowcode-editor-setters@0.9.14) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.11...@ali/lowcode-editor-setters@0.9.13) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.11...@ali/lowcode-editor-setters@0.9.12) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.10...@ali/lowcode-editor-setters@0.9.11) (2020-07-28) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.9...@ali/lowcode-editor-setters@0.9.10) (2020-07-22) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.8...@ali/lowcode-editor-setters@0.9.9) (2020-07-21) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.7...@ali/lowcode-editor-setters@0.9.8) (2020-07-21) + + +### Bug Fixes + +* rename MixinSetter to MixedSetter ([0e9a740](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e9a740)) + + + + + +## [0.9.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.6...@ali/lowcode-editor-setters@0.9.7) (2020-07-13) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.5...@ali/lowcode-editor-setters@0.9.6) (2020-07-12) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.4...@ali/lowcode-editor-setters@0.9.5) (2020-06-23) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.3...@ali/lowcode-editor-setters@0.9.4) (2020-06-23) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.2...@ali/lowcode-editor-setters@0.9.3) (2020-06-15) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.9.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-setters@0.9.1...@ali/lowcode-editor-setters@0.9.2) (2020-05-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## 0.9.1 (2020-05-18) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-setters + + +## [0.8.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.14...@ali/lowcode-setters@0.8.15) (2020-05-15) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.13...@ali/lowcode-setters@0.8.14) (2020-05-13) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.12...@ali/lowcode-setters@0.8.13) (2020-05-08) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.11...@ali/lowcode-setters@0.8.12) (2020-05-07) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.10...@ali/lowcode-setters@0.8.11) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.9...@ali/lowcode-setters@0.8.10) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.8...@ali/lowcode-setters@0.8.9) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.7...@ali/lowcode-setters@0.8.8) (2020-04-16) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.6...@ali/lowcode-setters@0.8.7) (2020-04-15) + + +### Features + +* mixin-setter get all setter ([eaa84d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eaa84d2)) +* mixin-setter get all setter ([a5eb62d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5eb62d)) + + + + + +## [0.8.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.5...@ali/lowcode-setters@0.8.6) (2020-03-31) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.4...@ali/lowcode-setters@0.8.5) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.3...@ali/lowcode-setters@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## [0.8.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-setters@0.8.2...@ali/lowcode-setters@0.8.3) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-setters + + +## 0.8.2 (2020-03-30) + + +### Bug Fixes + +* ts type ([1732e7d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1732e7d)) + + + + + +## 0.8.1 (2020-03-30) + + +### Bug Fixes + +* ts type ([1732e7d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1732e7d)) diff --git a/packages/editor-setters/package.json b/packages/editor-setters/package.json new file mode 100644 index 000000000..f67b65903 --- /dev/null +++ b/packages/editor-setters/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ali/lowcode-editor-setters", + "version": "0.13.1-29", + "description": "Builtin setters for Ali lowCode engine", + "files": [ + "es", + "lib" + ], + "main": "lib/index.js", + "module": "es/index.js", + "scripts": { + "build": "build-scripts build --skip-demo", + "test": "ava", + "test:snapshot": "ava --update-snapshots" + }, + "dependencies": { + "@ali/iceluna-comp-expression": "^1.0.6", + "@ali/iceluna-comp-form": "^1.0.20", + "@ali/iceluna-comp-list": "^1.0.26", + "@ali/iceluna-comp-object-button": "^1.0.23", + "@ali/iceluna-comp-react-node": "^1.0.5", + "@ali/iceluna-sdk": "^1.0.5-beta.24", + "@ali/lc-style-setter": "^0.0.1", + "@ali/lowcode-editor-core": "^0.13.1-29", + "@alifd/next": "^1.19.16", + "acorn": "^6.4.1", + "classnames": "^2.2.6", + "intl-messageformat": "^9.3.1", + "js-beautify": "^1.13.0", + "qs": "^6.9.1", + "react": "^16", + "react-dom": "^16.7.0", + "react-monaco-editor": "0.40.0" + }, + "devDependencies": { + "@alib/build-scripts": "^0.1.18", + "@types/classnames": "^2.2.7", + "@types/node": "^13.7.1", + "@types/react": "^16", + "@types/react-dom": "^16", + "build-plugin-component": "^0.2.10", + "build-plugin-fusion": "^0.1.0", + "build-plugin-moment-locales": "^0.1.0" + }, + "ava": { + "compileEnhancements": false, + "snapshotDir": "test/fixtures/__snapshots__", + "extensions": [ + "ts" + ], + "require": [ + "ts-node/register" + ] + }, + "publishConfig": { + "registry": "https://registry.npm.alibaba-inc.com" + } +} diff --git a/packages/editor-setters/src/color-setter/index.tsx b/packages/editor-setters/src/color-setter/index.tsx new file mode 100644 index 000000000..2415e8935 --- /dev/null +++ b/packages/editor-setters/src/color-setter/index.tsx @@ -0,0 +1,97 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { SketchPicker } from 'react-color'; +import { Input, Balloon } from '@alife/next'; +import './index.scss'; + +interface Color { + rgb: any; + onChange: () => void; +} + +export interface PluginProps { + value: string; + onChange: any; +} + +export default class ColorPickerView extends PureComponent { + static display = 'ColorPicker'; + + static propTypes = { + onChange: PropTypes.func, + value: PropTypes.string, + }; + + static defaultProps = { + onChange: () => {}, + value: '', + }; + + constructor(props: Readonly<{ value: string; defaultValue: string }>) { + super(props); + this.state = { + value: props.value || props.defaultValue, + }; + } + + static getDerivedStateFromProps(props: { value: string }, state: { preValue: string }) { + if (props.value != state.preValue) { + return { + preValue: props.value, + value: props.value, + }; + } + return null; + } + + onChangeComplete = (color: Color): void => { + let value; + if (color.rgb.a < 1) { + const { rgb } = color; + const rgba = [rgb.r, rgb.g, rgb.b, rgb.a]; + value = `rgba(${rgba.join(',')})`; + } else { + value = color.hex; + } + this.setState({ + value, + }); + this.props.onChange && this.props.onChange(value); + }; + + onInputChange = (value: string): void => { + if (/^[0-9a-zA-Z]{6}$/.test(value)) value = `#${ value}`; + this.setState({ + value, + }); + this.props.onChange && this.props.onChange(value); + }; + + render(): React.ReactNode { + const { value, onChange, ...restProps } = this.props; + const boxStyle = { + backgroundColor: this.state.value, + }; + const triggerNode = ( +

+
+
+ ); + const InnerBeforeNode = ( + + + + ); + return ( + + ); + } +} diff --git a/packages/editor-setters/src/events-setter/index.tsx b/packages/editor-setters/src/events-setter/index.tsx new file mode 100644 index 000000000..b0d806ecb --- /dev/null +++ b/packages/editor-setters/src/events-setter/index.tsx @@ -0,0 +1,464 @@ +import { Component } from 'react'; +import { Radio, Menu, Table, Icon } from '@alifd/next'; +import nativeEvents from './native-events'; + +import './index.scss'; + +const { Item, Group } = Menu; +const RadioGroup = Radio.Group; + +const EVENT_CONTENTS = { + COMPONENT_EVENT: 'componentEvent', + NATIVE_EVENT: 'nativeEvent', + LIFE_CYCLE_EVENT: 'lifeCycleEvent', +}; + +const DEFINITION_EVENT_TYPE = { + EVENTS: 'events', + NATIVE_EVENTS: 'nativeEvents', + LIFE_CYCLE_EVENT: 'lifeCycleEvent', +}; + +const SETTER_NAME = 'event-setter'; + +export default class EventsSetter extends Component<{ + value: any[]; + onChange: (eventList: any[]) => void; +}> { + state = { + eventBtns: [], + eventList: [], + selectType: null, + nativeEventList: [], + lifeCycleEventList: [], + eventDataList: (this.props?.value?.eventDataList ? this.props.value.eventDataList : this.props?.value) || [], + }; + + // constructor (){ + // super(); + // debugger; + // // if (!this.props || !this.props.value){ + // // this.setState({ + // // eventDataList:[] + // // }) + // // } + // } + + // static getDerivedStateFromProps(nextProps, prevState) { + // debugger; + // // const { value } = nextProps; + // // debugger; + // // if (value !== prevState.eventDataList) { + // // return { + // // value, + // // }; + // // } + // return null; + // } + + private bindEventName: string; + + componentDidMount() { + console.log(this.state.eventDataList); + + const { editor } = this.props.field; + this.initEventBtns(); + this.initEventList(); + editor.on(`${SETTER_NAME}.bindEvent`, (relatedEventName, paramStr) => { + this.bindEvent(relatedEventName, paramStr); + }); + } + + /** + * 初始化事件按钮 + */ + initEventBtns() { + const { definition } = this.props; + let isRoot = false; + let isCustom = false; + let eventBtns = []; + definition.map(item => { + if (item.type === DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT) { + isRoot = true; + } + + if (item.type === DEFINITION_EVENT_TYPE.EVENTS) { + isCustom = true; + } + + return item; + }); + + if (isRoot) { + eventBtns = [ + { + value: EVENT_CONTENTS.LIFE_CYCLE_EVENT, + label: '生命周期', + }, + ]; + } else if (isCustom) { + eventBtns = [ + { + value: EVENT_CONTENTS.COMPONENT_EVENT, + label: '组件自带事件', + }, + ]; + } else { + eventBtns = [ + { + value: EVENT_CONTENTS.NATIVE_EVENT, + label: '原生事件', + }, + ]; + } + + this.setState({ + eventBtns, + }); + } + + initEventList() { + const { definition } = this.props; + let nativeEventList = []; + definition.map(item => { + if (item.type === DEFINITION_EVENT_TYPE.EVENTS) { + this.checkEventListStatus(item.list, DEFINITION_EVENT_TYPE.EVENTS); + this.setState({ + eventList: item.list, + }); + } + + if (item.type === DEFINITION_EVENT_TYPE.NATIVE_EVENTS) { + this.checkEventListStatus( + item.list, + DEFINITION_EVENT_TYPE.NATIVE_EVENTS, + ); + nativeEventList = item.list; + } + + if (item.type === DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT) { + this.checkEventListStatus( + item.list, + DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT, + ); + this.setState({ + lifeCycleEventList: item.list, + }); + } + + return item; + }); + + if (nativeEventList.length == 0) { + nativeEventList = nativeEvents; + this.setState({ + nativeEventList, + }); + } + } + + checkEventListStatus = (eventList: any[], eventType: string) => { + const { eventDataList } = this.state; + if ( + eventType === DEFINITION_EVENT_TYPE.EVENTS || + eventType === DEFINITION_EVENT_TYPE.LIFE_CYCLE_EVENT + ) { + eventList.map(item => { + item.disabled = false; + eventDataList.map(eventDataItem => { + if (item.name === eventDataItem.name) { + item.disabled = true; + } + + return eventDataItem; + }); + + return item; + }); + } else if (eventType === DEFINITION_EVENT_TYPE.NATIVE_EVENTS) { + eventDataList.map(eventDataItem => { + eventList.map(item => { + item.eventList.map(eventItem => { + if (eventItem.name === eventDataItem.name) { + item.disabled = true; + } else { + item.disabled = false; + } + return eventItem; + }); + return item; + }); + + return eventDataItem; + }); + } + }; + + /** + * 渲染事件信息 + */ + renderEventInfoCell = (value, index, record) => { + let eventTagText = ''; + if (record.type === EVENT_CONTENTS.NATIVE_EVENT) { + eventTagText = '原'; + } else if (record.type === EVENT_CONTENTS.COMPONENT_EVENT) { + eventTagText = '组'; + } else if (record.type === EVENT_CONTENTS.LIFE_CYCLE_EVENT) { + eventTagText = '生'; + } + return ( +
+
+
{eventTagText}
+ {record.name} +
+
+ + this.onRelatedEventNameClick(record.relatedEventName)}> + {record.relatedEventName || ''} + +
+
+ ); + }; + + /** + * 渲染事件操作项 + */ + renderEventOperateCell = (eventName: string) => { + return ( +
+ this.openDialog(eventName)} + /> + this.openDeleteEventDialog(eventName)} + /> +
+ ); + }; + + updateEventListStatus = (eventName: string, unDisabled: boolean) => { + const { eventList, nativeEventList, lifeCycleEventList } = this.state; + eventList.map(item => { + if (item.name === eventName) { + item.disabled = !unDisabled; + } + return item; + }); + + lifeCycleEventList.map(item => { + if (item.name === eventName) { + item.disabled = !unDisabled; + } + return item; + }); + + nativeEventList.map(item => { + item.eventList.map(itemData => { + if (itemData.name === eventName) { + itemData.disabled = !unDisabled; + } + return itemData; + }); + + return item; + }); + }; + + onRadioChange = value => { + this.setState({ + selectType: value, + }); + }; + + + onEventMenuClick = (eventName: string) => { + const { selectType, eventDataList } = this.state; + eventDataList.push({ + type: selectType, + name: eventName, + }); + + this.setState({ + eventDataList, + }); + + this.updateEventListStatus(eventName); + this.closeEventMenu(); + this.openDialog(eventName); + }; + + onRelatedEventNameClick = (eventName: string) => { + const { editor } = this.props.field; + + editor.get('skeleton').getPanel('sourceEditor').show(); + + setTimeout(() => { + editor.emit('sourceEditor.focusByFunction', { + functionName: eventName, + }); + }, 300); + + + // editor.emit('sourceEditor.focusByFunction',{ + // functionName:eventName + // }) + }; + + closeEventMenu = () => { + if (this.state.selectType !== null) { + this.setState({ + selectType: null, + }); + } + }; + + openDeleteEventDialog = (eventName: string) => { + this.deleteEvent(eventName); + // Dialog.confirm({ + // title: '删除事件', + // content: '确定删除当前事件吗', + // onOk: () => this.deleteEvent(eventName), + // }); + }; + + deleteEvent = (eventName: string) => { + const { eventDataList, eventList } = this.state; + eventDataList.map((item, index) => { + if (item.name === eventName) { + eventDataList.splice(index, 1); + } + + return item; + }); + + this.setState({ + eventDataList, + }); + this.props.onChange({ eventDataList, eventList }); + this.updateEventListStatus(eventName, true); + }; + + openDialog = (bindEventName: string) => { + const { editor } = this.props.field; + const { eventDataList } = this.state; + let paramStr; + eventDataList.map((item) => { + if (item.name == bindEventName) { + paramStr = item.paramStr; + } + return item; + }); + this.bindEventName = bindEventName; + editor.emit('eventBindDialog.openDialog', bindEventName, SETTER_NAME, paramStr); + }; + + + bindEvent = (relatedEventName: string, paramStr: string) => { + const { eventDataList, eventList } = this.state; + eventDataList.map(item => { + if (item.name === this.bindEventName) { + item.relatedEventName = relatedEventName; + if (paramStr) { + item.paramStr = paramStr; + } + } + + return item; + }); + + this.setState({ + eventDataList, + }); + + + this.props.onChange({ eventDataList, eventList }); + + // this.closeDialog(); + }; + + render() { + const { + eventBtns, + eventList, + nativeEventList, + lifeCycleEventList, + selectType, + eventDataList, + } = this.state; + const showEventList = + lifeCycleEventList.length > 0 ? lifeCycleEventList : eventList; + return ( +
+ +
+ { + eventBtns.length > 1 ? 点击选择事件类型 : 点击绑定事件 + } +
+ + + {selectType && selectType != EVENT_CONTENTS.NATIVE_EVENT && ( + + {showEventList.map((item) => ( + + {item.name} + + ))} + + )} + + {selectType && selectType === EVENT_CONTENTS.NATIVE_EVENT && ( + + {nativeEventList.map((item, index) => ( + + {item.eventList.map(groupItem => ( + + {groupItem.name} + + ))} + + ))} + + )} + +
+ + + +
+
+
+ ); + } +} diff --git a/packages/editor-setters/src/expression-setter/index.tsx b/packages/editor-setters/src/expression-setter/index.tsx new file mode 100644 index 000000000..a4195a7a1 --- /dev/null +++ b/packages/editor-setters/src/expression-setter/index.tsx @@ -0,0 +1,349 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { Select, Balloon } from '@alife/next'; +import * as acorn from 'acorn'; + +import { isJSExpression, generateI18n } from './locale/utils'; +import zhCN from './locale/zh-CN'; + +import './index.scss'; + +const { Option, AutoComplete } = Select; +const { Tooltip } = Balloon; +const helpMap = { + this: '容器上下文对象', + state: '容器的state', + props: '容器的props', + context: '容器的context', + schema: '页面上下文对象', + component: '组件上下文对象', + constants: '应用常量对象', + utils: '应用工具对象', + dataSourceMap: '容器数据源Map', + field: '表单Field对象', +}; + +export default class ExpressionView extends PureComponent { + static displayName = 'Expression'; + + static propTypes = { + context: PropTypes.object, + dataSource: PropTypes.array, + locale: PropTypes.string, + messages: PropTypes.object, + onChange: PropTypes.func, + placeholder: PropTypes.string, + value: PropTypes.string, + }; + + static defaultProps = { + context: {}, + dataSource: [], + locale: 'zh-CN', + messages: zhCN, + onChange: () => {}, + placeholder: '', + value: '', + }; + + expression: React.RefObject; + + i18n: any; + + t: void; + + $input: any; + + listenerFun: ((event: any) => void) | undefined; + + static getInitValue(val: { value: any; match: (arg0: RegExp) => any; }) { + if (isJSExpression(val)) { + if (typeof val === 'object') { + return val.value; + } else if (typeof val === 'string') { + const arr = val.match(/^\{\{(.*?)\}\}$/); + if (arr) return arr[1]; + } + } + return val; + } + + constructor(props: any) { + super(props); + this.expression = React.createRef(); + this.i18n = generateI18n(props.locale, props.messages); + this.state = { + value: ExpressionView.getInitValue(props.value), + dataSource: props.dataSource || [], + }; + } + + static getDerivedStateFromProps(props: { value: any; }, state: { preValue: any; }) { + const curValue = ExpressionView.getInitValue(props.value); + if (curValue !== state.preValue) { + return { + preValue: curValue, + value: curValue, + }; + } + return null; + } + + onChange(value: string, actionType: string) { + let realInputValue = value; + const realDataSource = null; + let nextCursorIndex: number; + // 更新值 + if (actionType === 'itemClick' || actionType === 'enter') { + const curValue = this.state.value; + if (curValue) { + realInputValue = curValue + realInputValue; + } + } + // 更新数据源 + const newState = { + value: realInputValue, + }; + if (realDataSource !== null) newState.dataSource = realDataSource; + this.setState(newState, () => { + nextCursorIndex && this.setInputCursorPosition(nextCursorIndex); + }); + // 默认加上变量表达式 + this.t && clearTimeout(this.t); + this.t = setTimeout(() => { + const { onChange } = this.props; + // realInputValue = realInputValue ? `{{${realInputValue}}}` : undefined; + onChange && onChange({ + type: 'JSExpression', + value: realInputValue, + }); + }, 300); + } + + /** + * 获取AutoComplete数据源 + * @param {String} + * @return {Array} + */ + getDataSource(): any[] { + const { editor } = this.props.field; + const schema = editor.get('designer').project.getSchema(); + const stateMap = schema.componentsTree[0].state; + const dataSource = []; + + for (const key in stateMap) { + dataSource.push(`this.state.${key}`); + } + + return dataSource; + } + + /** + * 获取光标前的对象字符串,语法解析获取对象字符串 + * @param {String} str 模板字符串 + * @return {String} 光标前的对象字符串 + */ + getCurrentFiled(str: string | any[]) { + str += 'x'; // .后面加一个x字符,便于acorn解析 + try { + const astTree = acorn.parse(str); + const right = astTree.body[0].expression.right || astTree.body[0].expression; + if (right.type === 'MemberExpression') { + const { start, end } = right; + str = str.slice(start, end); + return { str, start, end }; + } + } catch (e) { + return null; + } + } + + /** + * 获取输入的上下文信息 + * @param {Array} + * @return {Array} + */ + getContextKeys(keys: []) { + const { editor } = this.props.field; + console.log(editor); + const limitKeys = ['schema', 'utils', 'constants']; + if (keys.length === 0) return limitKeys; + if (!limitKeys.includes(keys[0])) return []; + let result = []; + let keyValue = editor; + let assert = false; + keys.forEach(item => { + if (!keyValue[item] || typeof keyValue[item] !== 'object') { + assert = true; + } + if (keyValue[item]) { + keyValue = keyValue[item]; + } + }); + if (assert) return []; + result = Object.keys(keyValue); + return result; + // return utilsKeys.concat(constantsKeys).concat(schemaKeys); + } + + /* 过滤key */ + filterKey(obj: any, name: string) { + const filterKeys = [ + 'reloadDataSource', + 'REACT_HOT_LOADER_RENDERED_GENERATION', + 'refs', + 'updater', + 'appHelper', + 'isReactComponent', + 'forceUpdate', + 'setState', + 'isPureReactComponent', + ]; + const result = []; + for (const key in obj) { + if (key.indexOf('_') !== 0 && filterKeys.indexOf(key) === -1) { + result.push(`${name}.${key}`); + } + } + return result; + } + + /** + * 根据输入项进行筛选 + * @param {String} + * @param {String} + * @return {Boolen} + */ + filterOption(inputValue: string, item: { value: string | any[]; }) { + const cursorIndex = this.getInputCursorPosition(); + const preStr = inputValue.substr(0, cursorIndex); + const lastKey: string[] = preStr.split('.').slice(-1); + if (!lastKey) return true; + if (item.value.indexOf(lastKey) > -1) return true; + return false; + } + + // handleClick = () => { + // this.props.field.editor.emit('variableBindDialog.open'); + // } + + render() { + const { value, dataSource } = this.state; + const { placeholder } = this.props; + const isValObject = !!(value == '[object Object]'); + const title = isValObject + ? this.i18n('valueIllegal') + : (value || placeholder || this.i18n('jsExpression')).toString(); + const cursorIndex = this.getInputCursorPosition(); + const childNode = cursorIndex ? ( +
+ {title.substr(0, cursorIndex)} + | + {title.substr(cursorIndex)} +
+ ) : ( + title + ); + + return ( +
+ + {'{{'}} + innerAfter={{'}}'}} + popupClassName="expression-setter-item-inner" + // eslint-disable-next-line no-shadow + itemRender={({ value }) => { + return ( + + ); + }} + onChange={this.onChange.bind(this)} + filter={this.filterOption.bind(this)} + /> +
+ ) + } + > + {childNode} + +
+ ); + } + + componentDidMount() { + this.$input = this.findInputElement(); + if (this.$input) { + this.listenerFun = event => { + const isMoveKey = !!(event.type == 'keyup' && ~[37, 38, 39, 91].indexOf(event.keyCode)); + const isMouseup = event.type == 'mouseup'; + if (isMoveKey || isMouseup) { + // eslint-disable-next-line react/no-access-state-in-setstate + const dataSource = this.getDataSource(this.state.value) || []; + this.setState({ + dataSource, + }); + } + }; + this.$input.addEventListener('keyup', this.listenerFun, false); + this.$input.addEventListener('mouseup', this.listenerFun, false); + } + } + + componentWillUnmount() { + if (this.listenerFun && this.$input) { + this.$input.removeEventListener('keyup', this.listenerFun, false); + this.$input.removeEventListener('mouseup', this.listenerFun, false); + } + } + + /** + * 获取Input输入框DOM节点 + */ + findInputElement() { + return this.expression.current.children[0].getElementsByTagName('input')[0]; + } + + /** + * 获取光标位置 + * + */ + getInputCursorPosition() { + if (!this.$input) return; + return this.$input.selectionStart; + } + + /* + * 字符串取得对象keys + */ + getObjectKeys(str: string) { + let keys: string | any[] = []; + if (str) keys = str.split('.'); + return keys.slice(0, keys.length - 1); + } + + /* + * 设置input组件光标位置在闭合}前 + */ + setInputCursorPosition(idx: number) { + this.$input.setSelectionRange(idx, idx); + this.forceUpdate(); + } +} diff --git a/packages/editor-setters/src/expression-setter/locale/snippets.ts b/packages/editor-setters/src/expression-setter/locale/snippets.ts new file mode 100644 index 000000000..2bcb0dd00 --- /dev/null +++ b/packages/editor-setters/src/expression-setter/locale/snippets.ts @@ -0,0 +1,242 @@ +export default [ + { + label: 'constants', + kind: 'Class', + insertText: 'constants', + detail: '应用全局常量', + documentation: '应用范围定义的通用常量', + }, + { + label: 'utils', + kind: 'Class', + insertText: 'utils', + detail: '应用全局公共函数', + documentation: '应用范围扩展的公共函数', + }, + { + label: 'state', + kind: 'Enum', + insertText: 'state', + detail: '当前所在容器组件内部状态', + documentation: 'React Class内部状态state', + }, + { + label: 'setState', + kind: 'Function', + insertText: 'setState({\n\t$0\n})', + insertTextRules: 'InsertAsSnippet', + detail: '设置当前所在容器组件的state数据', + documentation: '原生React方法,会自动更新组件视图', + }, + { + label: 'reloadDataSource', + kind: 'Function', + insertText: 'reloadDataSource(${1:${2:namespace}, ${3:false}, ${4:callback}})', + insertTextRules: 'InsertAsSnippet', + detail: '刷新当前所在的容器组件', + documentation: '触发当前所在的容器组件,重新发送异步请求,并用最新数据更新视图', + }, + { + label: 'location', + kind: 'Class', + insertText: 'location', + detail: '路由解析对象', + }, + { + label: 'location.query', + kind: 'Value', + insertText: 'location.query.${1:xxxx}', + insertTextRules: 'InsertAsSnippet', + detail: '从路由解析对象中获取参数信息', + }, + { + label: 'history', + kind: 'Class', + insertText: 'history', + detail: '路由历史对象', + }, + { + label: 'React', + kind: 'Keyword', + insertText: 'React', + detail: 'React对象', + }, + { + label: 'ReactDOM', + kind: 'Keyword', + insertText: 'ReactDOM', + detail: 'ReactDom对象', + }, + { + label: 'ReactDOM.findDOMNode', + kind: 'Function', + insertText: 'ReactDOM.findDOMNode(${1:this.refs.xxxx})', + insertTextRules: 'InsertAsSnippet', + detail: 'ReactDom查找真实dom node', + }, + { + label: 'Dialog.alert', + kind: 'Method', + insertText: [ + 'Dialog.alert({', + "\tcontent: '${1:Alert content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: 'alert弹框 By Fusion', + }, + { + label: 'Dialog.confirm', + kind: 'Method', + insertText: [ + 'Dialog.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Fusion', + }, + { + label: 'Message.success', + kind: 'Method', + insertText: 'Message.success(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '成功反馈提示 By Fusion', + }, + { + label: 'Message.error', + kind: 'Method', + insertText: 'Message.error(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '错误反馈提示 By Fusion', + }, + { + label: 'Message.help', + kind: 'Method', + insertText: 'Message.help(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '帮助反馈提示 By Fusion', + }, + { + label: 'Message.loading', + kind: 'Method', + insertText: 'Message.loading(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: 'loading反馈提示 By Fusion', + }, + { + label: 'Message.notice', + kind: 'Method', + insertText: 'Message.notice(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '注意反馈提示 By Fusion', + }, + { + label: 'Message.waining', + kind: 'Method', + insertText: 'Message.waining(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '警告反馈提示 By Fusion', + }, + { + label: 'Modal.confirm', + kind: 'Method', + insertText: [ + 'Modal.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Antd', + }, + { + label: 'Modal.info', + kind: 'Method', + insertText: [ + 'Modal.info({', + "\tcontent: '${1:Info content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '信息弹出框 By Antd', + }, + { + label: 'Modal.success', + kind: 'Method', + insertText: [ + 'Modal.success({', + "\tcontent: '${1:Success content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '成功弹出框 By Antd', + }, + { + label: 'Modal.error', + kind: 'Method', + insertText: [ + 'Modal.error({', + "\tcontent: '${1:Error content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '错误弹出框 By Antd', + }, + { + label: 'Modal.warning', + kind: 'Method', + insertText: [ + 'Modal.warning({', + "\tcontent: '${1:Warning content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '警告弹出框 By Antd', + }, +]; diff --git a/packages/editor-setters/src/expression-setter/locale/utils.ts b/packages/editor-setters/src/expression-setter/locale/utils.ts new file mode 100644 index 000000000..d401ad511 --- /dev/null +++ b/packages/editor-setters/src/expression-setter/locale/utils.ts @@ -0,0 +1,21 @@ +import IntlMessageFormat from 'intl-messageformat'; + +export const isJSExpression = (obj = '') => { + if (obj && typeof obj === 'object' && obj.type === 'JSExpression') { + return true; + } + return false; +}; + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export const generateI18n = (locale = 'zh-CN', messages = {}) => { + return function (key, values = {}) { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +}; diff --git a/packages/editor-setters/src/expression-setter/locale/zh-CN.ts b/packages/editor-setters/src/expression-setter/locale/zh-CN.ts new file mode 100644 index 000000000..f4f2e0630 --- /dev/null +++ b/packages/editor-setters/src/expression-setter/locale/zh-CN.ts @@ -0,0 +1,36 @@ +export default { + // function + setting: '点击设置', + edit: '编辑', + submitConfirm: '确认提交 cmd+s', + close: '关闭 esc', + fullScreen: '全屏', + cancelFullScreen: '取消全屏', + jsonIllegal: '非json格式', + functionIllegal: '非function格式', + objectIllegal: '非object格式', + circularRef: '对象中出现循环引用的对象', + formatError: '格式错误', + saved: '已保存', + // expression + valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑', + jsExpression: '请输入JS表达式', + // Mixin + input: '字符串Input', + textarea: '多行字符串Textarea', + expression: '变量控件Expression', + monacoEditor: '编辑器MonacoEditor', + numberPicker: '数字NumberPicker', + bool: '布尔Switch', + datePicker: '日期选择DatePicker', + select: '下拉选择Select', + radio: '单项选择RadioGroup', + date: '日期选择DatePicker', + dateYear: '年选择DatePicker', + dateMonth: '月选择DatePicker', + dateRange: '日期区间选择DatePicker', + list: '数组List', + object: '对象ObjectButton', + reactNode: '节点类型ReactNode', + typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置', +}; diff --git a/packages/editor-setters/src/function-setter/index.tsx b/packages/editor-setters/src/function-setter/index.tsx new file mode 100644 index 000000000..003c45e68 --- /dev/null +++ b/packages/editor-setters/src/function-setter/index.tsx @@ -0,0 +1,230 @@ +import React, { PureComponent } from 'react'; +// import PropTypes from 'prop-types'; +import { Button, Icon, Dialog } from '@alifd/next'; +import MonacoEditor from 'react-monaco-editor'; +import { js_beautify } from 'js-beautify'; +import './index.scss'; + +const SETTER_NAME = 'function-setter'; + +const defaultEditorOption = { + width: '100%', + height: '100%', + options: { + readOnly: false, + automaticLayout: true, + folding: true, // 默认开启折叠代码功能 + lineNumbers: 'on', + wordWrap: 'off', + formatOnPaste: true, + fontSize: 12, + tabSize: 2, + scrollBeyondLastLine: false, + fixedOverflowWidgets: false, + snippetSuggestions: 'top', + minimap: { + enabled: false, + }, + scrollbar: { + vertical: 'auto', + horizontal: 'auto', + }, + }, +}; + + +interface FunctionSetterProps { + value: string; + type: string; + defaultValue: string; + placeholder: string; + hasClear: boolean; + onChange: (icon: string) => undefined; + icons: string[]; +} +export default class FunctionSetter extends PureComponent { + static defaultProps = { + value: undefined, + type: 'string', + defaultValue: '', + hasClear: true, + placeholder: '请点击选择 Icon', + onChange: () => undefined, + }; + + private emitEventName = ''; + + state = { + isShowDialog: false, + }; + + componentDidMount() { + const { editor } = this.props.field; + this.emitEventName = `${SETTER_NAME}-${this.props.field.id}`; + editor.on(`${this.emitEventName}.bindEvent`, this.bindEvent); + } + + bindEvent = (eventName) => { + this.bindEventCallback(eventName); + }; + + + componentWillUnmount() { + const { editor } = this.props.field; + editor.off(`${this.emitEventName}.bindEvent`, this.bindEvent); + } + + + bindFunction = () => { + const { field } = this.props; + field.editor.emit('eventBindDialog.openDialog', field.name, this.emitEventName); + }; + + openDialog = () => { + const { value = {} } = this.props; + this.setState({ + isShowDialog: true, + }); + + this.functionCode = value.value; + }; + + closeDialog = () => { + this.setState({ + isShowDialog: false, + }); + }; + + removeFunctionBind = () => { + const { removeProp } = this.props; + removeProp(); + }; + + parseFunctionName = (functionString: string) => { + // 因为函数格式是固定的,所以可以按照字符换去匹配获取函数名 + const funNameStr = functionString.split('this.')[1]; + + + if (funNameStr) { + const endIndex = funNameStr.indexOf('('); + return funNameStr.substr(0, endIndex); + } else { + return ''; + } + }; + + /** + * 渲染按钮(初始状态) + */ + renderButton = () => { + return ; + }; + + updateCode = (newCode) => { + this.functionCode = newCode; + }; + + onDialogOk = () => { + const { onChange } = this.props; + onChange({ + type: 'JSFunction', + value: this.functionCode, + }); + + this.closeDialog(); + }; + + focusFunctionName = (functionName) => { + const { editor } = this.props.field; + + editor.get('skeleton').getPanel('sourceEditor').show(); + + setTimeout(() => { + editor.emit('sourceEditor.focusByFunction', { + functionName, + }); + }, 300); + }; + + /** + * 渲染绑定函数 + */ + renderBindFunction = () => { + const { value } = this.props; + + // 解析函数名 + const functionName = this.parseFunctionName(value.value); + return ( +
+ + this.focusFunctionName(functionName)}>{functionName} + + +
+ ); + }; + + + /** + * 渲染编辑函数按钮(可直接编辑函数内容) + */ + renderEditFunctionButton = () => { + return ( +
+ +
+ ); + }; + + + bindEventCallback = (eventName: string) => { + const { onChange } = this.props; + onChange({ + type: 'JSFunction', + value: `function(){ this.${eventName}() }`, + }); + }; + + render() { + const { value } = this.props; + const { isShowDialog } = this.state; + + let functionName = ''; + if (value && value.value) { + functionName = this.parseFunctionName(value.value); + } + + let renderFunction; + if (value) { + if (functionName) { + renderFunction = this.renderBindFunction; + } else { + renderFunction = this.renderEditFunctionButton; + } + } else { + renderFunction = this.renderButton; + } + + return ( +
+ { + renderFunction() + } + + { + value && value.value && + { this.closeDialog(); }}> +
+ this.updateCode(newCode)} + /> +
+
+ } +
+ ); + } +} diff --git a/packages/editor-setters/src/icon-setter/index.tsx b/packages/editor-setters/src/icon-setter/index.tsx new file mode 100644 index 000000000..f61c32271 --- /dev/null +++ b/packages/editor-setters/src/icon-setter/index.tsx @@ -0,0 +1,171 @@ +import React, { PureComponent } from 'react'; +// import PropTypes from 'prop-types'; +import { Input, Icon, Balloon } from '@alifd/next'; + +import './index.scss'; + +const icons = [ + 'smile', + 'cry', + 'success', + 'warning', + 'prompt', + 'error', + 'help', + 'clock', + 'success-filling', + 'delete-filling', + 'favorites-filling', + 'add', + 'minus', + 'arrow-up', + 'arrow-down', + 'arrow-left', + 'arrow-right', + 'arrow-double-left', + 'arrow-double-right', + 'switch', + 'sorting', + 'descending', + 'ascending', + 'select', + 'semi-select', + 'loading', + 'search', + 'close', + 'ellipsis', + 'picture', + 'calendar', + 'ashbin', + 'upload', + 'download', + 'set', + 'edit', + 'refresh', + 'filter', + 'attachment', + 'account', + 'email', + 'atm', + 'copy', + 'exit', + 'eye', + 'eye-close', + 'toggle-left', + 'toggle-right', + 'lock', + 'unlock', + 'chart-pie', + 'chart-bar', + 'form', + 'detail', + 'list', + 'dashboard', +]; +interface IconSetterProps { + value: string; + type: string; + defaultValue: string; + placeholder: string; + hasClear: boolean; + onChange: (icon: string) => undefined; + icons: string[]; +} +export default class IconSetter extends PureComponent { + static defaultProps = { + value: undefined, + type: 'string', + defaultValue: '', + hasClear: true, + icons, + placeholder: '请点击选择 Icon', + onChange: () => undefined, + }; + + state = { + firstLoad: true, + }; + + _onChange = (icon: string) => { + const { onChange, type } = this.props; + if (type === 'string') { + onChange(icon); + } else if (type === 'node') { + onChange({ + componentName: 'Icon', + props: { + type: icon, + }, + }); + } + }; + + onInputChange = (icon: string) => { + this._onChange(icon); + }; + + onSelectIcon = (icon: string) => { + this._onChange(icon); + }; + + render() { + const { value, defaultValue, onChange, placeholder, hasClear } = this.props; + const { firstLoad } = this.state; + const _value = typeof value === 'object' ? value?.props?.type : value; + if (firstLoad && defaultValue && typeof value === 'undefined') { + onChange(defaultValue); + this.setState({ + firstLoad: false, + }); + } + const currentIcon = ; + const clearIcon = hasClear && ( + { + e.preventDefault(); + e.stopPropagation(); + this.onSelectIcon(''); + }} + /> + ); + + const triggerNode = ( +
+ +
+ ); + const InnerBeforeNode = ( + +
    + {icons.map((icon) => ( +
  • this.onSelectIcon(icon)}> + +
  • + ))} +
+
+ ); + + return
{InnerBeforeNode}
; + } +} diff --git a/packages/editor-setters/src/index.tsx b/packages/editor-setters/src/index.tsx new file mode 100644 index 000000000..b56133f12 --- /dev/null +++ b/packages/editor-setters/src/index.tsx @@ -0,0 +1,131 @@ +import React, { Component } from 'react'; +import { registerSetter } from '@ali/lowcode-editor-core'; +import { isJSExpression, isJSFunction } from '@ali/lowcode-types'; +import { DatePicker, TimePicker, Input, Radio, Select, Switch, NumberPicker } from '@alifd/next'; +import ExpressionSetter from './expression-setter'; +import ColorSetter from './color-setter'; +import JsonSetter from './json-setter'; +import EventsSetter from './events-setter'; +import StyleSetter from './style-setter'; +import IconSetter from './icon-setter'; +import FunctionSetter from './function-setter'; +// import ClassNameSetter from './classname-setter'; +// import MixedSetter from './mixed-setter'; + +export const StringSetter = { + component: Input, + defaultProps: { placeholder: '请输入', style: { maxWidth: 180 } }, + title: 'StringSetter', + recommend: true, + condition: (field: any) => { + const v = field.getValue(); + return typeof v === 'string'; + }, +}; +export const NumberSetter = NumberPicker; +export class BoolSetter extends Component { + render() { + const { onChange, value, defaultValue } = this.props; + return ; + } +} +export const SelectSetter = Select; + +// suggest: 做成 SelectSetter 一种变体 +export const RadioGroupSetter = { + component: Radio.Group, + defaultProps: { + shape: 'button', + }, +}; +// suggest: 做成 StringSetter 的一个参数, +export const TextAreaSetter = { + component: Input.TextArea, + defaultProps: { placeholder: '请输入', style: { maxWidth: 180 } }, + title: 'TextAreaSetter', + recommend: true, + condition: (field: any) => { + const v = field.getValue(); + return typeof v === 'string'; + }, +}; +export const DateSetter = DatePicker; +export const DateYearSetter = DatePicker.YearPicker; +export const DateMonthSetter = DatePicker.MonthPicker; +export const DateRangeSetter = DatePicker.RangePicker; + +export { ExpressionSetter, EventsSetter, JsonSetter, IconSetter }; + +// eslint-disable-next-line react/no-multi-comp +class StringDateSetter extends Component { + render() { + const { onChange } = this.props; + return ( + { + onChange(val.format()); + }} + /> + ); + } +} + +// eslint-disable-next-line react/no-multi-comp +class StringTimePicker extends Component { + render() { + const { onChange } = this.props; + return ( + { + onChange(val.format('HH:mm:ss')); + }} + /> + ); + } +} + +const VariableSetter = { + component: ExpressionSetter, + condition: (field: any) => { + const v = field.getValue(); + return isJSExpression(v); + }, + defaultProps: { placeholder: '请输入表达式' }, + title: '表达式输入', + recommend: true, +}; + + +const FunctionBindSetter = { + component: FunctionSetter, + title: '函数绑定', + condition: (field: any) => { + const v = field.getValue(); + return v == isJSFunction(v); + }, +}; + +const builtinSetters: any = { + StringSetter, + NumberSetter, + BoolSetter, + SelectSetter, + VariableSetter, + ExpressionSetter: VariableSetter, + RadioGroupSetter, + TextAreaSetter, + DateSetter: StringDateSetter, + TimePicker: StringTimePicker, + DateYearSetter, + DateMonthSetter, + DateRangeSetter, + EventsSetter, + ColorSetter, + JsonSetter, + StyleSetter, + IconSetter, + ClassNameSetter, + FunctionSetter: FunctionBindSetter, +}; + +registerSetter(builtinSetters); diff --git a/packages/editor-setters/src/json-setter/index.tsx b/packages/editor-setters/src/json-setter/index.tsx new file mode 100644 index 000000000..d09ef038e --- /dev/null +++ b/packages/editor-setters/src/json-setter/index.tsx @@ -0,0 +1,622 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { js_beautify, css_beautify } from 'js-beautify'; +import MonacoEditor from 'react-monaco-editor'; +import classNames from 'classnames'; + +import { Icon, Message } from '@alife/next'; +import ObjectButton from '@ali/iceluna-comp-object-button'; +import FormItem from '@ali/iceluna-comp-form/lib/item'; +import { serialize, jsonuri, generateI18n } from '@ali/iceluna-sdk/lib/utils'; +import localeConfig from '@ali/iceluna-sdk/lib/hoc/localeConfig'; + +import Snippets from './locale/snippets'; +import zhCN from './locale/zh-CN'; +import './index.scss'; + +let registerApiAndSnippetStatus = false; // 判断注册api机制 + +window.bt = js_beautify; +class MonacoEditorView extends PureComponent { + static displayName = 'MonacoEditor'; + + render() { + const { type, ...restProps } = this.props; + const Node = type == 'button' ? MonacoEditorButtonView : MonacoEditorDefaultView; + return Object.assign(this, apis)} />; + } +} + +localeConfig('MonacoEditor', MonacoEditorView); + +// monaco编辑器存在3种主题:vs、vs-dark、hc-black +// eslint-disable-next-line react/no-multi-comp +class MonacoEditorDefaultView extends PureComponent { + static displayName = 'MonacoEditorDefault'; + + static propTypes = { + locale: PropTypes.string, + messages: PropTypes.object, + language: PropTypes.string, + }; + + static defaultProps = { + locale: 'zh-CN', + messages: zhCN, + width: '100%', + height: '300px', + language: 'json', + autoFocus: false, // 自动获得焦点 + autoSubmit: true, // 自动提交 + placeholder: '', // 默认占位内容 + btnText: '提交', + btnSize: 'small', + rules: [], // 校验规则 + options: { + readOnly: false, + automaticLayout: true, + folding: true, // 默认开启折叠代码功能 + lineNumbers: 'on', + wordWrap: 'off', + formatOnPaste: true, + fontSize: 12, + tabSize: 2, + scrollBeyondLastLine: false, + fixedOverflowWidgets: false, + snippetSuggestions: 'top', + minimap: { + enabled: true, + }, + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden', + verticalScrollbarSize: 0, + }, + }, + }; + + strValue: string; + + i18n: any; + + editorRef: React.RefObject; + + options: any; + + fullScreenOptions: any; + + position: any; + + editor: any; + + editorNode: unknown; + + editorParentNode: any; + + constructor(props: Readonly) { + super(props); + this.strValue = ''; + this.i18n = generateI18n(props.locale, props.messages); + this.editorRef = React.createRef(); + this.options = Object.assign({}, MonacoEditorDefaultView.defaultProps.options, props.options); + this.fullScreenOptions = { + ...this.options, + lineNumbers: 'on', + folding: true, + scrollBeyondLastLine: true, + minimap: { + enabled: true, + }, + }; + this.state = { + isFullScreen: false, + }; + this.onChange = this.onChange.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.fullScreen = this.fullScreen.bind(this); + this.format = this.format.bind(this); + } + + componentDidUpdate() { + // 如果是全屏操作,获得焦点,光标保留在原来位置; + if (this.position) { + this.editor.focus(); + this.editor.setPosition(this.position); + delete this.position; + } + } + + componentDidMount() { + this.editorNode = this.editorRef.current; // 记录当前dom节点; + this.editorParentNode = this.editorNode.parentNode; // 记录父节点; + // 自动获得焦点, 格式化需要时间 + if (this.props.autoFocus) { + setTimeout(() => { + this.editor.setPosition({ + column: 4, + lineNumber: 2, + }); + this.editor.focus(); + }, 100); + } + // 快捷键编码 + const CtrlCmd = 2048; + const KEY_S = 49; + const Shift = 1024; + const KEY_F = 36; + const KEY_B = 32; + const Escape = 9; + + this.editor.addCommand(CtrlCmd | KEY_S, () => { + this.onSubmit(); // 保存快捷键 + }); + this.editor.addCommand(CtrlCmd | Shift | KEY_F, () => { + this.fullScreen(); // 全屏快捷键 + }); + this.editor.addCommand(CtrlCmd | KEY_B, () => { + this.format(); // 美化快捷键 + }); + this.editor.addCommand(Escape, () => { + this.props.onEscape && this.props.onEscape(); + }); + // 注册api + this.editor.submit = this.onSubmit; + this.editor.format = this.format; + this.editor.fullScreen = this.fullScreen; + this.editor.toJson = this.toJson; + this.editor.toObject = this.toObject; + this.editor.toFunction = this.toFunction; + // 针对object情况,改写setValue和getValue api + if (this.props.language === 'object') { + const { getValue } = this.editor; + const { setValue } = this.editor; + this.editor.getValue = () => { + return getValue.call(this.editor).substring(this.valuePrefix.length); + }; + this.editor.setValue = (value) => { + return setValue.call(this.editor, [this.valuePrefix + value]); + }; + } + } + + render() { + const { + value, + placeholder, + style, + className, + width, + height, + language, + theme, + editorWillMount, + editorDidMount, + registerApi, + } = this.props; + + const { isFullScreen } = this.state; + this.valuePrefix = ''; // 值前缀 + if (language === 'object') this.valuePrefix = 'export default '; + if (!this.isFullScreenAction) { + // 将值转换成目标值 + const nowValue = this.valueHandler(value || placeholder, language); + const curValue = this.valueHandler(this.strValue, language); + if (nowValue !== curValue) this.strValue = nowValue; + if (language === 'object') this.strValue = this.strValue || placeholder || '{\n\t\n}'; // 设置初始化值 + if (language === 'json' && this.strValue === '{}') this.strValue = '{\n\t\n}'; + } + this.isFullScreenAction = false; + // 真实高亮语言 + let tarLanguage = language; + if (language === 'object' || language === 'function') { + tarLanguage = 'javascript'; + } + const classes = classNames('monaco-editor-wrap', { + 'monaco-fullscreen': !!isFullScreen, + 'monaco-nofullscreen': !isFullScreen, + }); + const tarStyle = Object.assign({ minHeight: 60, width, height }, style); + let tempValue = this.valuePrefix + this.strValue; + if (tarLanguage === 'json') { + try { + tempValue = JSON.stringify(JSON.parse(tempValue || '{}'), null, 2); + } catch (err) { + console.log(err); + } + } + return ( +
+
+ { + this.editor = editor; + registerApi({ editor }); + this.registerApiAndSnippet(monaco); + editorDidMount && editorDidMount.call(this, arguments); + }} + /> + + + +
+
+ ); + } + + // 值变化 + onChange(curValue) { + if (curValue === this.valuePrefix + this.strValue) return; + const { onAfterChange, language, autoSubmit, onChange } = this.props; + this.strValue = curValue; // 记录当前格式 + if (this.ct) clearTimeout(this.ct); + this.ct = setTimeout(() => { + this.position = this.editor.getPosition(); + const ret = this.resultHandler(curValue, language); + if (autoSubmit) onChange && onChange(ret.value); + onAfterChange && onAfterChange(ret.value, ret.error, this.editor); + }, 300); + } + + // 提交动作 + onSubmit() { + const { onSubmit, onChange, language } = this.props; + const curValue = this.editor.getValue(); + const ret = this.resultHandler(curValue, language); + if (!ret.error) onChange && onChange(ret.value); + onSubmit && onSubmit(ret.value, ret.error, this.editor); + } + + // 值类型转换处理 + valueHandler(value, language) { + let tarValue = value || ''; + if (language === 'json') { + if (value && typeof value === 'object') { + tarValue = JSON.stringify(value, null, 2); + } else if (value && typeof value === 'string') { + try { + const ret = this.toJson(value); + if (!ret.error) { + tarValue = JSON.stringify(ret.value, null, 2); + } + } catch (err) { + // empty + } + } + } else if (language === 'function') { + if (typeof value === 'function') { + tarValue = value.toString(); + } + if (tarValue && typeof tarValue === 'string') { + tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true }); + } + } else if (language === 'object') { + // 先转成对象,在进行序列化和格式化; + value = value || {}; + if (value && typeof value === 'object') { + try { + tarValue = serialize(value, { unsafe: true }); + tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true }); + } catch (err) { + // empty + } + } else if (typeof value === 'string') { + try { + const ret = this.resultHandler(value, 'object'); + tarValue = ret.error ? ret.value : serialize(ret.value, { unsafe: true }); + tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true }); + } catch (err) { + // empty + } + } + } + return tarValue; + } + + // 结果处理 + resultHandler(value, language) { + let ret = { value }; + if (language === 'json') { + ret = this.toJson(value); + } else if (language === 'object') { + ret = this.toObject(value); + } else if (language === 'function') { + ret = this.toFunction(value); + } + return ret; + } + + // 设置全屏时的动作 + fullScreen() { + if (!this.editorRef) return; + // 还原到原来位置; + this.position = this.editor.getPosition(); + if (this.state.isFullScreen) { + if (this.editorParentNode) { + if (this.editorParentNode.firstChild) { + this.editorParentNode.insertBefore(this.editorNode, this.editorParentNode.firstChild); + } else { + this.editorParentNode.appendChild(this.editorNode); + } + } + } else { + document.body.appendChild(this.editorNode); + } + // eslint-disable-next-line react/no-access-state-in-setstate + const nextFs = !this.state.isFullScreen; + this.isFullScreenAction = true; // 记录是全屏幕操作 + this.setState( + { + isFullScreen: nextFs, + }, + () => { + this.editor.updateOptions(nextFs ? this.fullScreenOptions : this.options); + }, + ); + } + + // 美化代码 + format() { + if (!this.editor) return; + if (/^\$_obj?\{.*?\}$/m.test(this.editor.getValue())) return; + if (this.props.language === 'json' || this.props.language === 'object' || this.props.language === 'function') { + const tarValue = js_beautify(this.editor.getValue(), { indent_size: 2 }); + this.editor.setValue(tarValue); + } else if (this.props.language === 'less' || this.props.language === 'css' || this.props.language === 'scss') { + const tarValue = css_beautify(this.editor.getValue(), { indent_size: 2 }); + this.editor.setValue(tarValue); + } else { + this.editor.getAction('editor.action.formatDocument').run(); + } + } + + // 校验是否是json + toJson(value) { + try { + // eslint-disable-next-line no-new-func + const obj = new Function(`'use strict'; return ${value.replace(/[\r\n\t]/g, '')}`)(); + if (typeof obj === 'object' && obj) { + // eslint-disable-next-line no-new-func + const tarValue = new Function(`'use strict'; return ${value}`)(); + return { value: JSON.parse(JSON.stringify(tarValue)) }; + } + return { error: this.i18n('jsonIllegal'), value }; + } catch (err) { + return { error: err, value }; + } + } + + // 校验是否为object对象 + toObject(value) { + try { + // eslint-disable-next-line no-new-func + const obj = new Function(`'use strict';return ${value}`)(); + if (obj && typeof obj === 'object') { + if (jsonuri.isCircular(obj)) return { error: this.i18n('circularRef'), value }; + return { value: obj }; + } else { + return { error: this.i18n('objectIllegal'), value }; + } + } catch (err) { + return { error: err, value }; + } + } + + // 校验是否为function + toFunction(value) { + try { + // eslint-disable-next-line no-new-func + const fun = new Function(`'use strict';return ${value}`)(); + if (fun && typeof fun === 'function') { + return { value: fun }; + } else { + return { error: this.i18n('functionIllegal'), value }; + } + } catch (err) { + return { error: err, value }; + } + } + + // 注册api和代码片段 + registerApiAndSnippet(monaco) { + if (registerApiAndSnippetStatus) return; + registerApiAndSnippetStatus = true; + // 注册this.提示的方法; + const thisSuggestions = []; + Snippets.map((item) => { + if (!item.label || !item.kind || !item.insertText) return; + const tarItem = Object.assign(item, { + label: item.label, + kind: monaco.languages.CompletionItemKind[item.kind], + insertText: item.insertText, + }); + if (item.insertTextRules) tarItem.insertTextRules = monaco.languages.CompletionItemInsertTextRule[item.insertTextRules]; + thisSuggestions.push(tarItem); + return item; + }); + monaco.languages.registerCompletionItemProvider('javascript', { + provideCompletionItems: (model, position) => { + const textUntilPosition = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + const match = textUntilPosition.match(/(^this\.)|(\sthis\.)/); + const suggestions = match ? thisSuggestions : []; + return { suggestions }; + }, + triggerCharacters: ['.'], + }); + } +} +// const prefix = 'data:text/javascript;charset=utf-8,'; +// const baseUrl = 'https://g.alicdn.com/iceluna/iceluna-vendor/0.0.1/'; +// window.MonacoEnvironment = { +// getWorkerUrl(label: string) { +// if (label === 'json') { +// return `${prefix}${encodeURIComponent(` +// importScripts('${baseUrl}json.worker.js');`)}`; +// } +// if (['css', 'less', 'scss'].includes(label)) { +// return `${prefix}${encodeURIComponent(` +// importScripts('${baseUrl}css.worker.js');`)}`; +// } +// if (label === 'html') { +// return `${prefix}${encodeURIComponent(` +// importScripts('${baseUrl}html.worker.js');`)}`; +// } +// if (['typescript', 'javascript'].includes(label)) { +// return `${prefix}${encodeURIComponent(` +// importScripts('${baseUrl}typescript.worker.js');`)}`; +// } +// return `${prefix}${encodeURIComponent(` +// importScripts('${baseUrl}editor.worker.js');`)}`; +// }, +// }; + +// eslint-disable-next-line react/no-multi-comp +export default class MonacoEditorButtonView extends PureComponent { + static displayName = 'JsonSetter'; + + static propTypes = { + locale: PropTypes.string, + messages: PropTypes.object, + }; + + static defaultProps = { + locale: 'zh-CN', + messages: zhCN, + }; + + i18n: any; + + objectButtonRef: React.RefObject; + + constructor(props: Readonly) { + super(props); + this.i18n = generateI18n(props.locale, props.messages); + this.objectButtonRef = React.createRef(); + // 兼容代码,待去除 + window.__ctx.appHelper.constants = window.__ctx.appHelper.constants || {}; + } + + afterHandler(value: { nrs_temp_field: any }) { + if (!value) return; + return value.nrs_temp_field; + } + + beforeHandler(value: any) { + if (!value) return; + return { nrs_temp_field: value }; + } + + message(type: string, title: any, dom: Element | null) { + Message.show({ + type, + title, + duration: 1000, + align: 'cc cc', + overlayProps: { + target: dom, + }, + }); + } + + componentDidMount() { + const { registerApi } = this.props; + const objectButtonThis = this.objectButtonRef; + registerApi && + registerApi({ + show: objectButtonThis.showModal, + hide: objectButtonThis.hideModal, + submit: objectButtonThis.submitHandler, + setValues: objectButtonThis.setValues, + }); + } + + render() { + const self = this; + const { locale, messages, value, onChange, field, languages, ...restProps } = this.props; + const { id } = field; + const tarRestProps = { ...restProps }; + tarRestProps.autoSubmit = true; + tarRestProps.autoFocus = true; + const tarOnSubmit = tarRestProps.onSubmit; + // 确保monaco快捷键保存,能出发最外层的保存 + tarRestProps.onSubmit = (editorValue, error) => { + const msgDom = document.querySelector('.object-button-overlay .next-dialog-body'); + if (error) return this.message('error', this.i18n('formatError'), msgDom); + this.objectButtonRef && + this.objectButtonRef.current && + this.objectButtonRef.current.submitHandler(() => { + this.message('success', this.i18n('saved'), msgDom); + }); + }; + const tarObjProps = {}; + tarObjProps.className = 'luna-monaco-button'; + if (tarRestProps['data-meta']) { + delete tarRestProps['data-meta']; + tarObjProps['data-meta'] = 'Field'; + } + + tarObjProps.id = id; + tarObjProps.value = value || ''; + tarObjProps.onChange = onChange; + const tarRule = []; + // 判断,如果是json,function, object等类型,自动追加校验规则; + if (tarRestProps.language && ['json', 'function', 'object'].includes(tarRestProps.language)) { + if (['json', 'object'].includes(tarRestProps.language)) { + tarRule.push({ + validator(validatorValue: any, callback: (arg0: undefined) => void) { + if (typeof validatorValue !== 'object') { + callback(self.i18n('formatError')); + } else { + callback(); + } + }, + }); + } else { + tarRule.push({ + validator(validatorValue: any, callback: (arg0: undefined) => void) { + if (typeof validatorValue !== 'function') { + callback(self.i18n('formatError')); + } else { + callback(); + } + }, + }); + } + } + return ( +
+ + + Object.assign(this, apis)} /> + + +
+ ); + } +} diff --git a/packages/editor-setters/src/json-setter/locale/snippets.ts b/packages/editor-setters/src/json-setter/locale/snippets.ts new file mode 100644 index 000000000..2bcb0dd00 --- /dev/null +++ b/packages/editor-setters/src/json-setter/locale/snippets.ts @@ -0,0 +1,242 @@ +export default [ + { + label: 'constants', + kind: 'Class', + insertText: 'constants', + detail: '应用全局常量', + documentation: '应用范围定义的通用常量', + }, + { + label: 'utils', + kind: 'Class', + insertText: 'utils', + detail: '应用全局公共函数', + documentation: '应用范围扩展的公共函数', + }, + { + label: 'state', + kind: 'Enum', + insertText: 'state', + detail: '当前所在容器组件内部状态', + documentation: 'React Class内部状态state', + }, + { + label: 'setState', + kind: 'Function', + insertText: 'setState({\n\t$0\n})', + insertTextRules: 'InsertAsSnippet', + detail: '设置当前所在容器组件的state数据', + documentation: '原生React方法,会自动更新组件视图', + }, + { + label: 'reloadDataSource', + kind: 'Function', + insertText: 'reloadDataSource(${1:${2:namespace}, ${3:false}, ${4:callback}})', + insertTextRules: 'InsertAsSnippet', + detail: '刷新当前所在的容器组件', + documentation: '触发当前所在的容器组件,重新发送异步请求,并用最新数据更新视图', + }, + { + label: 'location', + kind: 'Class', + insertText: 'location', + detail: '路由解析对象', + }, + { + label: 'location.query', + kind: 'Value', + insertText: 'location.query.${1:xxxx}', + insertTextRules: 'InsertAsSnippet', + detail: '从路由解析对象中获取参数信息', + }, + { + label: 'history', + kind: 'Class', + insertText: 'history', + detail: '路由历史对象', + }, + { + label: 'React', + kind: 'Keyword', + insertText: 'React', + detail: 'React对象', + }, + { + label: 'ReactDOM', + kind: 'Keyword', + insertText: 'ReactDOM', + detail: 'ReactDom对象', + }, + { + label: 'ReactDOM.findDOMNode', + kind: 'Function', + insertText: 'ReactDOM.findDOMNode(${1:this.refs.xxxx})', + insertTextRules: 'InsertAsSnippet', + detail: 'ReactDom查找真实dom node', + }, + { + label: 'Dialog.alert', + kind: 'Method', + insertText: [ + 'Dialog.alert({', + "\tcontent: '${1:Alert content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: 'alert弹框 By Fusion', + }, + { + label: 'Dialog.confirm', + kind: 'Method', + insertText: [ + 'Dialog.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Fusion', + }, + { + label: 'Message.success', + kind: 'Method', + insertText: 'Message.success(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '成功反馈提示 By Fusion', + }, + { + label: 'Message.error', + kind: 'Method', + insertText: 'Message.error(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '错误反馈提示 By Fusion', + }, + { + label: 'Message.help', + kind: 'Method', + insertText: 'Message.help(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '帮助反馈提示 By Fusion', + }, + { + label: 'Message.loading', + kind: 'Method', + insertText: 'Message.loading(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: 'loading反馈提示 By Fusion', + }, + { + label: 'Message.notice', + kind: 'Method', + insertText: 'Message.notice(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '注意反馈提示 By Fusion', + }, + { + label: 'Message.waining', + kind: 'Method', + insertText: 'Message.waining(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '警告反馈提示 By Fusion', + }, + { + label: 'Modal.confirm', + kind: 'Method', + insertText: [ + 'Modal.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Antd', + }, + { + label: 'Modal.info', + kind: 'Method', + insertText: [ + 'Modal.info({', + "\tcontent: '${1:Info content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '信息弹出框 By Antd', + }, + { + label: 'Modal.success', + kind: 'Method', + insertText: [ + 'Modal.success({', + "\tcontent: '${1:Success content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '成功弹出框 By Antd', + }, + { + label: 'Modal.error', + kind: 'Method', + insertText: [ + 'Modal.error({', + "\tcontent: '${1:Error content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '错误弹出框 By Antd', + }, + { + label: 'Modal.warning', + kind: 'Method', + insertText: [ + 'Modal.warning({', + "\tcontent: '${1:Warning content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '警告弹出框 By Antd', + }, +]; diff --git a/packages/editor-setters/src/json-setter/locale/utils.ts b/packages/editor-setters/src/json-setter/locale/utils.ts new file mode 100644 index 000000000..d401ad511 --- /dev/null +++ b/packages/editor-setters/src/json-setter/locale/utils.ts @@ -0,0 +1,21 @@ +import IntlMessageFormat from 'intl-messageformat'; + +export const isJSExpression = (obj = '') => { + if (obj && typeof obj === 'object' && obj.type === 'JSExpression') { + return true; + } + return false; +}; + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export const generateI18n = (locale = 'zh-CN', messages = {}) => { + return function (key, values = {}) { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +}; diff --git a/packages/editor-setters/src/json-setter/locale/zh-CN.ts b/packages/editor-setters/src/json-setter/locale/zh-CN.ts new file mode 100644 index 000000000..f4f2e0630 --- /dev/null +++ b/packages/editor-setters/src/json-setter/locale/zh-CN.ts @@ -0,0 +1,36 @@ +export default { + // function + setting: '点击设置', + edit: '编辑', + submitConfirm: '确认提交 cmd+s', + close: '关闭 esc', + fullScreen: '全屏', + cancelFullScreen: '取消全屏', + jsonIllegal: '非json格式', + functionIllegal: '非function格式', + objectIllegal: '非object格式', + circularRef: '对象中出现循环引用的对象', + formatError: '格式错误', + saved: '已保存', + // expression + valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑', + jsExpression: '请输入JS表达式', + // Mixin + input: '字符串Input', + textarea: '多行字符串Textarea', + expression: '变量控件Expression', + monacoEditor: '编辑器MonacoEditor', + numberPicker: '数字NumberPicker', + bool: '布尔Switch', + datePicker: '日期选择DatePicker', + select: '下拉选择Select', + radio: '单项选择RadioGroup', + date: '日期选择DatePicker', + dateYear: '年选择DatePicker', + dateMonth: '月选择DatePicker', + dateRange: '日期区间选择DatePicker', + list: '数组List', + object: '对象ObjectButton', + reactNode: '节点类型ReactNode', + typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置', +}; diff --git a/packages/editor-setters/src/locale/snippets.ts b/packages/editor-setters/src/locale/snippets.ts new file mode 100644 index 000000000..2bcb0dd00 --- /dev/null +++ b/packages/editor-setters/src/locale/snippets.ts @@ -0,0 +1,242 @@ +export default [ + { + label: 'constants', + kind: 'Class', + insertText: 'constants', + detail: '应用全局常量', + documentation: '应用范围定义的通用常量', + }, + { + label: 'utils', + kind: 'Class', + insertText: 'utils', + detail: '应用全局公共函数', + documentation: '应用范围扩展的公共函数', + }, + { + label: 'state', + kind: 'Enum', + insertText: 'state', + detail: '当前所在容器组件内部状态', + documentation: 'React Class内部状态state', + }, + { + label: 'setState', + kind: 'Function', + insertText: 'setState({\n\t$0\n})', + insertTextRules: 'InsertAsSnippet', + detail: '设置当前所在容器组件的state数据', + documentation: '原生React方法,会自动更新组件视图', + }, + { + label: 'reloadDataSource', + kind: 'Function', + insertText: 'reloadDataSource(${1:${2:namespace}, ${3:false}, ${4:callback}})', + insertTextRules: 'InsertAsSnippet', + detail: '刷新当前所在的容器组件', + documentation: '触发当前所在的容器组件,重新发送异步请求,并用最新数据更新视图', + }, + { + label: 'location', + kind: 'Class', + insertText: 'location', + detail: '路由解析对象', + }, + { + label: 'location.query', + kind: 'Value', + insertText: 'location.query.${1:xxxx}', + insertTextRules: 'InsertAsSnippet', + detail: '从路由解析对象中获取参数信息', + }, + { + label: 'history', + kind: 'Class', + insertText: 'history', + detail: '路由历史对象', + }, + { + label: 'React', + kind: 'Keyword', + insertText: 'React', + detail: 'React对象', + }, + { + label: 'ReactDOM', + kind: 'Keyword', + insertText: 'ReactDOM', + detail: 'ReactDom对象', + }, + { + label: 'ReactDOM.findDOMNode', + kind: 'Function', + insertText: 'ReactDOM.findDOMNode(${1:this.refs.xxxx})', + insertTextRules: 'InsertAsSnippet', + detail: 'ReactDom查找真实dom node', + }, + { + label: 'Dialog.alert', + kind: 'Method', + insertText: [ + 'Dialog.alert({', + "\tcontent: '${1:Alert content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: 'alert弹框 By Fusion', + }, + { + label: 'Dialog.confirm', + kind: 'Method', + insertText: [ + 'Dialog.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Fusion', + }, + { + label: 'Message.success', + kind: 'Method', + insertText: 'Message.success(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '成功反馈提示 By Fusion', + }, + { + label: 'Message.error', + kind: 'Method', + insertText: 'Message.error(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '错误反馈提示 By Fusion', + }, + { + label: 'Message.help', + kind: 'Method', + insertText: 'Message.help(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '帮助反馈提示 By Fusion', + }, + { + label: 'Message.loading', + kind: 'Method', + insertText: 'Message.loading(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: 'loading反馈提示 By Fusion', + }, + { + label: 'Message.notice', + kind: 'Method', + insertText: 'Message.notice(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '注意反馈提示 By Fusion', + }, + { + label: 'Message.waining', + kind: 'Method', + insertText: 'Message.waining(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '警告反馈提示 By Fusion', + }, + { + label: 'Modal.confirm', + kind: 'Method', + insertText: [ + 'Modal.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Antd', + }, + { + label: 'Modal.info', + kind: 'Method', + insertText: [ + 'Modal.info({', + "\tcontent: '${1:Info content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '信息弹出框 By Antd', + }, + { + label: 'Modal.success', + kind: 'Method', + insertText: [ + 'Modal.success({', + "\tcontent: '${1:Success content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '成功弹出框 By Antd', + }, + { + label: 'Modal.error', + kind: 'Method', + insertText: [ + 'Modal.error({', + "\tcontent: '${1:Error content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '错误弹出框 By Antd', + }, + { + label: 'Modal.warning', + kind: 'Method', + insertText: [ + 'Modal.warning({', + "\tcontent: '${1:Warning content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})', + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '警告弹出框 By Antd', + }, +]; diff --git a/packages/editor-setters/src/locale/utils.ts b/packages/editor-setters/src/locale/utils.ts new file mode 100644 index 000000000..d401ad511 --- /dev/null +++ b/packages/editor-setters/src/locale/utils.ts @@ -0,0 +1,21 @@ +import IntlMessageFormat from 'intl-messageformat'; + +export const isJSExpression = (obj = '') => { + if (obj && typeof obj === 'object' && obj.type === 'JSExpression') { + return true; + } + return false; +}; + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export const generateI18n = (locale = 'zh-CN', messages = {}) => { + return function (key, values = {}) { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +}; diff --git a/packages/editor-setters/src/locale/zh-CN.ts b/packages/editor-setters/src/locale/zh-CN.ts new file mode 100644 index 000000000..f4f2e0630 --- /dev/null +++ b/packages/editor-setters/src/locale/zh-CN.ts @@ -0,0 +1,36 @@ +export default { + // function + setting: '点击设置', + edit: '编辑', + submitConfirm: '确认提交 cmd+s', + close: '关闭 esc', + fullScreen: '全屏', + cancelFullScreen: '取消全屏', + jsonIllegal: '非json格式', + functionIllegal: '非function格式', + objectIllegal: '非object格式', + circularRef: '对象中出现循环引用的对象', + formatError: '格式错误', + saved: '已保存', + // expression + valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑', + jsExpression: '请输入JS表达式', + // Mixin + input: '字符串Input', + textarea: '多行字符串Textarea', + expression: '变量控件Expression', + monacoEditor: '编辑器MonacoEditor', + numberPicker: '数字NumberPicker', + bool: '布尔Switch', + datePicker: '日期选择DatePicker', + select: '下拉选择Select', + radio: '单项选择RadioGroup', + date: '日期选择DatePicker', + dateYear: '年选择DatePicker', + dateMonth: '月选择DatePicker', + dateRange: '日期区间选择DatePicker', + list: '数组List', + object: '对象ObjectButton', + reactNode: '节点类型ReactNode', + typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置', +}; diff --git a/packages/editor-setters/src/mixed-setter/index.tsx b/packages/editor-setters/src/mixed-setter/index.tsx new file mode 100644 index 000000000..f99c1bec3 --- /dev/null +++ b/packages/editor-setters/src/mixed-setter/index.tsx @@ -0,0 +1,161 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { Dropdown, Button, Menu, Icon } from '@alifd/next'; +import { getSetter } from '@ali/lowcode-editor-core'; + +import { generateI18n } from './locale/utils'; +import zhCN from './locale/zh-CN'; +import './index.scss'; + +export default class Mixed extends PureComponent { + static displayName = 'Mixed'; + + static propTypes = { + locale: PropTypes.string, + messages: PropTypes.object, + defaultType: PropTypes.string, + types: PropTypes.arrayOf(PropTypes.string), + inputProps: PropTypes.object, + expressionProps: PropTypes.object, + monacoEditorProps: PropTypes.object, + switchProps: PropTypes.object, + selectProps: PropTypes.object, + radioGroupProps: PropTypes.object, + }; + + static defaultProps = { + locale: 'zh-CN', + messages: zhCN, + types: [ + { + name: 'StringSetter', + props: {}, + }, + ], + }; + + typeMap: any; + + i18n: (key: any, values) => string | void | Array; + + constructor(props: Readonly) { + super(props); + const type = props.defaultType; // judgeTypeHandler(props, {}); + this.i18n = generateI18n(props.locale, props.messages); + this.state = { + type, + }; + } + + changeType(type: string) { + if (typeof type === 'object' || type === this.state.type) return; + const { onChange } = this.props; + let newValue; + const setterProps = this.typeMap[type].props; + if (setterProps) { + if (setterProps.value !== undefined) { + newValue = setterProps.value; + } else if (setterProps.defaultValue !== undefined) { + newValue = setterProps.defaultValue; + } + } + if (type === 'BoolSetter' && newValue === undefined) { + newValue = false; // 给切换到switch默认值为false + } + this.setState({ type }); + onChange && onChange(newValue); + } + + render() { + const { style = {}, className, locale, messages, types = [], defaultType, ...restProps } = this.props; + this.typeMap = {}; + const realTypes: any[] = []; + types.forEach((el: { name: any; props: any }) => { + const { name, props } = el; + const Setter = getSetter(name); + if (Setter) { + this.typeMap[name] = { + label: name, + component: Setter.component, + props, + }; + } + realTypes.push(name); + }); + let moreBtnNode = null; + // 如果只有2种,且有变量表达式,则直接展示变量按钮 + if (realTypes.length > 1) { + const isTwoType = !!(realTypes.length === 2 && ~realTypes.indexOf('ExpressionSetter')); + const btnProps = { + size: 'small', + text: true, + style: { + position: 'absolute', + left: '100%', + top: 0, + bottom: 0, + margin: 'auto 0 auto 8px', + padding: 0, + width: 16, + height: 16, + lineHeight: '16px', + textAlign: 'center', + }, + }; + if (isTwoType) { + btnProps.onClick = this.changeType.bind(this, realTypes.indexOf(this.state.type) ? realTypes[0] : realTypes[1]); + } + const triggerNode = ( + + ); + if (isTwoType) { + moreBtnNode = triggerNode; + } else { + // eslint-disable-next-line @typescript-eslint/ban-types + const MenuItems: {} | null | undefined = []; + realTypes.map((type) => { + if (this.typeMap[type]) { + MenuItems.push({this.typeMap[type].label}); + } else { + console.error( + this.i18n('typeError', { + type, + }), + ); + } + return type; + }); + const MenuNode = ( + + {MenuItems} + + ); + + moreBtnNode = ( + + {MenuNode} + + ); + } + } + const TargetNode = this.typeMap[this.state.type]?.component || 'div'; + const targetProps = this.typeMap[this.state.type]?.props || {}; + const tarStyle = { position: 'relative', ...style }; + const classes = classNames(className, 'lowcode-setter-mixin'); + + return ( +
+ + {moreBtnNode} +
+ ); + } +} diff --git a/packages/editor-setters/src/mixed-setter/locale/utils.ts b/packages/editor-setters/src/mixed-setter/locale/utils.ts new file mode 100644 index 000000000..d401ad511 --- /dev/null +++ b/packages/editor-setters/src/mixed-setter/locale/utils.ts @@ -0,0 +1,21 @@ +import IntlMessageFormat from 'intl-messageformat'; + +export const isJSExpression = (obj = '') => { + if (obj && typeof obj === 'object' && obj.type === 'JSExpression') { + return true; + } + return false; +}; + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export const generateI18n = (locale = 'zh-CN', messages = {}) => { + return function (key, values = {}) { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +}; diff --git a/packages/editor-setters/src/mixed-setter/locale/zh-CN.ts b/packages/editor-setters/src/mixed-setter/locale/zh-CN.ts new file mode 100644 index 000000000..f4f2e0630 --- /dev/null +++ b/packages/editor-setters/src/mixed-setter/locale/zh-CN.ts @@ -0,0 +1,36 @@ +export default { + // function + setting: '点击设置', + edit: '编辑', + submitConfirm: '确认提交 cmd+s', + close: '关闭 esc', + fullScreen: '全屏', + cancelFullScreen: '取消全屏', + jsonIllegal: '非json格式', + functionIllegal: '非function格式', + objectIllegal: '非object格式', + circularRef: '对象中出现循环引用的对象', + formatError: '格式错误', + saved: '已保存', + // expression + valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑', + jsExpression: '请输入JS表达式', + // Mixin + input: '字符串Input', + textarea: '多行字符串Textarea', + expression: '变量控件Expression', + monacoEditor: '编辑器MonacoEditor', + numberPicker: '数字NumberPicker', + bool: '布尔Switch', + datePicker: '日期选择DatePicker', + select: '下拉选择Select', + radio: '单项选择RadioGroup', + date: '日期选择DatePicker', + dateYear: '年选择DatePicker', + dateMonth: '月选择DatePicker', + dateRange: '日期区间选择DatePicker', + list: '数组List', + object: '对象ObjectButton', + reactNode: '节点类型ReactNode', + typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置', +}; diff --git a/packages/editor-setters/src/style-setter/index.tsx b/packages/editor-setters/src/style-setter/index.tsx new file mode 100644 index 000000000..004fe2bf1 --- /dev/null +++ b/packages/editor-setters/src/style-setter/index.tsx @@ -0,0 +1,37 @@ +import './style.less'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import LowStyleSetter from '@ali/lc-style-setter'; +import { globalLocale } from '@ali/lowcode-editor-core'; + +export default class StyleSetter extends Component { + static displayName = 'StyleSetter'; + + static propTypes = { + value: PropTypes.object, + onChange: PropTypes.func, + placeholder: PropTypes.string, + locale: PropTypes.string, + }; + + static defaultProps = { + value: {}, + onChange: () => { }, + placeholder: '', + locale: globalLocale.getLocale() || 'en-US', + }; + + onChange = (val: any) => { + const { onChange } = this.props; + onChange(val.native); + }; + + render() { + const { value } = this.props; + return ( +
+ +
+ ); + } +} diff --git a/packages/editor-skeleton/.eslintrc.js b/packages/editor-skeleton/.eslintrc.js index 0430ed0a5..d74532d8f 100644 --- a/packages/editor-skeleton/.eslintrc.js +++ b/packages/editor-skeleton/.eslintrc.js @@ -1,7 +1,7 @@ module.exports = { - extends: '../../.eslintrc.js', + extends: 'eslint-config-ali/typescript/react', rules: { - 'react/no-multi-comp': 1, + 'react/no-multi-comp': 0, 'no-unused-expressions': 1, 'implicit-arrow-linebreak': 1, 'no-nested-ternary': 1, @@ -12,5 +12,7 @@ module.exports = { 'no-prototype-builtins': 1, 'no-confusing-arrow': 1, 'no-case-declarations': 1, + 'lines-between-class-members': 0, + '@typescript-eslint/member-ordering': 0, } } \ No newline at end of file diff --git a/packages/editor-skeleton/CHANGELOG.md b/packages/editor-skeleton/CHANGELOG.md index f8b97cb93..07a2dc658 100644 --- a/packages/editor-skeleton/CHANGELOG.md +++ b/packages/editor-skeleton/CHANGELOG.md @@ -93,24 +93,84 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline * fix array and sourceeditor bugs ([26b8b2c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26b8b2c)) * fix bug ([113e409](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/113e409)) * remove wrong propType match ([73e69fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/73e69fa)) +* **editor-skeleton:** fix dynamic setter support in mixed-setter ([fca10ac](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fca10ac)) - -## [1.0.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@1.0.10...@ali/lowcode-editor-skeleton@1.0.11) (2020-10-19) + +## [0.13.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-3...v0.13.1-1) (2020-10-12) **Note:** Version bump only for package @ali/lowcode-editor-skeleton + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + + +## [0.12.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-9...v0.12.1-1) (2020-09-22) + + + + +<<<<<<< HEAD + +## [1.0.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@1.0.10...@ali/lowcode-editor-skeleton@1.0.11) (2020-10-19) +======= +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + + +## [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) +>>>>>>> origin/refactor/vision-code-split + + + + +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + +<<<<<<< HEAD ## [1.0.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@1.0.9...@ali/lowcode-editor-skeleton@1.0.10) (2020-09-29) +======= + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + + +## [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) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + + +## [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) +>>>>>>> origin/refactor/vision-code-split ### Bug Fixes +<<<<<<< HEAD * **editor-skeleton:** fix dynamic setter support in mixed-setter ([1726354](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1726354)) @@ -118,20 +178,117 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ## [1.0.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@1.0.8...@ali/lowcode-editor-skeleton@1.0.9) (2020-09-28) +======= +* 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)) + + + + + +## [1.0.9-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-1...v1.0.9-2) (2020-09-14) +>>>>>>> origin/refactor/vision-code-split **Note:** Version bump only for package @ali/lowcode-editor-skeleton +<<<<<<< HEAD ## [1.0.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@1.0.8-0...@ali/lowcode-editor-skeleton@1.0.8) (2020-09-28) +======= + +## [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) +>>>>>>> origin/refactor/vision-code-split **Note:** Version bump only for package @ali/lowcode-editor-skeleton +<<<<<<< HEAD +======= + +## 1.0.9-0 (2020-09-14) + + +### Bug Fixes + +* 🐛 add tip on setter title ([c93c1d0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c93c1d0)) +* 🐛 history pane zindex ([48f3be1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/48f3be1)) +* 🐛 mainArea 画布切换,MainArea 重新初始化导致 iframe 初始化报错 ([5054d06](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5054d06)) +* 🐛 修复主设置面板下 stagebox 的样式问题 ([d5a98c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d5a98c0)) +* 🐛 修复编辑面板 ([a0bad77](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a0bad77)) +* 🐛 添加 loop 和 condition 的判断 ([b521ebe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b521ebe)) +* 🐛 绑定动作无法打开代码面板 ([160d6f7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/160d6f7)) +* 😈 table 无法选中问题 ([34825f8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/34825f8)) +* 😊修复arraysetter删除不更新问题 ([9d8a730](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9d8a730)) +* condition增加异常保护 ([8324368](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8324368)) +* CR 问题修复 ([f054cbf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f054cbf)) +* fix mixsetter style ([0ecce83](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0ecce83)) +* left-fixed-pane 设置宽度不生效 ([a5f0d5e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5f0d5e)) +* modify docId ([dc95033](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dc95033)) +* panel visible time ([18ac1fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18ac1fa)) +* remove console ([6c703d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6c703d8)) +* remove console ([6889123](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6889123)) +* set i18n setter value when change mixed setter ([72d81c2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/72d81c2)) +* setter 报错不影响页面渲染 ([c0a6022](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c0a6022)) +* setting pane tab active ([06d7b50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/06d7b50)) +* setting 面板样式调整 ([922b361](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/922b361)) +* skeleton.topArea.hide() 不生效的问题 ([6d2b955](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d2b955)) +* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c)) +* topbar search icon ([0447801](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0447801)) +* typeName 为 any 时转换出的 MixedSetter 缺少 props 的问题 ([4b9084f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4b9084f)) +* vision 大包 window 指向问题 ([aa1b526](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aa1b526)) +* 修复 toolbar 弹出位置异常 ([b40b9a4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40b9a4)) +* 修复bool类型对应的setter ([2df6230](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2df6230)) +* 修复低代码组件设计器、区块设计器根节点为 Page 的问题,修复 topArea 样式 ([e85b542](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e85b542)) +* 兼容modal模式 ([1092ee9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1092ee9)) +* 兼容vision体系代码面板中引用计数功能 ([8ade6d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8ade6d8)) +* 兼容小程序面板的特殊情况 ([3c686ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3c686ab)) +* 区块模板切换之后数据不显示 ([292c1c3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/292c1c3)) +* 右侧配置面板样式修复 ([05f62da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05f62da)) +* 右侧配置面板面包屑点击无效 ([353fb10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/353fb10)) +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 在Transducer中添加对MixedSetter的支持 ([7317f2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7317f2f)) +* 多选时设置项异常 ([8cc9d73](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8cc9d73)) +* 导入的组件默认怎么变量绑定 ([194d8d8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/194d8d8)) +* 导入的组件默认怎么变量绑定 ([fc398c2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fc398c2)) +* **editor-skeleton:** add canSetFixed prop to panel config ([1b57d5c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1b57d5c)) +* 禁止组件拉到 Page 的直接子节点, 以及替换 tab 组件 ([d93a291](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d93a291)) +* 解决点击数据源,自动隐藏的问题 ([7dcd61c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7dcd61c)) +* 调整保存成功弹出框位置 ([5198dae](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5198dae)) +* 钉住行为调整 ([91a390e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/91a390e)) + + +### Features + +* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c286)) +* add URL link for setter titles ([4678408](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4678408)) +* left pane style ([c149f64](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c149f64)) +* left pane title style; setting pane style ([66e8c25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66e8c25)) +* merge live mode ([92c3039](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/92c3039)) +* panel增加自动埋点 ([afc7758](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/afc7758)) +* register-defaults 改为可选项 ([2195797](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2195797)) +* run vision polyfill ([33750b7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/33750b7)) +* setting-pane 新增removeProp 函数 ([b97c807](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b97c807)) +* show value state ([bd49e50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd49e50)) +* support float pane fixed ([40d8260](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/40d8260)) +* 主设置面板里深层次界面通过 stagebox 进行过渡 ([783e945](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/783e945)) +* 引擎层埋点 ([69de533](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/69de533)) +* 支持 entry 模式 ([fe1f6f1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fe1f6f1)) +* 新增事件入参功能 ([0614fa7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0614fa7)) +* 自动埋点 ([fecf34d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fecf34d)) + + + + +>>>>>>> origin/refactor/vision-code-split ## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.61...@ali/lowcode-editor-skeleton@1.0.8-0) (2020-09-09) diff --git a/packages/editor-skeleton/src/area.ts b/packages/editor-skeleton/src/area.ts index 6ff3b6e07..d2de6cecf 100644 --- a/packages/editor-skeleton/src/area.ts +++ b/packages/editor-skeleton/src/area.ts @@ -66,4 +66,12 @@ export default class Area { }; render() { - const { content, visible, title, actionKey, offsetX } = this.state; + const { content, visible, title, actionKey, pos, offsetX } = this.state; if (!visible) { return null; } + let avoidLaterHidden = true; + setTimeout(() => { + avoidLaterHidden = false; + }, 10); const id = uniqueId('ball'); @@ -137,6 +141,15 @@ export class PopupContent extends PureComponent<{ safeId?: string }> { visible={visible} offset={[offsetX, 0]} hasMask={false} + onVisibleChange={(visible, type) => { + if (avoidLaterHidden) { + return; + } + if (!visible && type === 'closeClick') { + this.setState({ visible: false }); + } + }} + trigger={
} triggerType="click" canCloseByOutSideClick={false} animation={false} diff --git a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx index a91354945..24b4e4062 100644 --- a/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx +++ b/packages/editor-skeleton/src/components/settings/settings-primary-pane.tsx @@ -9,7 +9,10 @@ import { SkeletonContext } from '../../context'; import { createIcon } from '@ali/lowcode-utils'; @observer -export class SettingsPrimaryPane extends Component<{ editor: Editor }> { +export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any }, { shouldIgnoreRoot: boolean }> { + state = { + shouldIgnoreRoot: false, + }; private main = new SettingsMain(this.props.editor); @obx.ref private _activeKey?: any; @@ -18,12 +21,26 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> { return false; } + componentDidMount() { + this.setShouldIgnoreRoot(); + } + + async setShouldIgnoreRoot() { + const designMode = await this.props.editor.get('designMode'); + this.setState({ + shouldIgnoreRoot: designMode === 'live', + }); + } + componentWillUnmount() { this.main.purge(); } renderBreadcrumb() { const { settings } = this.main; + const { config } = this.props; + // const shouldIgnoreRoot = config.props?.ignoreRoot; + const { shouldIgnoreRoot } = this.state; if (!settings) { return null; } @@ -32,7 +49,7 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> {
{createIcon(settings.componentMeta?.icon, { className: 'lc-settings-navigator-icon' })} - <span>x {settings.nodes.length}</span> + <span> x {settings.nodes.length}</span> </div> ); } @@ -45,6 +62,10 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> { let l = 3; while (l-- > 0 && node) { const _node = node; + if (shouldIgnoreRoot && node.isRoot()) { + node = null; + continue; + } const props = l === 2 ? {} @@ -99,6 +120,16 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor }> { ); } + if (Array.isArray(settings.items) && settings.items.length === 0) { + return ( + <div className="lc-settings-main"> + <div className="lc-settings-notice"> + <p>该组件暂无配置</p> + </div> + </div> + ); + } + if (!settings.isSameComponent) { // todo: future support 获取设置项交集编辑 return ( diff --git a/packages/editor-skeleton/src/components/stage-box/index.less b/packages/editor-skeleton/src/components/stage-box/index.less index bd24f2933..a4df85cb8 100644 --- a/packages/editor-skeleton/src/components/stage-box/index.less +++ b/packages/editor-skeleton/src/components/stage-box/index.less @@ -41,10 +41,11 @@ .skeleton-stagebox-stage-arrow { position: absolute; - left: 3px; + left: 8px; top: 50%; transform: translateY(-50%) rotate(90deg); opacity: 0.6; + width: 12px; } .skeleton-stagebox-stage-title { font-weight: bold; @@ -57,7 +58,7 @@ } .skeleton-stagebox-stage-exit { position: absolute; - right: 3px; + right: 8px; top: 50%; transform: translateY(-50%); opacity: 0.6; diff --git a/packages/editor-skeleton/src/layouts/left-area.tsx b/packages/editor-skeleton/src/layouts/left-area.tsx index bff3f500d..6f8831954 100644 --- a/packages/editor-skeleton/src/layouts/left-area.tsx +++ b/packages/editor-skeleton/src/layouts/left-area.tsx @@ -26,7 +26,7 @@ class Contents extends Component<{ area: Area }> { const top: any[] = []; const bottom: any[] = []; area.container.items.forEach((item) => { - const content = <div id={`left-area-${item.name}`}>{item.content}</div>; + const content = <div key={`left-area-${item.name}`}>{item.content}</div>; if (item.align === 'bottom') { bottom.push(content); } else { diff --git a/packages/editor-skeleton/src/layouts/top-area.tsx b/packages/editor-skeleton/src/layouts/top-area.tsx index e28c729ca..db8da436b 100644 --- a/packages/editor-skeleton/src/layouts/top-area.tsx +++ b/packages/editor-skeleton/src/layouts/top-area.tsx @@ -31,7 +31,7 @@ class Contents extends Component<{ area: Area, itemClassName?: string }> { return index1 === index2 ? 0 : (index1 > index2 ? 1 : -1); }).forEach(item => { const content = ( - <div className={itemClassName || ''} id={`top-area-${item.name}`}> + <div className={itemClassName || ''} key={`top-area-${item.name}`}> {item.content} </div> ); diff --git a/packages/editor-skeleton/src/layouts/workbench.less b/packages/editor-skeleton/src/layouts/workbench.less index a0e830364..a20d84714 100644 --- a/packages/editor-skeleton/src/layouts/workbench.less +++ b/packages/editor-skeleton/src/layouts/workbench.less @@ -16,8 +16,8 @@ --right-area-width: 300px; --top-area-height: 48px; --toolbar-height: 36px; - --dock-pane-width: 280px; - --dock-fixed-pane-width: 280px; + --dock-pane-width: 300px; + --dock-fixed-pane-width: 300px; } @media (min-width: 1860px) { diff --git a/packages/editor-skeleton/src/layouts/workbench.tsx b/packages/editor-skeleton/src/layouts/workbench.tsx index 0d5d65379..ec9bc965d 100644 --- a/packages/editor-skeleton/src/layouts/workbench.tsx +++ b/packages/editor-skeleton/src/layouts/workbench.tsx @@ -26,6 +26,10 @@ export class Workbench extends Component<{ skeleton: Skeleton; config?: EditorCo return false; } + // componentDidCatch(error: any) { + // globalContext.get(Editor).emit('editor.skeleton.workbench.error', error); + // } + render() { const { skeleton, className, topAreaItemClassName } = this.props; return ( diff --git a/packages/editor-skeleton/src/skeleton.ts b/packages/editor-skeleton/src/skeleton.ts index c8feb4c19..51ddb3f63 100644 --- a/packages/editor-skeleton/src/skeleton.ts +++ b/packages/editor-skeleton/src/skeleton.ts @@ -350,6 +350,8 @@ export class Skeleton { return this.leftFloatArea.add(parsedConfig); case 'stages': return this.stages.add(parsedConfig); + default: + // do nothing } } } diff --git a/packages/editor-skeleton/src/transducers/addon-combine.ts b/packages/editor-skeleton/src/transducers/addon-combine.ts index 48146cdca..f20a488bb 100644 --- a/packages/editor-skeleton/src/transducers/addon-combine.ts +++ b/packages/editor-skeleton/src/transducers/addon-combine.ts @@ -330,7 +330,7 @@ export default function (metadata: TransformedComponentMetadata): TransformedCom if (advanceGroup.length > 0) { combined.push({ name: '#advanced', - title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' }, + title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advanced' }, items: advanceGroup, }); } diff --git a/packages/editor-skeleton/src/widget/panel-dock.ts b/packages/editor-skeleton/src/widget/panel-dock.ts index 9c52f483c..150d1a71b 100644 --- a/packages/editor-skeleton/src/widget/panel-dock.ts +++ b/packages/editor-skeleton/src/widget/panel-dock.ts @@ -141,6 +141,13 @@ export default class PanelDock implements IWidget { showPanel() { this.panel?.setActive(true); } + + /** + * @deprecated + */ + onActiveChange(func: () => any) { + return this.panel?.onActiveChange(func); + } } diff --git a/packages/editor-skeleton/src/widget/utils.ts b/packages/editor-skeleton/src/widget/utils.ts index f768e4fc3..bd680c202 100644 --- a/packages/editor-skeleton/src/widget/utils.ts +++ b/packages/editor-skeleton/src/widget/utils.ts @@ -38,7 +38,9 @@ export function composeTitle(title?: TitleContent, icon?: IconType, tip?: TipCon } } if (isTitleConfig(title) && noIcon) { - title.icon = undefined; + if (!isValidElement(title)) { + title.icon = undefined; + } } return title; } diff --git a/packages/plugin-datasource-pane/src/classname-setter/index.tsx b/packages/plugin-datasource-pane/src/classname-setter/index.tsx new file mode 100644 index 000000000..76f649059 --- /dev/null +++ b/packages/plugin-datasource-pane/src/classname-setter/index.tsx @@ -0,0 +1,90 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { Select } from '@alife/next'; + +interface Color { + rgb: any; + onChange: () => void; +} + +export interface PluginProps { + value: string; + onChange: any; +} + +export default class ClassNameView extends PureComponent<PluginProps> { + static display = 'ClassName'; + + static propTypes = { + onChange: PropTypes.func, + value: PropTypes.string, + }; + + static defaultProps = { + onChange: () => {}, + value: '', + }; + + getClassNameList = () => { + const { editor } = this.props.field; + const schema = editor.get('designer').project.getSchema(); + const css = schema.componentsTree[0].css; + const classNameList = []; + const re = /\.?\w+[^{]+\{[^}]*\}/g; + const list = css.match(re); + list.map((item) => { + if (item[0] === '.') { + const className = item.substring(1, item.indexOf('{')); + classNameList.push(className); + } + + return item; + }); + + return classNameList; + }; + + + handleChange = (value) => { + const { onChange } = this.props; + onChange(value.join(' ')); + this.setState({ + selectValue: value, + }); + }; + + // eslint-disable-next-line react/no-deprecated + componentWillMount() { + const { value } = this.props; + const classnameList = this.getClassNameList(); + const dataSource = []; + classnameList.map((item) => { + dataSource.push({ + value: item, + label: item, + }); + + return item; + }); + + + let selectValue = []; + if (value && value !== '') { + selectValue = value.split(' '); + } + + + this.setState({ + dataSource, + selectValue, + }); + } + + + render() { + const { dataSource, selectValue } = this.state; + return ( + <Select aria-label="tag mode" mode="tag" dataSource={dataSource} onChange={this.handleChange} value={selectValue} /> + ); + } +} diff --git a/packages/rax-render/.eslintrc.js b/packages/rax-render/.eslintrc.js index 235dc8230..986ff5f25 100644 --- a/packages/rax-render/.eslintrc.js +++ b/packages/rax-render/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - extends: '../../.eslintrc.js', + extends: 'eslint-config-ali/typescript/react', rules: { 'no-proto': 1, 'no-new-func': 1, diff --git a/packages/rax-render/CHANGELOG.md b/packages/rax-render/CHANGELOG.md index 4440fd661..718183bdd 100644 --- a/packages/rax-render/CHANGELOG.md +++ b/packages/rax-render/CHANGELOG.md @@ -121,6 +121,32 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Bug Fixes * get pakcage.json ([8b99a51](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8b99a51)) +* div 不显示问题 ([1b6533c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1b6533c)) +* **rax-render:** hidden无效 ([08a3e36](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/08a3e36)) +* get pakcage.json ([8b99a51](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8b99a51)) +* miniapp compwrapper ref ([5ae08f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5ae08f5)) +* VisualEngine 仍使用 ifframe 中 window 对象 ([9d19731](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9d19731)) +* window.parent ([7e1b8ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e1b8ff)) +* 修复低代码组件设计器、区块设计器根节点为 Page 的问题,修复 topArea 样式 ([e85b542](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e85b542)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) + + +### Features + +* $ method ([cf50292](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/cf50292)) +* init rax-render ([7167767](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7167767)) +* 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)) +* rax render ([95bf331](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/95bf331)) +* rax render ([038d74e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/038d74e)) +* rax simulator ([05b262d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05b262d)) +* rax-render 兼容 ([877d3fc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/877d3fc)) +* rax-render 拦截逻辑 & request 调用 webtable(mock) ([42108f6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/42108f6)) +* window._table ([e6cce31](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e6cce31)) +* 抽离AppHelper ([1f6d131](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f6d131)) +* 新增用于小程序跳过 variable 检测设置 hotvalue 的方法 ([ef799eb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ef799eb)) +* 适配 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)) diff --git a/packages/rax-render/package.json b/packages/rax-render/package.json index 3cd0a1244..1ffac730c 100644 --- a/packages/rax-render/package.json +++ b/packages/rax-render/package.json @@ -38,6 +38,7 @@ "@ali/lib-mtop": "^2.5.1", "@ali/lowcode-datasource-engine": "^1.0.22", "@ali/lowcode-utils": "^1.0.21", + "@ali/ui-table": "^1.0.1-beta.6", "classnames": "^2.2.6", "debug": "^4.1.1", "events": "^3.0.0", diff --git a/packages/rax-render/src/engine/base.tsx b/packages/rax-render/src/engine/base.tsx index 9c41d0f82..f097a147f 100644 --- a/packages/rax-render/src/engine/base.tsx +++ b/packages/rax-render/src/engine/base.tsx @@ -60,6 +60,7 @@ export default class BaseEngine extends Component { super(props, context); this.appHelper = props.__appHelper; this.__compScopes = {}; + this.__instanceMap = {}; const { locale, messages } = props; this.i18n = generateI18n(locale, messages); this.__bindCustomMethods(props); @@ -87,6 +88,31 @@ export default class BaseEngine extends Component { console.warn(e); } + reloadDataSource = () => new Promise((resolve, reject) => { + debug('reload data source'); + if (!this.__dataHelper) { + this.__showPlaceholder = false; + return resolve(); + } + this.__dataHelper + .getInitData() + .then((res) => { + this.__showPlaceholder = false; + if (isEmpty(res)) { + this.forceUpdate(); + return resolve(); + } + this.setState(res, resolve); + }) + .catch((err) => { + if (this.__showPlaceholder) { + this.__showPlaceholder = false; + this.forceUpdate(); + } + reject(err); + }); + }); + __setLifeCycleMethods = (method, args) => { const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {}); if (lifeCycleMethods[method]) { @@ -328,6 +354,7 @@ export default class BaseEngine extends Component { Comp = compWrapper(Comp); } otherProps.ref = (ref) => { + this.$(props.fieldId, ref); // 收集ref const refProps = props.ref; if (refProps && typeof refProps === 'string') { this[refProps] = ref; @@ -540,6 +567,17 @@ export default class BaseEngine extends Component { return checkProps(props); }; + $(filedId, instance) { + this.__instanceMap = this.__instanceMap || {}; + if (!filedId) { + return this.__instanceMap; + } + if (instance) { + this.__instanceMap[filedId] = instance; + } + return this.__instanceMap[filedId]; + } + get utils() { return this.appHelper && this.appHelper.utils; } diff --git a/packages/rax-render/src/engine/pageEngine.tsx b/packages/rax-render/src/engine/pageEngine.tsx index 7ec4e344e..82b1cb22e 100644 --- a/packages/rax-render/src/engine/pageEngine.tsx +++ b/packages/rax-render/src/engine/pageEngine.tsx @@ -1,5 +1,4 @@ // @ts-nocheck - import { createElement } from 'rax'; import PropTypes from 'prop-types'; import Debug from 'debug'; diff --git a/packages/rax-render/src/hoc/compWrapper.tsx b/packages/rax-render/src/hoc/compWrapper.tsx index 1d19663a8..e90eaabf4 100644 --- a/packages/rax-render/src/hoc/compWrapper.tsx +++ b/packages/rax-render/src/hoc/compWrapper.tsx @@ -1,13 +1,23 @@ // @ts-nocheck - -import { createElement, Component } from 'rax'; +import { createElement, Component, forwardRef } from 'rax'; export default function (Comp) { - return class CompWrapper extends Component { + class compWrapper extends Component { + // eslint-disable-next-line no-useless-constructor + constructor(props, context) { + super(props, context); + } + render() { + const { forwardRef } = this.props; return createElement(Comp, { ...this.props, + ref: forwardRef, }); } - }; + } + + return forwardRef((props, ref) => { + return createElement(compWrapper, { ...props, forwardRef: ref }); + }); } diff --git a/packages/rax-render/src/utils/dataHelper.ts b/packages/rax-render/src/utils/dataHelper.ts index b0425348f..7cd6b44d9 100644 --- a/packages/rax-render/src/utils/dataHelper.ts +++ b/packages/rax-render/src/utils/dataHelper.ts @@ -1,7 +1,7 @@ +/* eslint-disable object-curly-newline */ // @ts-nocheck - -import { transformArrayToMap, isJSFunction, transformStringToFunction, clone } from './index'; -import { jsonp, mtop, request, get, post, bzb } from './request'; +import { transformArrayToMap, isJSFunction, transformStringToFunction, clone, comboSkeletonConfig } from './index'; +import { jsonp, mtop, request, get, post, bzb, webTableProxy } from './request'; const DS_STATUS = { INIT: 'init', @@ -36,20 +36,18 @@ export default class DataHelper { this.ajaxList = (config && config.list) || []; const ajaxMap = transformArrayToMap(this.ajaxList, 'id'); // 删除已经移除的接口 - Object.keys(this.ajaxMap).forEach(key => { + Object.keys(this.ajaxMap).forEach((key) => { if (!ajaxMap[key]) { delete this.dataSourceMap[key]; } }); this.ajaxMap = ajaxMap; // 添加未加入到dataSourceMap中的接口 - this.ajaxList.forEach(item => { + this.ajaxList.forEach((item) => { if (!this.dataSourceMap[item.id]) { this.dataSourceMap[item.id] = { status: DS_STATUS.INIT, - load: (...args) => { - return this.getDataSource(item.id, ...args); - }, + load: (...args) => this.getDataSource(item.id, ...args), }; } }); @@ -58,12 +56,11 @@ export default class DataHelper { generateDataSourceMap() { const res = {}; - this.ajaxList.forEach(item => { + this.ajaxList.forEach((item) => { + item.id = item.id || item.name; res[item.id] = { status: DS_STATUS.INIT, - load: (...args) => { - return this.getDataSource(item.id, ...args); - }, + load: (...args) => this.getDataSource(item.id, ...args), }; }); return res; @@ -76,14 +73,14 @@ export default class DataHelper { } getInitData() { - const initSyncData = this.parser(this.ajaxList).filter(item => { + const initSyncData = this.parser(this.ajaxList).filter((item) => { if (item.isInit) { this.dataSourceMap[item.id].status = DS_STATUS.LOADING; return true; } return false; }); - return this.asyncDataHandler(initSyncData).then(res => { + return this.asyncDataHandler(initSyncData).then((res) => { let { dataHandler } = this.config; if (isJSFunction(dataHandler)) { dataHandler = transformStringToFunction(dataHandler.value); @@ -98,6 +95,7 @@ export default class DataHelper { } getDataSource(id, params, otherOptions, callback) { + console.log('load',id); const req = this.parser(this.ajaxMap[id]); const options = req.options || {}; if (typeof otherOptions === 'function') { @@ -119,9 +117,9 @@ export default class DataHelper { Array.isArray(options.params) || Array.isArray(params) ? params || options.params : { - ...options.params, - ...params, - }, + ...options.params, + ...params, + }, headers: { ...options.headers, ...headers, @@ -189,73 +187,73 @@ export default class DataHelper { if (allReq.length === 0) resolve({}); const res = {}; Promise.all( - allReq.map(item => { - return new Promise(resolve => { - const { type, id, dataHandler, options } = item; - const doFetch = (type, options) => { - this.fetchOne(type, options) - .then(async data => { - if (afterRequest) { - this.appHelper.utils.afterRequest(item, data, undefined, async (data, error) => { - await fetchHandler(data, error); - }); - } else { - await fetchHandler(data, undefined); + allReq.map( + (item) => + new Promise((resolve) => { + const { type, id, dataHandler, options } = item; + const fetchHandler = async (data, error) => { + if (type === 'doServer') { + if (!Array.isArray(data)) { + data = [data]; } - }) - .catch(async err => { - if (afterRequest) { - // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.afterRequest(item, undefined, err, async (data, error) => { - await fetchHandler(data, error); - }); - } else { - await fetchHandler(undefined, err); - } - }); - }; - const fetchHandler = async (data, error) => { - if (type === 'doServer') { - if (!Array.isArray(data)) { - data = [data]; + doserList.forEach(async (id, idx) => { + const req = this.ajaxMap[id]; + if (req) { + res[id] = await this.dataHandler(id, req.dataHandler, data && data[idx], error); + this.updateDataSourceMap(id, res[id], error); + } + }); + } else { + res[id] = await this.dataHandler(id, dataHandler, data, error); + this.updateDataSourceMap(id, res[id], error); } - doserList.forEach(async (id, idx) => { - const req = this.ajaxMap[id]; - if (req) { - res[id] = await this.dataHandler(id, req.dataHandler, data && data[idx], error); - this.updateDataSourceMap(id, res[id], error); - } + resolve(); + }; + const doFetch = (type, req) => { + this.fetchOne(type, req) + .then(async (data) => { + console.log(data) + if (afterRequest) { + this.appHelper.utils.afterRequest(item, data, undefined, async (data, error) => { + await fetchHandler(data, error); + }); + } else { + await fetchHandler(data, undefined); + } + }) + .catch(async (err) => { + if (afterRequest) { + // 必须要这么调用,否则beforeRequest中的this会丢失 + this.appHelper.utils.afterRequest(item, undefined, err, async (data, error) => { + await fetchHandler(data, error); + }); + } else { + await fetchHandler(undefined, err); + } + }); + }; + if (type === 'doServer') { + doserList.forEach(item => { + this.dataSourceMap[item].status = DS_STATUS.LOADING; }); } else { - res[id] = await this.dataHandler(id, dataHandler, data, error); - this.updateDataSourceMap(id, res[id], error); + this.dataSourceMap[id].status = DS_STATUS.LOADING; } - resolve(); - }; - - if (type === 'doServer') { - doserList.forEach(item => { - this.dataSourceMap[item].status = DS_STATUS.LOADING; - }); - } else { - this.dataSourceMap[id].status = DS_STATUS.LOADING; - } - // 请求切片 - if (beforeRequest) { - // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.beforeRequest(item, clone(options), options => doFetch(type, options)); - } else { - doFetch(type, options); - } - }); - }), - ) - .then(() => { - resolve(res); - }) - .catch(e => { - reject(e); - }); + // 请求切片 + if (beforeRequest) { + // 必须要这么调用,否则beforeRequest中的this会丢失 + this.appHelper.utils.beforeRequest(item, clone(options), () => doFetch(type, item)); + } else { + doFetch(type, item); + } + }) + ), + ).then(() => { + resolve(res); + }) + .catch((e) => { + reject(e); + }); }); } @@ -267,11 +265,12 @@ export default class DataHelper { try { return await dataHandler.call(this.host, data, error); } catch (e) { - console.error(`[${ id }]单个请求数据处理函数运行出错`, e); + console.error(`[${id}]单个请求数据处理函数运行出错`, e); } } - fetchOne(type, options) { + fetchOne(type, req) { + const { options } = req; // eslint-disable-next-line prefer-const let { uri, method = 'GET', headers, params, ...otherProps } = options; otherProps = otherProps || {}; @@ -287,11 +286,14 @@ export default class DataHelper { headers, ...otherProps, }); + case 'legao': + return webTableProxy(req); default: method = method.toUpperCase(); if (method === 'GET') { - return get(uri, params, headers, otherProps); - } else if (method === 'POST') { + return get(uri || otherProps.url, params, headers, otherProps); + } + if (method === 'POST') { return post(uri, params, headers, otherProps); } return request(uri, method, params, headers, otherProps); diff --git a/packages/rax-render/src/utils/index.ts b/packages/rax-render/src/utils/index.ts index d6498cfe3..ec4f2c450 100644 --- a/packages/rax-render/src/utils/index.ts +++ b/packages/rax-render/src/utils/index.ts @@ -682,6 +682,7 @@ export function parseData(schema, self) { } /* 全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,且均不支持省略this */ +// todo: export function parseExpression(str, self) { try { const contextArr = ['"use strict";', 'var __self = arguments[0];']; diff --git a/packages/rax-render/src/utils/request.ts b/packages/rax-render/src/utils/request.ts index 7b8a289fc..0d47bd0c9 100644 --- a/packages/rax-render/src/utils/request.ts +++ b/packages/rax-render/src/utils/request.ts @@ -171,3 +171,29 @@ export function bzb(apiCode, params, otherProps = {}) { ...otherProps, }); } + +export async function webTableProxy(req) { + const { _table } = window.parent; + const { VisualEngine } = window; + const { Bus } = VisualEngine; + if (_table) { + const { options } = req; + const { params, oneAPIConfig } = options; + const { code } = oneAPIConfig; + const sheetId = oneAPIConfig['x-model']; + const sheet = await _table.find({ id: sheetId }); + const result = await sheet.instance.fetch({ code }, params); + return result; + } + return new Promise((resolve, reject) => { + Bus.emitter.on('table.ready', async (table) => { + const { options } = req; + const { params, oneAPIConfig } = options; + const { code } = oneAPIConfig; + const sheetId = oneAPIConfig['x-model']; + const sheet = await table.find({ id: sheetId }); + const result = await sheet.instance.fetch({ code }, params); + resolve(result); + }); + }); +} diff --git a/packages/rax-render/tsconfig.json b/packages/rax-render/tsconfig.json index e1a57bf23..7e264d1f0 100644 --- a/packages/rax-render/tsconfig.json +++ b/packages/rax-render/tsconfig.json @@ -8,7 +8,8 @@ "strictPropertyInitialization": false, "allowSyntheticDefaultImports": true, "esModuleInterop": true, - "jsx": "preserve", + "jsx": "react", + "jsxFactory": "createElement", "importHelpers": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, diff --git a/packages/rax-simulator-renderer/.eslintrc.js b/packages/rax-simulator-renderer/.eslintrc.js index c4749b311..f8bcac9a2 100644 --- a/packages/rax-simulator-renderer/.eslintrc.js +++ b/packages/rax-simulator-renderer/.eslintrc.js @@ -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, diff --git a/packages/rax-simulator-renderer/CHANGELOG.md b/packages/rax-simulator-renderer/CHANGELOG.md index 9d96ad737..89bc3bfa4 100644 --- a/packages/rax-simulator-renderer/CHANGELOG.md +++ b/packages/rax-simulator-renderer/CHANGELOG.md @@ -18,6 +18,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Bug Fixes * 设计和预览两种场景下 requestHandlersMap 的接入 ([707de45](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/707de45)) +* documentModel 里的 addon 相关函数跟原 vision 实现对齐 ([b0ea548](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b0ea548)) @@ -117,6 +118,49 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Bug Fixes * 函数签名及方法名拼写问题 ([e05790b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e05790b)) +* (location) => ({location}) ([0e75b8e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e75b8e)) +* 🐛 解决点击组件时无法聚焦到点中的组件上的问题 ([852d882](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/852d882)) +* add component ([995785d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/995785d)) +* call consumer ([70a1472](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/70a1472)) +* findDOMNodes ([7abf606](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7abf606)) +* findDOMNodes error ([6f5342d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6f5342d)) +* getDocId ([34341d6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/34341d6)) +* getSuitablePlace ([03e7c57](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/03e7c57)) +* history API ([e411687](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e411687)) +* history.listen({location}) => history.listen(location) ([25a6390](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/25a6390)) +* layout tabbar number ([3975571](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3975571)) +* modify docId ([dc95033](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dc95033)) +* modify layout props ([9baba75](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9baba75)) +* onDocumentChange ([eb60d1f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eb60d1f)) +* onReRender ([29ea5f7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/29ea5f7)) +* rax finddom 方法重写 ([1d90928](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1d90928)) +* raxFindDOMNodes ([90430f3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/90430f3)) +* rendererContainer ([486713a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/486713a)) +* router change ([920e584](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/920e584)) +* router rerender ([d886abc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d886abc)) +* update package.json ([f1ec59c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f1ec59c)) +* 不应该限定 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)) +* 修复 preset-vision 版本 lifeCycles 丢失以及 slot 初始化问题 ([7cf6d24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7cf6d24)) +* 修复无法拖动的问题 ([2b2de74](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2b2de74)) +* 初始就create 所有documentInstance, 否则路由跳转有问题 ([fdd6978](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fdd6978)) +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) + + +### Features + +* 🎸 为了能更好地在设计态模拟, 将 device 透传到组件树根组件上 ([2a253fb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2a253fb)) +* current DocuemntInstance add refresh method ([b18a0d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b18a0d2)) +* 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)) +* merge live mode ([92c3039](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/92c3039)) +* rax render ([6ce0093](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6ce0093)) +* rax render ([95bf331](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/95bf331)) +* rax render ([038d74e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/038d74e)) +* rax simulator ([05b262d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/05b262d)) +* rax-render 兼容 ([877d3fc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/877d3fc)) +* 修改rax-render ([14ad77c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/14ad77c)) +* 新增simulatorurl,可以设置cdn使用simulator ([1f45b05](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f45b05)) diff --git a/packages/rax-simulator-renderer/package.json b/packages/rax-simulator-renderer/package.json index e977d015f..e0de30dda 100644 --- a/packages/rax-simulator-renderer/package.json +++ b/packages/rax-simulator-renderer/package.json @@ -22,8 +22,10 @@ "@recore/obx": "^1.0.8", "classnames": "^2.2.6", "driver-universal": "^3.1.3", + "history": "^5.0.0", "lodash": "^4.17.19", "rax-find-dom-node": "^1.0.0", + "rax-use-router": "^3.0.0", "react": "^16", "react-dom": "^16.7.0" }, diff --git a/packages/rax-simulator-renderer/src/rax-use-router.js b/packages/rax-simulator-renderer/src/rax-use-router.js new file mode 100644 index 000000000..4c1a6a0fc --- /dev/null +++ b/packages/rax-simulator-renderer/src/rax-use-router.js @@ -0,0 +1,288 @@ +// Inspired by react-router and universal-router +import { useState, useEffect, useLayoutEffect, createElement } from 'rax'; +import pathToRegexp from 'path-to-regexp'; + +const cache = {}; +function decodeParam(val) { + try { + return decodeURIComponent(val); + } catch (err) { + return val; + } +} + +function matchPath(route, pathname, parentParams) { + let { path, routes, exact: end = true, strict = false, sensitive = false } = route; + // If not has path or has routes that should do not exact match + if (path == null || routes) { + end = false; + } + + // Default path is empty + path = path || ''; + + const regexpCacheKey = `${path}|${end}|${strict}|${sensitive}`; + const keysCacheKey = regexpCacheKey + '|'; + + let regexp = cache[regexpCacheKey]; + let keys = cache[keysCacheKey] || []; + + if (!regexp) { + regexp = pathToRegexp(path, keys, { + end, + strict, + sensitive + }); + cache[regexpCacheKey] = regexp; + cache[keysCacheKey] = keys; + } + + const result = regexp.exec(pathname); + if (!result) { + return null; + } + + const url = result[0]; + const params = { ...parentParams, history: router.history, location: router.history.location }; + + for (let i = 1; i < result.length; i++) { + const key = keys[i - 1]; + const prop = key.name; + const value = result[i]; + if (value !== undefined || !Object.prototype.hasOwnProperty.call(params, prop)) { + if (key.repeat) { + params[prop] = value ? value.split(key.delimiter).map(decodeParam) : []; + } else { + params[prop] = value ? decodeParam(value) : value; + } + } + } + + return { + path: !end && url.charAt(url.length - 1) === '/' ? url.substr(1) : url, + params, + }; +} + +function matchRoute(route, baseUrl, pathname, parentParams) { + let matched; + let childMatches; + let childIndex = 0; + + return { + next() { + if (!matched) { + matched = matchPath(route, pathname, parentParams); + + if (matched) { + return { + done: false, + $: { + route, + baseUrl, + path: matched.path, + params: matched.params, + }, + }; + } + } + + if (matched && route.routes) { + while (childIndex < route.routes.length) { + if (!childMatches) { + const childRoute = route.routes[childIndex]; + childRoute.parent = route; + + childMatches = matchRoute( + childRoute, + baseUrl + matched.path, + pathname.substr(matched.path.length), + matched.params, + ); + } + + const childMatch = childMatches.next(); + if (!childMatch.done) { + return { + done: false, + $: childMatch.$, + }; + } + + childMatches = null; + childIndex++; + } + } + + return { done: true }; + }, + }; +} + +let _initialized = false; +let _routerConfig = null; +const router = { + history: null, + handles: [], + errorHandler() { }, + addHandle(handle) { + return router.handles.push(handle); + }, + removeHandle(handleId) { + router.handles[handleId - 1] = null; + }, + triggerHandles(component) { + router.handles.map((handle) => { + handle && handle(component); + }); + }, + match(fullpath) { + if (fullpath == null) return; + + router.fullpath = fullpath; + + const parent = router.root; + const matched = matchRoute( + parent, + parent.path, + fullpath + ); + + function next(parent) { + const current = matched.next(); + + if (current.done) { + const error = new Error(`No match for ${fullpath}`); + return router.errorHandler(error, router.history.location); + } + + let component = current.$.route.component; + if (typeof component === 'function') { + component = component(current.$.params, router.history.location); + } + if (component instanceof Promise) { + // Lazy loading component by import('./Foo') + return component.then((component) => { + // Check current fullpath avoid router has changed before lazy loading complete + if (fullpath === router.fullpath) { + router.triggerHandles(component); + } + }); + } else if (component != null) { + router.triggerHandles(component); + return component; + } else { + return next(parent); + } + } + + return next(parent); + } +}; + +function matchLocation({ pathname }) { + router.match(pathname); +} + + +function getInitialComponent(routerConfig) { + let InitialComponent = []; + + if (_routerConfig === null) { + if (process.env.NODE_ENV !== 'production') { + if (!routerConfig) { + throw new Error('Error: useRouter should have routerConfig, see: https://www.npmjs.com/package/rax-use-router.'); + } + if (!routerConfig.history || !routerConfig.routes) { + throw new Error('Error: routerConfig should contain history and routes, see: https://www.npmjs.com/package/rax-use-router.'); + } + } + _routerConfig = routerConfig; + } + if (_routerConfig.InitialComponent) { + InitialComponent = _routerConfig.InitialComponent; + } + router.history = _routerConfig.history; + + return InitialComponent; +} + +let unlisten = null; +let handleId = null; +let pathes = ''; +export function useRouter(routerConfig) { + const [component, setComponent] = useState(getInitialComponent(routerConfig)); + + let newPathes = ''; + if (routerConfig) { + _routerConfig = routerConfig; + const routes = _routerConfig.routes; + router.root = Array.isArray(routes) ? { routes } : routes; + if (Array.isArray(routes)) { + newPathes = routes.map(it => it.path).join(','); + } else { + newPathes = routes.path; + } + } + if (_initialized && _routerConfig.history) { + if (newPathes !== pathes) { + matchLocation(_routerConfig.history.location); + pathes = newPathes; + } + } + + useLayoutEffect(() => { + if (unlisten) { + unlisten(); + unlisten = null; + } + + if (handleId) { + router.removeHandle(handleId); + handleId = null; + } + + const history = _routerConfig.history; + const routes = _routerConfig.routes; + + router.root = Array.isArray(routes) ? { routes } : routes; + + handleId = router.addHandle((component) => { + setComponent(component); + }); + + // Init path match + if (_initialized || !_routerConfig.InitialComponent) { + matchLocation(history.location); + pathes = newPathes; + } + + unlisten = history.listen(({ location }) => { + matchLocation(location); + pathes = newPathes; + }); + + _initialized = true; + + return () => { + pathes = ''; + router.removeHandle(handleId); + handleId = null; + unlisten(); + unlisten = null; + }; + }, []); + + return { component }; +} + +export function withRouter(Component) { + function Wrapper(props) { + const history = router.history; + return createElement(Component, { ...props, history, location: history.location }); + }; + + Wrapper.displayName = 'withRouter(' + (Component.displayName || Component.name) + ')'; + Wrapper.WrappedComponent = Component; + return Wrapper; +} diff --git a/packages/rax-simulator-renderer/src/renderer-view.tsx b/packages/rax-simulator-renderer/src/renderer-view.tsx index 34fd78054..eb8440d83 100644 --- a/packages/rax-simulator-renderer/src/renderer-view.tsx +++ b/packages/rax-simulator-renderer/src/renderer-view.tsx @@ -1,11 +1,10 @@ -import { Fragment, Component, createElement } from 'rax'; -// import { observer } from './obx-rax/observer'; import RaxEngine from '@ali/lowcode-rax-renderer/lib/index'; -// import RaxEngine from '../../rax-render/lib/index'; -import { SimulatorRenderer } from './renderer'; -import { host } from './host'; - +import { History } from 'history'; +import { Component, createElement, Fragment } from 'rax'; +import { useRouter } from './rax-use-router'; +import { DocumentInstance, SimulatorRendererContainer } from './renderer'; import './renderer.less'; +import { uniqueId } from '@ali/lowcode-utils'; // patch cloneElement avoid lost keyProps const originCloneElement = (window as any).Rax.cloneElement; @@ -41,17 +40,52 @@ const originCloneElement = (window as any).Rax.cloneElement; return originCloneElement(child, props, ...rest); }; -export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> { +export default class SimulatorRendererView extends Component<{ rendererContainer: SimulatorRendererContainer }> { + private unlisten: any; + + componentDidMount() { + const { rendererContainer } = this.props; + this.unlisten = rendererContainer.onLayoutChange(() => { + this.forceUpdate(); + }); + } + + componentWillUnmount() { + if (this.unlisten) { + this.unlisten(); + } + } + render() { - const { renderer } = this.props; + const { rendererContainer } = this.props; return ( - <Layout renderer={renderer}> - <Renderer renderer={renderer} /> + <Layout rendererContainer={rendererContainer}> + <Routes rendererContainer={rendererContainer} history={rendererContainer.history} /> </Layout> ); } } +export const Routes = (props: { + rendererContainer: SimulatorRendererContainer, + history: History +}) => { + const { rendererContainer, history } = props; + const { documentInstances } = rendererContainer; + + const routes = { + history, + routes: documentInstances.map(instance => { + return { + path: instance.path, + component: (props: any) => <Renderer key={instance.id} rendererContainer={rendererContainer} documentInstance={instance} {...props} /> + }; + }) + }; + const { component } = useRouter(routes); + return component; +} + function ucfirst(s: string) { return s.charAt(0).toUpperCase() + s.substring(1); } @@ -72,59 +106,93 @@ function getDeviceView(view: any, device: string, mode: string) { return view; } -// @observer -class Layout extends Component<{ renderer: SimulatorRenderer }> { - shouldComponentUpdate() { - return false; +class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }> { + constructor(props: any) { + super(props); + this.props.rendererContainer.onReRender(() => { + this.forceUpdate(); + }); } render() { - const { renderer, children } = this.props; - const { layout } = renderer; + const { rendererContainer, children } = this.props; + const layout = rendererContainer.layout; if (layout) { - const { Component, props } = layout; - return <Component props={props}>{children}</Component>; + const { Component, props, componentName } = layout; + if (Component) { + return <Component props={props}>{children}</Component>; + } + if (componentName && rendererContainer.getComponent(componentName)) { + return createElement( + rendererContainer.getComponent(componentName), + { + ...props, + rendererContainer, + }, + [children], + ); + } } return <Fragment>{children}</Fragment>; } } -// @observer -class Renderer extends Component<{ renderer: SimulatorRenderer }> { - constructor(props: any) { - super(props); - this.props.renderer.onReRender(() => { +class Renderer extends Component<{ + rendererContainer: SimulatorRendererContainer; + documentInstance: DocumentInstance; +}> { + private unlisten: any; + private key: string; + + componentWillMount() { + this.key = uniqueId('renderer'); + } + + componentDidMount() { + const { documentInstance } = this.props; + this.unlisten = documentInstance.onReRender((params) => { + if (params && params.shouldRemount) { + this.key = uniqueId('renderer'); + } this.forceUpdate(); }); } + componentWillUnmount() { + if (this.unlisten) { + this.unlisten(); + } + } shouldComponentUpdate() { return false; } render() { - const { renderer } = this.props; - const { device, designMode } = renderer; - + const { documentInstance } = this.props; + const { container } = documentInstance; + const { designMode, device } = container; + const { rendererContainer: renderer } = this.props; return ( <RaxEngine - schema={renderer.schema} + schema={documentInstance.schema} components={renderer.components} + appHelper={renderer.context} context={renderer.context} appHelper={renderer.context} device={device} designMode={renderer.designMode} - suspended={renderer.suspended} - self={renderer.scope} + key={this.key} + suspended={documentInstance.suspended} + self={documentInstance.scope} onCompGetRef={(schema: any, ref: any) => { - renderer.mountInstance(schema.id, ref); + documentInstance.mountInstance(schema.id, ref); }} customCreateElement={(Component: any, props: any, children: any) => { const { __id, __desingMode, ...viewProps } = props; viewProps.componentId = __id; - const leaf = host.document.getNode(__id); + const leaf = documentInstance.getNode(__id); viewProps._leaf = leaf; viewProps._componentName = leaf?.componentName; diff --git a/packages/rax-simulator-renderer/src/renderer.ts b/packages/rax-simulator-renderer/src/renderer.ts index dd50dc918..febfdb2cb 100644 --- a/packages/rax-simulator-renderer/src/renderer.ts +++ b/packages/rax-simulator-renderer/src/renderer.ts @@ -1,20 +1,20 @@ -import { BuiltinSimulatorRenderer, NodeInstance, Component } from '@ali/lowcode-designer'; -import { shared, render as raxRender, createElement } from 'rax'; -import DriverUniversal from 'driver-universal'; +import { BuiltinSimulatorRenderer, Component, DocumentModel, Node, NodeInstance } from '@ali/lowcode-designer'; +import { ComponentSchema, NodeSchema, NpmInfo, RootSchema } from '@ali/lowcode-types'; +import { Asset, cursor, isElement, isESModule, isReactComponent, setNativeSelection } from '@ali/lowcode-utils'; import { computed, obx } from '@recore/obx'; -import { RootSchema, NpmInfo } from '@ali/lowcode-types'; -import { Asset, isReactComponent, isESModule, setNativeSelection, cursor, isElement } from '@ali/lowcode-utils'; - +import DriverUniversal from 'driver-universal'; +import { EventEmitter } from 'events'; +import { createMemoryHistory, MemoryHistory } from 'history'; +// @ts-ignore +import { ComponentType, createElement, render as raxRender, shared } from 'rax'; +import Leaf from './builtin-components/leaf'; +import Slot from './builtin-components/slot'; +import { host } from './host'; import SimulatorRendererView from './renderer-view'; import { raxFindDOMNodes } from './utils/find-dom-nodes'; import { getClientRects } from './utils/get-client-rects'; import loader from './utils/loader'; - -import Leaf from './builtin-components/leaf'; -import Slot from './builtin-components/slot'; - -import { host } from './host'; -import { EventEmitter } from 'events'; +import { parseQuery, withQueryParams } from './utils/url'; const { Instance } = shared; @@ -23,6 +23,7 @@ export interface LibraryMap { } const SYMBOL_VNID = Symbol('_LCNodeId'); +const SYMBOL_VDID = Symbol('_LCDocId'); function accessLibrary(library: string | object) { if (typeof library !== 'string') { @@ -32,62 +33,85 @@ function accessLibrary(library: string | object) { return (window as any)[library]; } -export class SimulatorRenderer implements BuiltinSimulatorRenderer { - readonly isSimulatorRenderer = true; +// Slot/Leaf and Fragment|FunctionComponent polyfill(ref) - private dispose?: () => void; +const builtinComponents = { + Slot, + Leaf, +}; +function buildComponents( + libraryMap: LibraryMap, + componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> | ComponentSchema }, + createComponent: (schema: ComponentSchema) => Component | null, +) { + const components: any = { + ...builtinComponents, + }; + Object.keys(componentsMap).forEach((componentName) => { + let component = componentsMap[componentName]; + if (component && (component as ComponentSchema).componentName === 'Component') { + components[componentName] = createComponent(component as ComponentSchema); + } else if (isReactComponent(component)) { + components[componentName] = component; + } else { + component = findComponent(libraryMap, componentName, component as NpmInfo); + if (component) { + components[componentName] = component; + } + } + }); + return components; +} + +let REACT_KEY = ''; +function cacheReactKey(el: Element): Element { + if (REACT_KEY !== '') { + return el; + } + REACT_KEY = Object.keys(el).find((key) => key.startsWith('__reactInternalInstance$')) || ''; + if (!REACT_KEY && (el as HTMLElement).parentElement) { + return cacheReactKey((el as HTMLElement).parentElement!); + } + return el; +} + +function checkInstanceMounted(instance: any): boolean { + if (isElement(instance)) { + return instance.parentElement != null; + } + return true; +} + +function isValidDesignModeRaxComponentInstance( + raxComponentInst: any, +): raxComponentInst is { + props: { + _leaf: Exclude<NodeInstance<any>['node'], null | undefined>; + }; +} { + const leaf = raxComponentInst?.props?._leaf; + return leaf && typeof leaf === 'object' && leaf.isNode; +} + +export class DocumentInstance { private instancesMap = new Map<string, any[]>(); - @obx.ref private _schema?: RootSchema; + private emitter = new EventEmitter(); + @obx.ref private _schema?: RootSchema; @computed get schema(): any { return this._schema; } - private _libraryMap: { [key: string]: string } = {}; + private dispose?: () => void; - private buildComponents() { - this._components = buildComponents(this._libraryMap, this._componentsMap); - } - - @obx.ref private _components: any = {}; - - @computed get components(): object { - // 根据 device 选择不同组件,进行响应式 - // 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl - return this._components; - } - - @obx.ref private _designMode = 'design'; - - @computed get designMode(): any { - return this._designMode; - } - - @obx.ref private _requestHandlersMap = null; - - @computed get requestHandlersMap(): any { - return this._requestHandlersMap; - } - - @obx.ref private _componentsMap = {}; - - @computed get componentsMap(): any { - return this._componentsMap; - } - - @obx.ref private _device = 'default'; - - @computed get device() { - return this._device; - } - - // context from: utils、constants、history、location、match - @obx.ref private _appContext = {}; - - @computed get context(): any { - return this._appContext; + constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) { + this.dispose = host.autorun(() => { + // sync schema + this._schema = document.export(1); + this.emitter.emit('rerender'); + }); } @computed get suspended(): any { @@ -98,24 +122,131 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { return null; } - @computed get layout(): any { - // TODO: parse layout Component - return null; + get path(): string { + return '/' + this.document.fileName; } + get id() { + return this.document.id; + } + + private unmountIntance(id: string, instance: any) { + const instances = this.instancesMap.get(id); + if (instances) { + const i = instances.indexOf(instance); + if (i > -1) { + instances.splice(i, 1); + host.setInstance(this.document.id, id, instances); + } + } + } + + refresh() { + this.emitter.emit('rerender', { shouldRemount: true }); + } + + onReRender(fn: () => void) { + this.emitter.on('rerender', fn); + return () => { + this.emitter.removeListener('renderer', fn); + }; + } + + mountInstance(id: string, instance: any) { + const docId = this.document.id; + const instancesMap = this.instancesMap; + if (instance == null) { + let instances = this.instancesMap.get(id); + if (instances) { + instances = instances.filter(checkInstanceMounted); + if (instances.length > 0) { + instancesMap.set(id, instances); + host.setInstance(this.document.id, id, instances); + } else { + instancesMap.delete(id); + host.setInstance(this.document.id, id, null); + } + } + return; + } + const unmountIntance = this.unmountIntance.bind(this); + const origId = (instance as any)[SYMBOL_VNID]; + if (origId && origId !== id) { + // 另外一个节点的 instance 在此被复用了,需要从原来地方卸载 + unmountIntance(origId, instance); + } + if (isElement(instance)) { + cacheReactKey(instance); + } else if (origId !== id) { + // 涵盖 origId == null || origId !== id 的情况 + let origUnmount: any = instance.componentWillUnmount; + if (origUnmount && origUnmount.origUnmount) { + origUnmount = origUnmount.origUnmount; + } + // hack! delete instance from map + const newUnmount = function(this: any) { + unmountIntance(id, instance); + origUnmount && origUnmount.call(this); + }; + (newUnmount as any).origUnmount = origUnmount; + instance.componentWillUnmount = newUnmount; + } + + (instance as any)[SYMBOL_VNID] = id; + (instance as any)[SYMBOL_VDID] = docId; + let instances = this.instancesMap.get(id); + if (instances) { + const l = instances.length; + instances = instances.filter(checkInstanceMounted); + let updated = instances.length !== l; + if (!instances.includes(instance)) { + instances.push(instance); + updated = true; + } + if (!updated) { + return; + } + } else { + instances = [instance]; + } + instancesMap.set(id, instances); + host.setInstance(this.document.id, id, instances); + } + + mountContext(docId: string, id: string, ctx: object) { + // this.ctxMap.set(id, ctx); + } + + getComponentInstances(id: string): any[] | null { + return this.instancesMap.get(id) || null; + } + + getNode(id: string): Node<NodeSchema> | null { + return this.document.getNode(id); + } +} + +export class SimulatorRendererContainer implements BuiltinSimulatorRenderer { + readonly isSimulatorRenderer = true; + private dispose?: () => void; + readonly history: MemoryHistory; + private emitter = new EventEmitter(); + @obx.ref private _documentInstances: DocumentInstance[] = []; + get documentInstances() { + return this._documentInstances; + } + + get currentDocumentInstance() { + return this._documentInstances.find((item) => item.id === host.project.currentDocument?.id); + } + constructor() { - if (!host) { - return; - } - - this.dispose = host.connect(this as any, () => { + this.dispose = host.connect(this, () => { // sync layout config - - // sync schema - this._schema = host.document.export(1); - + // debugger; + this._layout = host.project.get('config').layout; // todo: split with others, not all should recompute if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) { this._libraryMap = host.libraryMap || {}; @@ -126,17 +257,39 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { // sync designMode this._designMode = host.designMode; - // sync requestHandlersMap - this._requestHandlersMap = host.requestHandlersMap; - - // sync suspended - - // sync scope - // sync device this._device = host.device; - this.emitter.emit('rerender'); + this.emitter.emit('layoutChange'); + }); + const documentInstanceMap = new Map<string, DocumentInstance>(); + let initialEntry = '/'; + host.autorun(({ firstRun }) => { + this._documentInstances = host.project.documents.map((doc) => { + let inst = documentInstanceMap.get(doc.id); + if (!inst) { + inst = new DocumentInstance(this, doc); + documentInstanceMap.set(doc.id, inst); + } + return inst; + }); + + const path = host.project.currentDocument ? documentInstanceMap.get(host.project.currentDocument.id)!.path : '/'; + if (firstRun) { + initialEntry = path; + } else { + if (this.history.location.pathname !== path) { + this.history.replace(path); + } + this.emitter.emit('layoutChange'); + } + }); + const history = createMemoryHistory({ + initialEntries: [initialEntry], + }); + this.history = history; + history.listen(({ location }) => { + host.project.open(location.pathname.substr(1)); }); host.componentsConsumer.consume(async (componentsAsset) => { if (componentsAsset) { @@ -144,32 +297,75 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { this.buildComponents(); } }); - host.injectionConsumer.consume(() => { - // sync utils, i18n, contants,... config - this._appContext = { - utils: {}, - constants: { - name: 'demo', + this._appContext = { + utils: { + router: { + push(path: string, params?: object) { + history.push(withQueryParams(path, params)); + }, + replace(path: string, params?: object) { + history.replace(withQueryParams(path, params)); + }, + back() { + history.back(); + } }, - requestHandlersMap: this._requestHandlersMap, - }; + legaoBuiltins: { + getUrlParams() { + const search = history.location.search; + return parseQuery(search); + }, + }, + }, + constants: {}, + }; + host.injectionConsumer.consume((data) => { + // sync utils, i18n, contants,... config }); } - getClosestNodeInstance(from: any, nodeId?: string): NodeInstance<any> | null { - const node = getClosestNodeInstance(from, nodeId); - return node; + @obx private _layout: any = null; + @computed get layout(): any { + // TODO: parse layout Component + return this._layout; + } + set layout(value: any) { + this._layout = value; } - getComponentInstances(id: string): any[] | null { - return this.instancesMap.get(id) || null; + private _libraryMap: { [key: string]: string } = {}; + private buildComponents() { + // TODO: remove this.createComponent + this._components = buildComponents(this._libraryMap, this._componentsMap, this.createComponent.bind(this)); } - - onReRender(fn: () => void) { - this.emitter.on('rerender', fn); - return () => { - this.emitter.removeListener('renderer', fn); - }; + @obx.ref private _components: any = {}; + @computed get components(): object { + // 根据 device 选择不同组件,进行响应式 + // 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl + return this._components; + } + // context from: utils、constants、history、location、match + @obx.ref private _appContext = {}; + @computed get context(): any { + return this._appContext; + } + @obx.ref private _designMode: string = 'design'; + @computed get designMode(): any { + return this._designMode; + } + @obx.ref private _device: string = 'default'; + @computed get device() { + return this._device; + } + @obx.ref private _componentsMap = {}; + @computed get componentsMap(): any { + return this._componentsMap; + } + /** + * 加载资源 + */ + load(asset: Asset): Promise<any> { + return loader.load(asset); } getComponent(componentName: string) { @@ -190,27 +386,49 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { componentName = paths.join('.'); } - // return null; - } - - createComponent(/* schema: ComponentSchema */): Component | null { return null; } - setNativeSelection(enableFlag: boolean) { - setNativeSelection(enableFlag); + getNodeInstance(dom: HTMLElement): NodeInstance<any> | null { + const INTERNAL = '_internal'; + let instance: any = dom; + if (!isElement(instance)) { + return { + docId: instance.props._leaf.document.id, + nodeId: instance.props._leaf.getId(), + instance, + node: instance.props._leaf, + }; + } + instance = Instance.get(dom); + while (instance && instance[INTERNAL]) { + if (isValidDesignModeRaxComponentInstance(instance)) { + // const docId = (instance.props as any).schema.docId; + return { + docId: instance.props._leaf.document.id, + nodeId: instance.props._leaf.getId(), + instance, + node: instance.props._leaf, + }; + } + + instance = instance[INTERNAL].__parentInstance; + } + + return null; } - setDraggingState(state: boolean) { - cursor.setDragging(state); - } - - setCopyState(state: boolean) { - cursor.setCopy(state); - } - - clearState() { - cursor.release(); + getClosestNodeInstance(from: any, nodeId?: string): NodeInstance<any> | null { + const el: any = from; + if (el) { + // if (isElement(el)) { + // el = cacheReactKey(el); + // } else { + // return getNodeInstance(el, specId); + // } + return this.getNodeInstance(el); + } + return null; } findDOMNodes(instance: any, selector?: string): Array<Element | Text> | null { @@ -232,90 +450,131 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { return raxFindDOMNodes(el); } - /** - * 加载资源 - */ - load(asset: Asset): Promise<any> { - return loader.load(asset); - } - - // private instancesMap = new Map<string, any[]>(); - private unmountIntance(id: string, instance: any) { - const instances = this.instancesMap.get(id); - if (instances) { - const i = instances.indexOf(instance); - if (i > -1) { - instances.splice(i, 1); - host.setInstance(id, instances); - } - } - } - - mountInstance(id: string, instance: any | null) { - const { instancesMap } = this; - if (instance == null) { - let instances = this.instancesMap.get(id); - if (instances) { - instances = instances.filter(checkInstanceMounted); - if (instances.length > 0) { - instancesMap.set(id, instances); - host.setInstance(id, instances); - } else { - instancesMap.delete(id); - host.setInstance(id, null); - } - } - return; - } - const unmountIntance = this.unmountIntance.bind(this); - const origId = (instance as any)[SYMBOL_VNID]; - if (origId && origId !== id) { - // 另外一个节点的 instance 在此被复用了,需要从原来地方卸载 - unmountIntance(origId, instance); - } - if (isElement(instance)) { - // cacheReactKey(instance); - } else if (origId !== id) { - // 涵盖 origId == null || origId !== id 的情况 - let origUnmount: any = instance.componentWillUnmount; - if (origUnmount && origUnmount.origUnmount) { - origUnmount = origUnmount.origUnmount; - } - // hack! delete instance from map - const newUnmount = function (this: any) { - unmountIntance(id, instance); - origUnmount && origUnmount.call(this); - }; - (newUnmount as any).origUnmount = origUnmount; - instance.componentWillUnmount = newUnmount; - } - - (instance as any)[SYMBOL_VNID] = id; - let instances = this.instancesMap.get(id); - if (instances) { - const l = instances.length; - instances = instances.filter(checkInstanceMounted); - let updated = instances.length !== l; - if (!instances.includes(instance)) { - instances.push(instance); - updated = true; - } - if (!updated) { - return; - } - } else { - instances = [instance]; - } - instancesMap.set(id, instances); - host.setInstance(id, instances); - } - getClientRects(element: Element | Text) { return getClientRects(element); } - private _running = false; + setNativeSelection(enableFlag: boolean) { + setNativeSelection(enableFlag); + } + setDraggingState(state: boolean) { + cursor.setDragging(state); + } + setCopyState(state: boolean) { + cursor.setCopy(state); + } + clearState() { + cursor.release(); + } + onLayoutChange(cb: () => void) { + this.emitter.on('layoutChange', cb); + return () => { + this.emitter.removeListener('layoutChange', cb); + }; + } + + onReRender(fn: () => void) { + this.emitter.on('rerender', fn); + return () => { + this.emitter.removeListener('renderer', fn); + }; + } + + createComponent(schema: ComponentSchema): Component | null { + return null; + // TODO: use ComponentEngine refactor + /* + const _schema = { + ...schema, + }; + _schema.methods = {}; + _schema.lifeCycles = {}; + + const node = host.document.createNode(_schema); + _schema = node.export(TransformStage.Render); + + const processPropsSchema = (propsSchema: any, propsMap: any): any => { + if (!propsSchema) { + return {}; + } + + const result = { ...propsSchema }; + const reg = /^(?:this\.props|props)\.(\S+)$/; + Object.keys(result).map((key: string) => { + if (result[key]?.type === 'JSExpression') { + const { value } = result[key]; + const matched = reg.exec(value); + if (matched) { + const propName = matched[1]; + result[key] = propsMap[propName]; + } + } else if (result[key]?.type === 'JSSlot') { + const schema = result[key].value; + result[key] = createElement(Ele, {schema, propsMap: {}}); + } + }); + + return result; + }; + + const renderer = this; + const componentsMap = renderer.componentsMap; + + class Ele extends React.Component<{ schema: any, propsMap: any }> { + private isModal: boolean; + + constructor(props: any){ + super(props); + const componentMeta = host.document.getComponentMeta(props.schema.componentName); + if (componentMeta?.prototype?.isModal()) { + this.isModal = true; + return; + } + } + + render() { + if (this.isModal) { + return null; + } + const { schema, propsMap } = this.props; + const Com = componentsMap[schema.componentName]; + if (!Com) { + return null; + } + let children = null; + if (schema.children && schema.children.length > 0) { + children = schema.children.map((item: any) => createElement(Ele, {schema: item, propsMap})); + } + const props = processPropsSchema(schema.props, propsMap); + const _leaf = host.document.createNode(schema); + + return createElement(Com, {...props, _leaf}, children); + } + const _leaf = this.document.designer.currentDocument?.createNode(schema); + const node = this.document.createNode(schema); + let props = processPropsSchema(schema.props, propsMap); + props = this.document.designer.transformProps(props, node, TransformStage.Init); + props = this.document.designer.transformProps(props, node, TransformStage.Render); + return createElement(Com, { ...props, _leaf }, children); + }; + + const container = this; + class Com extends React.Component { + render() { + const componentsMap = container.componentsMap; + let children = null; + if (_schema.children && Array.isArray(_schema.children)) { + children = _schema.children?.map((item: any) => getElement(componentsMap, item, this.props)); + } + } + } + + return Com; + */ + } + + private _running = false; run() { if (this._running) { return; @@ -328,14 +587,17 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { document.body.appendChild(container); container.id = containerId; } + // ==== compatiable vision document.documentElement.classList.add('engine-page'); document.body.classList.add('engine-document'); // important! Stylesheet.invoke depends - raxRender(createElement(SimulatorRendererView, { renderer: this }), container, { + raxRender(createElement(SimulatorRendererView, { + rendererContainer: this + }), container, { driver: DriverUniversal, }); - host.document.setRendererReady(this); + host.project.setRendererReady(this); } } @@ -391,77 +653,4 @@ function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmI return getSubComponent(library, paths); } -const builtinComponents = { - Slot, - Leaf, -}; - -function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo }) { - const components: any = { - ...builtinComponents, - }; - Object.keys(componentsMap).forEach((componentName) => { - let component = componentsMap[componentName]; - if (isReactComponent(component)) { - components[componentName] = component; - } else { - component = findComponent(libraryMap, componentName, component); - if (component) { - components[componentName] = component; - } - } - }); - return components; -} - -function getClosestNodeInstance(from: any): NodeInstance<any> | null { - const el: any = from; - if (el) { - // if (isElement(el)) { - // el = cacheReactKey(el); - // } else { - // return getNodeInstance(el, specId); - // } - return getNodeInstance(el); - } - return null; -} - -function isValidDesignModeRaxComponentInstance( - raxComponentInst: any, -): raxComponentInst is { - props: { - _leaf: Exclude<NodeInstance<any>['node'], null | undefined>; - }; -} { - const leaf = raxComponentInst?.props?._leaf; - return leaf && typeof leaf === 'object' && leaf.isNode; -} - -function getNodeInstance(dom: HTMLElement): NodeInstance<any> | null { - const INTERNAL = '_internal'; - - let instance = Instance.get(dom); - while (instance && instance[INTERNAL]) { - if (isValidDesignModeRaxComponentInstance(instance)) { - return { - nodeId: instance.props._leaf.getId(), - instance, - node: instance.props._leaf, - }; - } - - instance = instance[INTERNAL].__parentInstance; - } - - return null; -} - -function checkInstanceMounted(instance: any): boolean { - if (isElement(instance)) { - return instance.parentElement != null; - } - return true; -} - -export default new SimulatorRenderer(); +export default new SimulatorRendererContainer(); diff --git a/packages/rax-simulator-renderer/src/utils/url.ts b/packages/rax-simulator-renderer/src/utils/url.ts new file mode 100644 index 000000000..d720323b3 --- /dev/null +++ b/packages/rax-simulator-renderer/src/utils/url.ts @@ -0,0 +1,74 @@ +/** + * Parse queryString + * @param {String} str '?q=query&b=test' + * @return {Object} + */ +export function parseQuery(str: string): object { + const ret: any = {}; + + if (typeof str !== 'string') { + return ret; + } + + const s = str.trim().replace(/^(\?|#|&)/, ''); + + if (!s) { + return ret; + } + + s.split('&').forEach((param) => { + const parts = param.replace(/\+/g, ' ').split('='); + let key = parts.shift()!; + let val: any = parts.length > 0 ? parts.join('=') : undefined; + + key = decodeURIComponent(key); + + val = val === undefined ? null : decodeURIComponent(val); + + if (ret[key] === undefined) { + ret[key] = val; + } else if (Array.isArray(ret[key])) { + ret[key].push(val); + } else { + ret[key] = [ret[key], val]; + } + }); + + return ret; +} + +/** + * Stringify object to query parammeters + * @param {Object} obj + * @return {String} + */ +export function stringifyQuery(obj: any): string { + const param: string[] = []; + Object.keys(obj).forEach((key) => { + let value = obj[key]; + if (value && typeof value === 'object') { + value = JSON.stringify(value); + } + param.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); + }); + return param.join('&'); +} + +export function uriEncode(uri: string) { + return encodeURIComponent(uri); +} + +export function uriDecode(uri: string) { + return decodeURIComponent(uri); +} + +export function withQueryParams(url: string, params?: object) { + const queryStr = params ? stringifyQuery(params) : ''; + if (queryStr === '') { + return url; + } + const urlSplit = url.split('#'); + const hash = urlSplit[1] ? `#${urlSplit[1]}` : ''; + const urlWithoutHash = urlSplit[0]; + return `${urlWithoutHash}${~urlWithoutHash.indexOf('?') ? '&' : '?'}${queryStr}${hash}`; +} diff --git a/packages/react-renderer/.eslintrc.js b/packages/react-renderer/.eslintrc.js index eea6aa758..6c3b4b4a7 100644 --- a/packages/react-renderer/.eslintrc.js +++ b/packages/react-renderer/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - extends: '../../.eslintrc.js', + extends: 'eslint-config-ali/typescript/react', rules: { "react/no-multi-comp": 1, "no-shadow": 1, diff --git a/packages/react-renderer/CHANGELOG.md b/packages/react-renderer/CHANGELOG.md index 5c1ae99de..49caff9b0 100644 --- a/packages/react-renderer/CHANGELOG.md +++ b/packages/react-renderer/CHANGELOG.md @@ -40,6 +40,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Bug Fixes * datasource版本错误问题 ([a247878](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a247878)) +* 解决 device 变化后不刷新视图的 bug ([11e8e02](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/11e8e02)) @@ -51,6 +52,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Bug Fixes * 去除 handler 依赖 ([806ca62](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/806ca62)) +* 解决 slot 在关闭时没有正常回收节点 ([642a404](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/642a404)) @@ -76,21 +78,54 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline <a name="1.0.14"></a> ## [1.0.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.12...@ali/lowcode-react-renderer@1.0.14) (2020-11-04) +* 修复修改 componentsMap 后无法刷新视图的 bug ([a1e7f21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a1e7f21)) + + + + +<a name="0.13.1-5"></a> +## [0.13.1-5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-4...v0.13.1-5) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-react-renderer + +<a name="0.13.1-4"></a> +## [0.13.1-4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-3...v0.13.1-4) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-react-renderer + +<a name="0.13.1-3"></a> +## [0.13.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-2...v0.13.1-3) (2020-10-19) ### Bug Fixes +<<<<<<< HEAD * children 在 schema 和 props 中并存的情况处理 ([7b639eb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7b639eb)) +======= +* 修复 JSSlot 被转成 i18n 结构 ([f2c3292](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f2c3292)) +>>>>>>> origin/refactor/vision-code-split +<<<<<<< HEAD <a name="1.0.13"></a> ## [1.0.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.12...@ali/lowcode-react-renderer@1.0.13) (2020-11-02) +======= +<a name="0.13.1-2"></a> +## [0.13.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.13.1-2) (2020-10-19) +>>>>>>> origin/refactor/vision-code-split ### Bug Fixes +<<<<<<< HEAD * children 在 schema 和 props 中并存的情况处理 ([7b639eb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7b639eb)) @@ -98,15 +133,37 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline <a name="1.0.12"></a> ## [1.0.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.11...@ali/lowcode-react-renderer@1.0.12) (2020-10-20) +======= +* convertI18nObject ([66d43f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66d43f2)) + + + + +<a name="0.13.1-1"></a> +## [0.13.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-3...v0.13.1-1) (2020-10-12) + + + + +**Note:** Version bump only for package @ali/lowcode-react-renderer + +<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) +>>>>>>> origin/refactor/vision-code-split ### Bug Fixes +<<<<<<< HEAD * monaco cdn url update ([7b2fe13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7b2fe13)) +======= +* 去掉 flags ([75fc3c6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/75fc3c6)) +>>>>>>> origin/refactor/vision-code-split +<<<<<<< HEAD <a name="1.0.11"></a> ## [1.0.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.10...@ali/lowcode-react-renderer@1.0.11) (2020-10-19) @@ -114,34 +171,144 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Features * **version:** 发布版本1.0.9 ([eb00490](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eb00490)) +======= +<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 + +* i18n 绑定变量后消失 ([0aafafe](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0aafafe)) +>>>>>>> origin/refactor/vision-code-split +<<<<<<< HEAD <a name="1.0.10"></a> ## [1.0.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.9...@ali/lowcode-react-renderer@1.0.10) (2020-09-29) +======= +<a name="0.12.1-1"></a> +## [0.12.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-9...v0.12.1-1) (2020-09-22) +>>>>>>> origin/refactor/vision-code-split **Note:** Version bump only for package @ali/lowcode-react-renderer +<<<<<<< HEAD <a name="1.0.9"></a> ## [1.0.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.8...@ali/lowcode-react-renderer@1.0.9) (2020-09-28) +======= +<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) +>>>>>>> origin/refactor/vision-code-split **Note:** Version bump only for package @ali/lowcode-react-renderer +<<<<<<< HEAD <a name="1.0.8"></a> ## [1.0.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.8-0...@ali/lowcode-react-renderer@1.0.8) (2020-09-28) +======= +<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) +>>>>>>> origin/refactor/vision-code-split **Note:** Version bump only for package @ali/lowcode-react-renderer +<<<<<<< HEAD +======= +<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 + +* 修改 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) + + +### Features + +* 补充一些 vision API ([933cef1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/933cef1)) + + + + +<a name="1.0.9-2"></a> +## [1.0.9-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.9-1...v1.0.9-2) (2020-09-14) + + + + +**Note:** Version bump only for package @ali/lowcode-react-renderer + +<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-react-renderer + +<a name="1.0.9-0"></a> +## 1.0.9-0 (2020-09-14) + + +### Bug Fixes + +* (location) => ({location}) ([0e75b8e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e75b8e)) +* add FaultComponent style ([77b0b2c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/77b0b2c)) +* demo 中引入locode-editor-general ([1f03857](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1f03857)) +* div 不显示问题 ([1b6533c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1b6533c)) +* dropdown and menu schema ([ae1d125](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ae1d125)) +* handling the undefined variable ([0efe8b4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0efe8b4)) +* modify docId ([dc95033](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dc95033)) +* onDocumentChange ([eb60d1f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eb60d1f)) +* parse custom methods function ([87d8b86](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/87d8b86)) +* remove debugger ([a835dc6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a835dc6)) +* render error样式 ([d601d5e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d601d5e)) +* slot 兼容问题 + loop key bug fix ([bc64017](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bc64017)) +* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c)) +* 修复低代码组件设计器、区块设计器根节点为 Page 的问题,修复 topArea 样式 ([e85b542](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e85b542)) +* 修复判断动态 setter 的逻辑 ([d195d7f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d195d7f)) +* **react-renderer:** fix hasLoop logic ([577e0eb](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/577e0eb)) +* 区块模板根节点支持 Div ([c3b796e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c3b796e)) +* 合并后bugfix ([c3e6b4b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c3e6b4b)) +* 支持自定义 Block 容器 ([1c0b508](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1c0b508)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) +* 组件缺失占位 ([aff2f34](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aff2f34)) + + +### Features + +* 🎸 add demo-server ([df35c6a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/df35c6a)) +* extend deviceClassName ([0e96074](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0e96074)) +* rax render ([95bf331](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/95bf331)) +* rax-render 拦截逻辑 & request 调用 webtable(mock) ([42108f6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/42108f6)) +* 在 editor-preset-vision 中对 legao schema 进行向前兼容 ([7867917](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7867917)) + + +### Reverts + +* 去掉多余注释 ([2495afa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2495afa)) + + + + +>>>>>>> origin/refactor/vision-code-split <a name="1.0.8-0"></a> ## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-renderer@1.0.7-0...@ali/lowcode-react-renderer@1.0.8-0) (2020-09-09) diff --git a/packages/react-renderer/package.json b/packages/react-renderer/package.json index bed80f399..f00cf238d 100644 --- a/packages/react-renderer/package.json +++ b/packages/react-renderer/package.json @@ -11,7 +11,7 @@ "scripts": { "test": "echo \"Error: run tests from root\" && exit 1", "start": "build-scripts start", - "build": "build-scripts build", + "build": "build-scripts build --skip-demo", "prepublishOnly": "npm run build" }, "repository": { diff --git a/packages/react-renderer/src/index.tsx b/packages/react-renderer/src/index.tsx index a2b2a8b0e..d38896a29 100644 --- a/packages/react-renderer/src/index.tsx +++ b/packages/react-renderer/src/index.tsx @@ -2,6 +2,7 @@ import React, { Component, PureComponent, createElement as reactCreateElement } import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import Debug from 'debug'; +import { ConfigProvider } from '@alifd/next'; import { isEmpty } from '@ali/b3-one/lib/obj'; import AppContext from './context/appContext'; import { isFileSchema, goldlog } from './utils'; @@ -196,15 +197,17 @@ export default class Renderer extends Component { engine: this, }} > - <Comp - key={schema.__ctx && `${schema.__ctx.lunaKey}_${schema.__ctx.idx || '0'}`} - ref={this.__getRef} - __appHelper={appHelper} - __components={allComponents} - __schema={schema} - __designMode={designMode} - {...this.props} - /> + <ConfigProvider device={this.props.device}> + <Comp + key={schema.__ctx && `${schema.__ctx.lunaKey}_${schema.__ctx.idx || '0'}`} + ref={this.__getRef} + __appHelper={appHelper} + __components={allComponents} + __schema={schema} + __designMode={designMode} + {...this.props} + /> + </ConfigProvider> </AppContext.Provider> ); } diff --git a/packages/react-renderer/src/renderer/base.tsx b/packages/react-renderer/src/renderer/base.tsx index 40c823b2b..45d30f1c1 100644 --- a/packages/react-renderer/src/renderer/base.tsx +++ b/packages/react-renderer/src/renderer/base.tsx @@ -83,6 +83,31 @@ export default class BaseRender extends PureComponent { console.warn(e); } + reloadDataSource = () => new Promise((resolve, reject) => { + debug('reload data source'); + if (!this.__dataHelper) { + this.__showPlaceholder = false; + return resolve(); + } + this.__dataHelper + .getInitData() + .then((res) => { + this.__showPlaceholder = false; + if (isEmpty(res)) { + this.forceUpdate(); + return resolve(); + } + this.setState(res, resolve); + }) + .catch((err) => { + if (this.__showPlaceholder) { + this.__showPlaceholder = false; + this.forceUpdate(); + } + reject(err); + }); + }); + __setLifeCycleMethods = (method, args) => { const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {}); let fn = lifeCycleMethods[method]; @@ -358,7 +383,7 @@ export default class BaseRender extends PureComponent { if (refProps && typeof refProps === 'string') { this[refProps] = ref; } - engine && engine.props.onCompGetRef(schema, ref); + ref && engine && engine.props.onCompGetRef(schema, ref); }; } // scope需要传入到组件上 @@ -382,7 +407,7 @@ export default class BaseRender extends PureComponent { } let child = null; - if (!isFileSchema(schema) && !!_children) { + if (/*!isFileSchema(schema) && */!!_children) { child = this.__createVirtualDom( isJSExpression(_children) ? parseExpression(_children, self) : _children, self, diff --git a/packages/react-renderer/src/renderer/page.tsx b/packages/react-renderer/src/renderer/page.tsx index 6eea473c3..fadd80e71 100644 --- a/packages/react-renderer/src/renderer/page.tsx +++ b/packages/react-renderer/src/renderer/page.tsx @@ -12,7 +12,6 @@ const debug = Debug('renderer:page'); export default class PageRenderer extends BaseRenderer { static dislayName = 'page-renderer'; - static propTypes = { __schema: PropTypes.object, }; diff --git a/packages/react-renderer/src/utils/dataHelper.ts b/packages/react-renderer/src/utils/dataHelper.ts index dca90e215..7503b92da 100644 --- a/packages/react-renderer/src/utils/dataHelper.ts +++ b/packages/react-renderer/src/utils/dataHelper.ts @@ -1,3 +1,4 @@ +/* eslint-disable object-curly-newline */ import { transformArrayToMap, isJSFunction, transformStringToFunction, clone } from './index'; import { jsonp, mtop, request, get, post, bzb } from './request'; @@ -80,6 +81,7 @@ export default class DataHelper { } return false; }); + // 所有 datasource 的 datahandler return this.asyncDataHandler(initSyncData).then((res) => { let { dataHandler } = this.config; if (isJSFunction(dataHandler)) { @@ -158,7 +160,7 @@ export default class DataHelper { const _tb_token_ = csrfInput && csrfInput.value; asyncDataList.forEach((req) => { const { id, type, options } = req; - if (!id || !type) return; + if (!id || !type || type === 'legao') return; if (type === 'doServer') { const { uri, params } = options || {}; if (!uri) return; @@ -185,6 +187,7 @@ export default class DataHelper { } if (allReq.length === 0) resolve({}); const res = {}; + // todo: Promise.all( allReq.map((item) => { return new Promise((resolve) => { @@ -256,6 +259,7 @@ export default class DataHelper { }); } + // dataHandler todo: dataHandler(id, dataHandler, data, error) { if (isJSFunction(dataHandler)) { dataHandler = transformStringToFunction(dataHandler.value); @@ -270,7 +274,7 @@ export default class DataHelper { fetchOne(type, options) { // eslint-disable-next-line prefer-const - let { uri, method = 'GET', headers, params, ...otherProps } = options; + let { uri, url, method = 'GET', headers, params, ...otherProps } = options; otherProps = otherProps || {}; switch (type) { case 'mtop': @@ -284,11 +288,19 @@ export default class DataHelper { headers, ...otherProps, }); + // todo: + case 'legao': + if (method === 'JSONP') { + return jsonp(url, params, otherProps); + } + // return webTable(uri, params, otherProps); + break; default: method = method.toUpperCase(); if (method === 'GET') { return get(uri, params, headers, otherProps); - } else if (method === 'POST') { + } + if (method === 'POST') { return post(uri, params, headers, otherProps); } return request(uri, method, params, headers, otherProps); diff --git a/packages/react-simulator-renderer/.eslintrc.js b/packages/react-simulator-renderer/.eslintrc.js index 78b88f421..39a7d9067 100644 --- a/packages/react-simulator-renderer/.eslintrc.js +++ b/packages/react-simulator-renderer/.eslintrc.js @@ -1,15 +1,17 @@ module.exports = { - extends: '../../.eslintrc.js', + extends: 'eslint-config-ali/typescript/react', rules: { - 'react/no-multi-comp': 1, - 'no-unused-expressions': 1, - 'implicit-arrow-linebreak': 1, - 'no-nested-ternary': 1, - 'no-mixed-operators': 1, - '@typescript-eslint/no-parameter-properties': 1, - '@typescript-eslint/ban-types': 1, - 'no-shadow': 1, - 'no-prototype-builtins': 1, - 'array-callback-return': 1, + 'react/no-multi-comp': 0, + 'no-unused-expressions': 0, + 'implicit-arrow-linebreak': 0, + 'no-nested-ternary': 0, + 'no-mixed-operators': 0, + '@typescript-eslint/no-parameter-properties': 0, + '@typescript-eslint/ban-types': 0, + 'no-shadow': 0, + 'no-prototype-builtins': 0, + 'array-callback-return': 0, + '@typescript-eslint/member-ordering': 0, + 'react/no-find-dom-node', 0 } } \ No newline at end of file diff --git a/packages/react-simulator-renderer/CHANGELOG.md b/packages/react-simulator-renderer/CHANGELOG.md index 01aa70a13..66f2ca2b2 100644 --- a/packages/react-simulator-renderer/CHANGELOG.md +++ b/packages/react-simulator-renderer/CHANGELOG.md @@ -25,6 +25,117 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline <a name="1.0.19"></a> ## [1.0.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-simulator-renderer@1.0.18...@ali/lowcode-react-simulator-renderer@1.0.19) (2020-11-10) +* 使用 componentMeta.isModal 代替 protoType.isModal() ([b787dc4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b787dc4)) +* 使用引擎标准的 lc-container-placeholder,支持 children 属性 ([b262665](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b262665)) + + + + +<a name="0.13.1-12"></a> +## [0.13.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-12) (2020-11-18) + + +### Bug Fixes + +* 解决 device 变化后不刷新视图的 bug ([11e8e02](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/11e8e02)) + + + +<a name="0.12.1-19"></a> +## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17) + + + +<a name="0.12.1-18"></a> +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + + +<a name="0.12.1-17"></a> +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + + +<a name="0.12.1-16"></a> +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + +<a name="0.12.1-15"></a> +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + +### Bug Fixes + +* 样式微调 ([9816859](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9816859)) + + + +<a name="0.12.1-14"></a> +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + +### Bug Fixes + +* lc-container-placeholder 样式修改 ([d939285](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d939285)) + + + +<a name="0.12.1-13"></a> +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + + +<a name="0.12.1-12"></a> +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + + +<a name="0.12.1-11"></a> +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + +<a name="0.12.1-10"></a> +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + +<a name="0.12.1-9"></a> +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + +<a name="0.12.1-8"></a> +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + +<a name="0.12.1-7"></a> +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + +### Bug Fixes + +* 使用 componentMeta.isModal 代替 protoType.isModal() ([b787dc4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b787dc4)) +* 使用引擎标准的 lc-container-placeholder,支持 children 属性 ([b262665](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b262665)) + + + + +<a name="0.13.1-11"></a> +## [0.13.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-10...v0.13.1-11) (2020-11-02) + + +### Bug Fixes + +* 解决 slot 在关闭时没有正常回收节点 ([642a404](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/642a404)) + + + + +<a name="0.13.1-10"></a> +## [0.13.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-9...v0.13.1-10) (2020-10-26) @@ -119,6 +230,59 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @ali/lowcode-react-simulator-renderer +<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-react-simulator-renderer + +<a name="1.0.9-0"></a> +## 1.0.9-0 (2020-09-14) + + +### Bug Fixes + +* 🐛 style setter not working ([c88ea6b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c88ea6b)) +* 🐛 增加传入组件children的默认值[], 对之前的非健壮组件做兼容 ([af0f2df](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/af0f2df)) +* cloneElement bug ([d5c5614](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d5c5614)) +* createComponent 支持所有 schema ([7f946f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7f946f5)) +* layout tabbar number ([3975571](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3975571)) +* live editing outline colore ([791771c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/791771c)) +* merge ([ac55847](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ac55847)) +* modify layout props ([9baba75](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9baba75)) +* react simulator rendererContainer props ([6e1eac0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e1eac0)) +* render children ([487f257](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/487f257)) +* 临时解决 lowCodeComponent 性能问题 ([25b4ba2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/25b4ba2)) +* 低代码组件 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)) +* 修复低代码组件内部部分区域无法选中 ([f0adaa5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f0adaa5)) +* 合并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)) +* 处理 schema id 重复的问题 ([d2316be](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d2316be)) +* 支持事件 VE_EVENTS.VE_PAGE_PAGE_READY ([935ffad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/935ffad)) +* 支持低代码组件样式 ([6e64be1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e64be1)) +* 简化 onPageReady 实现逻辑 ([a36e5f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a36e5f2)) +* 调整 upgrade 和 init 的流程 ([09fc1a0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/09fc1a0)) +* 适配Nav组件 ([7e9829f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7e9829f)) +* 部分低代码组件渲染报错 ([093015c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/093015c)) + + +### Features + +* 🎸 为了能更好地在设计态模拟, 将 device 透传到组件树根组件上 ([7ab7def](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7ab7def)) +* 🎸 为容器的占位元素增加一个特定的 class 方便在设计器里定制样式 ([5077141](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5077141)) +* 🎸 容器占位原生的样式从内联改成写在 CSS 文件里,方便被覆盖样式 ([a616e18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a616e18)) +* get layout config from legao-design ([b9103a2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b9103a2)) +* support plaintext liveediting ([ea62f12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ea62f12)) +* 在 editor-preset-vision 中对 legao schema 进行向前兼容 ([7867917](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7867917)) +* 容器组件支持传入 placeholder 和对应样式 ([0c4de43](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0c4de43)) +* 支持低代码组件设计态实时改变 ([c5a817b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c5a817b)) + + + + <a name="1.0.8-0"></a> ## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-simulator-renderer@0.8.61...@ali/lowcode-react-simulator-renderer@1.0.8-0) (2020-09-09) diff --git a/packages/react-simulator-renderer/src/renderer-view.tsx b/packages/react-simulator-renderer/src/renderer-view.tsx index 8d7599ef7..aa758fe16 100644 --- a/packages/react-simulator-renderer/src/renderer-view.tsx +++ b/packages/react-simulator-renderer/src/renderer-view.tsx @@ -1,9 +1,8 @@ import LowCodeRenderer from '@ali/lowcode-react-renderer'; -// import { isObject } from 'lodash'; import { ReactInstance, Fragment, Component, createElement } from 'react'; import { observer } from '@recore/obx-react'; -import { SimulatorRenderer } from './renderer'; -import { host } from './host'; +import { SimulatorRendererContainer, DocumentInstance } from './renderer'; +import { Router, Route, Switch } from 'react-router'; import './renderer.less'; // patch cloneElement avoid lost keyProps @@ -40,17 +39,38 @@ const originCloneElement = window.React.cloneElement; return originCloneElement(child, props, ...rest); }; -export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> { +export default class SimulatorRendererView extends Component<{ rendererContainer: SimulatorRendererContainer }> { render() { - const { renderer } = this.props; + const { rendererContainer } = this.props; return ( - <Layout renderer={renderer}> - <Renderer renderer={renderer} /> - </Layout> + <Router history={rendererContainer.history}> + <Layout rendererContainer={rendererContainer}> + <Routes rendererContainer={rendererContainer} /> + </Layout> + </Router> ); } } +@observer +export class Routes extends Component<{ rendererContainer: SimulatorRendererContainer }> { + render() { + const { rendererContainer } = this.props; + return ( + <Switch> + {rendererContainer.documentInstances.map((instance) => { + return ( + <Route + path={instance.path} + key={instance.id} + render={(routeProps) => <Renderer documentInstance={instance} rendererContainer={rendererContainer} {...routeProps} />} + /> + ); + })} + </Switch> + ); + } +} function ucfirst(s: string) { return s.charAt(0).toUpperCase() + s.substring(1); } @@ -72,18 +92,30 @@ function getDeviceView(view: any, device: string, mode: string) { } @observer -class Layout extends Component<{ renderer: SimulatorRenderer }> { +class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }> { shouldComponentUpdate() { return false; } render() { - const { renderer, children } = this.props; - const { layout } = renderer; - + const { rendererContainer, children } = this.props; + const layout = rendererContainer.layout; if (layout) { - const { Component, props } = layout; - return <Component props={props}>{children}</Component>; + const { Component, props, componentName } = layout; + if (Component) { + return <Component key='layout' props={props}>{children}</Component>; + } + if (componentName && rendererContainer.getComponent(componentName)) { + return createElement( + rendererContainer.getComponent(componentName), + { + ...props, + rendererContainer, + key: 'layout', + }, + [children], + ); + } } return <Fragment>{children}</Fragment>; @@ -91,20 +123,23 @@ class Layout extends Component<{ renderer: SimulatorRenderer }> { } @observer -class Renderer extends Component<{ renderer: SimulatorRenderer }> { +class Renderer extends Component<{ + rendererContainer: SimulatorRendererContainer; + documentInstance: DocumentInstance } + > { shouldComponentUpdate() { return false; } render() { - const { renderer } = this.props; - const { device, designMode } = renderer; + const { documentInstance, rendererContainer: renderer } = this.props; + const { container } = documentInstance; + const { designMode, device } = container; return ( <LowCodeRenderer - schema={renderer.schema} - components={renderer.components} - appHelper={renderer.context} - // context={renderer.context} + schema={documentInstance.schema} + components={container.components} + appHelper={container.context} designMode={designMode} device={device} suspended={renderer.suspended} @@ -112,7 +147,7 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> { customCreateElement={(Component: any, props: any, children: any) => { const { __id, __desingMode, ...viewProps } = props; viewProps.componentId = __id; - const leaf = host.document.getNode(__id); + const leaf = documentInstance.getNode(__id); viewProps._leaf = leaf; viewProps._componentName = leaf?.componentName; // 如果是容器 && 无children && 高宽为空 增加一个占位容器,方便拖动 @@ -149,21 +184,20 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> { selectMode: false, triggerType: 'click', }); - console.info('menuprops', viewProps); } return createElement( getDeviceView(Component, device, designMode), viewProps, - children, + leaf?.isContainer() ? (children == null ? [] : Array.isArray(children) ? children : [children]) : children, ); }} onCompGetRef={(schema: any, ref: ReactInstance | null) => { - renderer.mountInstance(schema.id, ref); + documentInstance.mountInstance(schema.id, ref); }} - // onCompGetCtx={(schema: any, ctx: object) => { - // renderer.mountContext(schema.id, ctx); - // }} + //onCompGetCtx={(schema: any, ctx: object) => { + // documentInstance.mountContext(schema.id, ctx); + //}} /> ); } diff --git a/packages/react-simulator-renderer/src/renderer.less b/packages/react-simulator-renderer/src/renderer.less index 0e77cb867..8bf34b972 100644 --- a/packages/react-simulator-renderer/src/renderer.less +++ b/packages/react-simulator-renderer/src/renderer.less @@ -54,35 +54,16 @@ html.engine-cursor-ew-resize, html.engine-cursor-ew-resize * { } .lc-container-placeholder { - height: 44px; - line-height: 44px; - background-color: #f0f0f0; - border-color: #a7b1bd; + min-height: 60px; + height: 100%; + width: 100%; + background-color: rgb(240, 240, 240); border: 1px dotted; - color: #a7b1bd; - text-align: center; -} - -.engine-empty { - background: #f2f3f5; - color: #a7b1bd; - outline: 1px dashed rgba(31, 56, 88, 0.2); - outline-offset: -1px !important; - height: 66px; - max-height: 100%; - min-width: 140px; - text-align: center; - overflow: hidden; + color: rgb(167, 177, 189); display: flex; align-items: center; -} - -.engine-empty:before { - content: '\62D6\62FD\7EC4\4EF6\6216\6A21\677F\5230\8FD9\91CC'; + justify-content: center; font-size: 14px; - z-index: 1; - width: 100%; - white-space: nowrap; } body.engine-document { @@ -93,16 +74,6 @@ body.engine-document { &:after { clear: both; } - - /* - .next-input-group, - .next-checkbox-group,.next-date-picker,.next-input,.next-month-picker, - .next-number-picker,.next-radio-group,.next-range,.next-range-picker, - .next-rating,.next-select,.next-switch,.next-time-picker,.next-upload, - .next-year-picker, - .next-breadcrumb-item,.next-calendar-header,.next-calendar-table { - pointer-events: none !important; - }*/ } .engine-live-editing { diff --git a/packages/react-simulator-renderer/src/renderer.ts b/packages/react-simulator-renderer/src/renderer.ts index d815f6809..67df2b641 100644 --- a/packages/react-simulator-renderer/src/renderer.ts +++ b/packages/react-simulator-renderer/src/renderer.ts @@ -3,13 +3,15 @@ import { render as reactRender } from 'react-dom'; import { host } from './host'; import SimulatorRendererView from './renderer-view'; import { computed, obx } from '@recore/obx'; -import { Asset, +import { getClientRects } from './utils/get-client-rects'; +import { reactFindDOMNodes, FIBER_KEY } from './utils/react-find-dom-nodes'; +import { + Asset, isElement, cursor, setNativeSelection, buildComponents, getSubComponent, - AssetLoader, } from '@ali/lowcode-utils'; import { getClientRects } from './utils/get-client-rects'; import { reactFindDOMNodes, FIBER_KEY } from './utils/react-find-dom-nodes'; @@ -18,85 +20,36 @@ import { RootSchema, ComponentSchema, TransformStage, NodeSchema } from '@ali/lo // import { isESModule, isElement, acceptsRef, wrapReactClass, cursor, setNativeSelection } from '@ali/lowcode-utils'; // import { RootSchema, NpmInfo, ComponentSchema, TransformStage, NodeSchema } from '@ali/lowcode-types'; // just use types -import { BuiltinSimulatorRenderer, NodeInstance, Component } from '@ali/lowcode-designer'; +import { BuiltinSimulatorRenderer, NodeInstance, Component, DocumentModel } from '@ali/lowcode-designer'; +import { createMemoryHistory, MemoryHistory } from 'history'; import Slot from './builtin-components/slot'; import Leaf from './builtin-components/leaf'; +import { withQueryParams, parseQuery } from './utils/url'; -const loader = new AssetLoader(); - -export class SimulatorRenderer implements BuiltinSimulatorRenderer { - readonly isSimulatorRenderer = true; - - private dispose?: () => void; - - constructor() { - if (!host) { - return; - } - - this.dispose = host.connect(this, () => { - // sync layout config - // sync schema - this._schema = host.document.export(1); - // todo: split with others, not all should recompute - if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) { - this._libraryMap = host.libraryMap || {}; - this._componentsMap = host.designer.componentsMap; - this.buildComponents(); - } - - // sync designMode - this._designMode = host.designMode; - - // sync requestHandlersMap - this._requestHandlersMap = host.requestHandlersMap; - - // sync suspended - - // sync scope - - // sync device - this._device = host.device; - }); - - host.componentsConsumer.consume(async (componentsAsset) => { - if (componentsAsset) { - await this.load(componentsAsset); - this.buildComponents(); - } - }); - host.injectionConsumer.consume(() => { - // sync utils, i18n, contants,... config - this._appContext = { - utils: {}, - constants: { - name: 'demo', - }, - requestHandlersMap: this._requestHandlersMap, - }; - }); - } - - @computed get layout(): any { - // TODO: parse layout Component - return null; - } +export class DocumentInstance { + private instancesMap = new Map<string, ReactInstance[]>(); @obx.ref private _schema?: RootSchema; - @computed get schema(): any { return this._schema; } - private _libraryMap: { [key: string]: string } = {}; + private dispose?: () => void; - private buildComponents() { - this._components = { - ...builtinComponents, - ...buildComponents(this._libraryMap, this._componentsMap), - }; + constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) { + this.dispose = host.autorun(() => { + // sync schema + this._schema = document.export(1); + }); } + // private _libraryMap: { [key: string]: string } = {}; + // private buildComponents() { + // this._components = { + // ...builtinComponents, + // ...buildComponents(this._libraryMap, this._componentsMap), + // }; + // } @obx.ref private _components: any = {}; @computed get components(): object { @@ -144,43 +97,38 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { return null; } - /** - * 加载资源 - */ - load(asset: Asset): Promise<any> { - return loader.load(asset); + get path(): string { + return '/' + this.document.fileName; } - async loadAsyncLibrary(asyncLibraryMap) { - await loader.loadAsyncLibrary(asyncLibraryMap); - this.buildComponents(); + get id() { + return this.document.id; } - private instancesMap = new Map<string, ReactInstance[]>(); - private unmountIntance(id: string, instance: ReactInstance) { const instances = this.instancesMap.get(id); if (instances) { const i = instances.indexOf(instance); if (i > -1) { instances.splice(i, 1); - host.setInstance(id, instances); + host.setInstance(this.document.id, id, instances); } } } mountInstance(id: string, instance: ReactInstance | null) { - const { instancesMap } = this; + const docId = this.document.id; + const instancesMap = this.instancesMap; if (instance == null) { let instances = this.instancesMap.get(id); if (instances) { instances = instances.filter(checkInstanceMounted); if (instances.length > 0) { instancesMap.set(id, instances); - host.setInstance(id, instances); + host.setInstance(this.document.id, id, instances); } else { instancesMap.delete(id); - host.setInstance(id, null); + host.setInstance(this.document.id, id, null); } } return; @@ -209,6 +157,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { } (instance as any)[SYMBOL_VNID] = id; + (instance as any)[SYMBOL_VDID] = docId; let instances = this.instancesMap.get(id); if (instances) { const l = instances.length; @@ -225,13 +174,156 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { instances = [instance]; } instancesMap.set(id, instances); - host.setInstance(id, instances); + host.setInstance(this.document.id, id, instances); } - private ctxMap = new Map<string, object>(); + mountContext(docId: string, id: string, ctx: object) { + // this.ctxMap.set(id, ctx); + } - mountContext(id: string, ctx: object) { - this.ctxMap.set(id, ctx); + getNode(id: string): Node | null { + return this.document.getNode(id); + } +} + +export class SimulatorRendererContainer implements BuiltinSimulatorRenderer { + readonly isSimulatorRenderer = true; + private dispose?: () => void; + readonly history: MemoryHistory; + + @obx.ref private _documentInstances: DocumentInstance[] = []; + get documentInstances() { + return this._documentInstances; + } + + constructor() { + this.dispose = host.connect(this, () => { + // sync layout config + this._layout = host.project.get('config').layout; + + // todo: split with others, not all should recompute + if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) { + this._libraryMap = host.libraryMap || {}; + this._componentsMap = host.designer.componentsMap; + // 需要注意的是,autorun 依赖收集的是同步执行的代码,所以 await / promise / callback 里的变量不会被收集依赖 + // 此例中,host.designer.componentsMap 是需要被收集依赖的,否则无法响应式 + // await host.waitForCurrentDocument(); + this.buildComponents(); + } + + // sync designMode + this._designMode = host.designMode; + + // sync device + this._device = host.device; + }); + const documentInstanceMap = new Map<string, DocumentInstance>(); + let initialEntry = '/'; + host.autorun(({ firstRun }) => { + this._documentInstances = host.project.documents.map((doc) => { + let inst = documentInstanceMap.get(doc.id); + if (!inst) { + inst = new DocumentInstance(this, doc); + documentInstanceMap.set(doc.id, inst); + } + return inst; + }); + const path = host.project.currentDocument + ? documentInstanceMap.get(host.project.currentDocument.id)!.path + : '/'; + if (firstRun) { + initialEntry = path; + } else if (this.history.location.pathname !== path) { + this.history.replace(path); + } + }); + const history = createMemoryHistory({ + initialEntries: [initialEntry], + }); + this.history = history; + history.listen((location, action) => { + host.project.open(location.pathname.substr(1)); + }); + host.componentsConsumer.consume(async (componentsAsset) => { + if (componentsAsset) { + await this.load(componentsAsset); + this.buildComponents(); + } + }); + this._appContext = { + utils: { + router: { + push(path: string, params?: object) { + history.push(withQueryParams(path, params)); + }, + replace(path: string, params?: object) { + history.replace(withQueryParams(path, params)); + }, + }, + legaoBuiltins: { + getUrlParams() { + const search = history.location.search; + return parseQuery(search); + } + } + }, + constants: {}, + }; + host.injectionConsumer.consume((data) => { + // sync utils, i18n, contants,... config + }); + } + + @obx private _layout: any = null; + + @computed get layout(): any { + // TODO: parse layout Component + return this._layout; + } + + set layout(value: any) { + this._layout = value; + } + + private _libraryMap: { [key: string]: string } = {}; + + private buildComponents() { + // TODO: remove this.createComponent + this._components = buildComponents(this._libraryMap, this._componentsMap, this.createComponent.bind(this)); + this._components = { + ...builtinComponents, + ...this._components, + }; + } + @obx.ref private _components: any = {}; + + @computed get components(): object { + // 根据 device 选择不同组件,进行响应式 + // 更好的做法是,根据 device 选择加载不同的组件资源,甚至是 simulatorUrl + return this._components; + } + // context from: utils、constants、history、location、match + @obx.ref private _appContext = {}; + @computed get context(): any { + return this._appContext; + } + @obx.ref private _designMode: string = 'design'; + @computed get designMode(): any { + return this._designMode; + } + @obx.ref private _device: string = 'default'; + @computed get device() { + return this._device; + } + @obx.ref private _componentsMap = {}; + @computed get componentsMap(): any { + return this._componentsMap; + } + /** + * 加载资源 + */ + load(asset: Asset): Promise<any> { + return loader.load(asset); } getComponent(componentName: string) { @@ -251,111 +343,8 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { subs.unshift(sub); componentName = paths.join('.'); } - } - getComponentInstances(id: string): ReactInstance[] | null { - return this.instancesMap.get(id) || null; - } - - createComponent(schema: NodeSchema): Component | null { - let _schema: any = { - ...schema, - }; - _schema.methods = {}; - _schema.lifeCycles = {}; - - if (schema.componentName === 'Component' && (schema as ComponentSchema).css) { - const doc = window.document; - const s = doc.createElement('style'); - s.setAttribute('type', 'text/css'); - s.setAttribute('id', `Component-${schema.id || ''}`); - s.appendChild(doc.createTextNode((schema as ComponentSchema).css || '')); - doc.getElementsByTagName('head')[0].appendChild(s); - } - - const node = host.document.createNode(_schema); - _schema = node.export(TransformStage.Render); - - const processPropsSchema = (propsSchema: any, propsMap: any): any => { - if (!propsSchema) { - return {}; - } - - const result = { ...propsSchema }; - const reg = /^(?:this\.props|props)\.(\S+)$/; - Object.keys(result).map((key: string) => { - if (result[key]?.type === 'JSExpression') { - const { value } = result[key]; - const matched = reg.exec(value); - if (matched) { - const propName = matched[1]; - result[key] = propsMap[propName]; - } - } else if (result[key]?.type === 'JSSlot') { - const schema = result[key].value; - result[key] = createElement(Ele, { schema, propsMap: {} }); - } - }); - - return result; - }; - - const renderer = this; - const { componentsMap } = renderer; - - class Ele extends React.Component<{ schema: any; propsMap: any }> { - private isModal: boolean; - - constructor(props: any) { - super(props); - const componentMeta = host.document.getComponentMeta(props.schema.componentName); - if (componentMeta?.prototype?.isModal()) { - this.isModal = true; - } - } - - render() { - if (this.isModal) { - return null; - } - const { schema, propsMap } = this.props; - const Com = componentsMap[schema.componentName]; - if (!Com) { - return null; - } - let children = null; - if (schema.children && schema.children.length > 0) { - children = schema.children.map((item: any) => createElement(Ele, { schema: item, propsMap })); - } - const props = processPropsSchema(schema.props, propsMap); - const _leaf = host.document.createNode(schema); - - return createElement(Com, { ...props, _leaf }, children); - } - } - - class Com extends React.Component { - // TODO: 暂时解决性能问题 - shouldComponentUpdate() { - return false; - } - - render() { - const { componentName } = _schema; - if (componentName === 'Component') { - let children = []; - const propsMap = this.props || {}; - if (_schema.children && Array.isArray(_schema.children)) { - children = _schema.children.map((item: any) => createElement(Ele, { schema: item, propsMap })); - } - return createElement('div', {}, children); - } else { - return createElement(Ele, { schema: _schema, propsMap: {} }); - } - } - } - - return Com; + return null; } getClosestNodeInstance(from: ReactInstance, nodeId?: string): NodeInstance<ReactInstance> | null { @@ -386,6 +375,33 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { cursor.release(); } + createComponent(schema: NodeSchema): Component | null { + const _schema: any = { + ...schema, + }; + _schema.methods = {}; + _schema.lifeCycles = {}; + + if (schema.componentName === 'Component' && (schema as ComponentSchema).css) { + const doc = window.document; + const s = doc.createElement('style'); + s.setAttribute('type', 'text/css'); + s.setAttribute('id', `Component-${schema.id || ''}`); + s.appendChild(doc.createTextNode((schema as ComponentSchema).css || '')); + doc.getElementsByTagName('head')[0].appendChild(s); + } + + // const node = host.currentDocument?.createNode(_schema); + // _schema = node?.export(TransformStage.Render) || {}; + + + + const renderer = this; + const { componentsMap } = renderer; + + return getComponentController(schema, componentsMap); + } + private _running = false; run() { @@ -400,12 +416,13 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { document.body.appendChild(container); container.id = containerId; } + // ==== compatiable vision document.documentElement.classList.add('engine-page'); document.body.classList.add('engine-document'); // important! Stylesheet.invoke depends - reactRender(createElement(SimulatorRendererView, { renderer: this }), container); - host.document.setRendererReady(this); + reactRender(createElement(SimulatorRendererView, { rendererContainer: this }), container); + host.project.setRendererReady(this); } } @@ -429,6 +446,7 @@ function cacheReactKey(el: Element): Element { } const SYMBOL_VNID = Symbol('_LCNodeId'); +const SYMBOL_VDID = Symbol('_LCDocId'); function getClosestNodeInstance(from: ReactInstance, specId?: string): NodeInstance<ReactInstance> | null { let el: any = from; @@ -442,8 +460,10 @@ function getClosestNodeInstance(from: ReactInstance, specId?: string): NodeInsta while (el) { if (SYMBOL_VNID in el) { const nodeId = el[SYMBOL_VNID]; + const docId = el[SYMBOL_VDID]; if (!specId || specId === nodeId) { return { + docId, nodeId, instance: el, }; @@ -462,13 +482,16 @@ function getNodeInstance(fiberNode: any, specId?: string): NodeInstance<ReactIns const instance = fiberNode?.stateNode; if (instance && SYMBOL_VNID in instance) { const nodeId = instance[SYMBOL_VNID]; + const docId = instance[SYMBOL_VDID]; if (!specId || specId === nodeId) { return { + docId, nodeId, instance, }; } } + if (!instance && !fiberNode?.return) return null; return getNodeInstance(fiberNode?.return); } @@ -479,4 +502,92 @@ function checkInstanceMounted(instance: any): boolean { return true; } -export default new SimulatorRenderer(); +const processPropsSchema = (propsSchema: any, propsMap: any, componentsMap: any): any => { + if (!propsSchema) { + return {}; + } + + const result = { ...propsSchema }; + const reg = /^(?:this\.props|props)\.(\S+)$/; + Object.keys(result).map((key: string) => { + if (result[key]?.type === 'JSExpression') { + const { value } = result[key]; + const matched = reg.exec(value); + if (matched) { + const propName = matched[1]; + result[key] = propsMap[propName]; + } + } else if (result[key]?.type === 'JSSlot') { + const schema = result[key].value; + result[key] = createElement(ComponentCreator, { schema, propsMap: {}, componentsMap }); + } + }); + + return result; +}; + +class ComponentCreator extends React.Component<{ schema: any; propsMap: any, componentsMap: any }> { + private isModal: boolean; + + constructor(props: any) { + super(props); + const componentMeta = host.currentDocument?.getComponentMeta(props.schema.componentName); + if (componentMeta?.isModal) { + this.isModal = true; + } + } + + render() { + if (this.isModal) { + return null; + } + const { schema, propsMap, componentsMap } = this.props; + const ComponentClass = componentsMap[schema.componentName]; + if (!ComponentClass) { + return null; + } + let children = null; + if (schema.children && schema.children.length > 0) { + children = schema.children.map((item: any) => createElement(ComponentCreator, { schema: item, propsMap, componentsMap })); + } + const props = processPropsSchema(schema.props, propsMap, componentsMap); + const _leaf = host.currentDocument?.createNode(schema); + + return createElement(ComponentClass, { ...props, _leaf }, children); + } +} + +function getComponentController(schema: NodeSchema, componentsMap: any) { + class ComponentController extends React.Component<{ schema: any }> { + renderSchema: any; + + constructor(props: any) { + super(props); + const node = host.currentDocument?.createNode(schema); + this.renderSchema = node?.export(TransformStage.Render) || {}; + } + + // TODO: 暂时解决性能问题 + shouldComponentUpdate() { + return false; + } + + render() { + const { renderSchema } = this; + const { componentName } = renderSchema; + if (componentName === 'Component') { + let children = [] as any; + const propsMap = this.props || {}; + if (renderSchema.children && Array.isArray(renderSchema.children)) { + children = renderSchema.children.map((item: any) => createElement(ComponentCreator, { schema: item, propsMap, componentsMap })); + } + return createElement('div', {}, children); + } else { + return createElement(ComponentCreator, { schema, propsMap: {}, componentsMap }); + } + } + } + return ComponentController; +} + +export default new SimulatorRendererContainer(); diff --git a/packages/react-simulator-renderer/src/utils/react-find-dom-nodes.ts b/packages/react-simulator-renderer/src/utils/react-find-dom-nodes.ts index f7dde3925..36aa3bfb2 100644 --- a/packages/react-simulator-renderer/src/utils/react-find-dom-nodes.ts +++ b/packages/react-simulator-renderer/src/utils/react-find-dom-nodes.ts @@ -1,4 +1,5 @@ import { ReactInstance } from 'react'; +import { findDOMNode } from 'react-dom'; import { isElement } from '@ali/lowcode-utils'; import { isDOMNode } from './is-dom-node'; @@ -29,5 +30,5 @@ export function reactFindDOMNodes(elem: ReactInstance | null): Array<Element | T const elements: Array<Element | Text> = []; const fiberNode = (elem as any)[FIBER_KEY]; elementsFromFiber(fiberNode.child, elements); - return elements.length > 0 ? elements : null; + return elements.length > 0 ? elements : [findDOMNode(elem)]; } diff --git a/packages/react-simulator-renderer/src/utils/url.ts b/packages/react-simulator-renderer/src/utils/url.ts new file mode 100644 index 000000000..d720323b3 --- /dev/null +++ b/packages/react-simulator-renderer/src/utils/url.ts @@ -0,0 +1,74 @@ +/** + * Parse queryString + * @param {String} str '?q=query&b=test' + * @return {Object} + */ +export function parseQuery(str: string): object { + const ret: any = {}; + + if (typeof str !== 'string') { + return ret; + } + + const s = str.trim().replace(/^(\?|#|&)/, ''); + + if (!s) { + return ret; + } + + s.split('&').forEach((param) => { + const parts = param.replace(/\+/g, ' ').split('='); + let key = parts.shift()!; + let val: any = parts.length > 0 ? parts.join('=') : undefined; + + key = decodeURIComponent(key); + + val = val === undefined ? null : decodeURIComponent(val); + + if (ret[key] === undefined) { + ret[key] = val; + } else if (Array.isArray(ret[key])) { + ret[key].push(val); + } else { + ret[key] = [ret[key], val]; + } + }); + + return ret; +} + +/** + * Stringify object to query parammeters + * @param {Object} obj + * @return {String} + */ +export function stringifyQuery(obj: any): string { + const param: string[] = []; + Object.keys(obj).forEach((key) => { + let value = obj[key]; + if (value && typeof value === 'object') { + value = JSON.stringify(value); + } + param.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); + }); + return param.join('&'); +} + +export function uriEncode(uri: string) { + return encodeURIComponent(uri); +} + +export function uriDecode(uri: string) { + return decodeURIComponent(uri); +} + +export function withQueryParams(url: string, params?: object) { + const queryStr = params ? stringifyQuery(params) : ''; + if (queryStr === '') { + return url; + } + const urlSplit = url.split('#'); + const hash = urlSplit[1] ? `#${urlSplit[1]}` : ''; + const urlWithoutHash = urlSplit[0]; + return `${urlWithoutHash}${~urlWithoutHash.indexOf('?') ? '&' : '?'}${queryStr}${hash}`; +} diff --git a/packages/react-simulator-renderer/tsconfig.json b/packages/react-simulator-renderer/tsconfig.json index c37b76ecc..9519ab847 100644 --- a/packages/react-simulator-renderer/tsconfig.json +++ b/packages/react-simulator-renderer/tsconfig.json @@ -4,6 +4,6 @@ "outDir": "lib" }, "include": [ - "./src/" + "./src/", "../../index.ts" ] } diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 9847aa33e..7a88fb979 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -48,6 +48,161 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline <a name="1.0.16"></a> ## [1.0.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-types@1.0.15...@ali/lowcode-types@1.0.16) (2020-11-05) +* 低成本方案支持绝对布局容器 ([a6067e8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a6067e8)) + + + +<a name="0.12.1-18"></a> +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + + +<a name="0.12.1-17"></a> +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + + +<a name="0.12.1-16"></a> +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + +<a name="0.12.1-15"></a> +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + + +<a name="0.12.1-14"></a> +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + + +<a name="0.12.1-13"></a> +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + + +<a name="0.12.1-12"></a> +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + + +<a name="0.12.1-11"></a> +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + +<a name="0.12.1-10"></a> +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + +<a name="0.12.1-9"></a> +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + +<a name="0.12.1-8"></a> +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + +<a name="0.12.1-7"></a> +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + + + +<a name="0.13.1-11"></a> +## [0.13.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-10...v0.13.1-11) (2020-11-02) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-10"></a> +## [0.13.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-9...v0.13.1-10) (2020-10-26) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-9"></a> +## [0.13.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-9) (2020-10-26) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-8"></a> +## [0.13.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-7...v0.13.1-8) (2020-10-26) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-7"></a> +## [0.13.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-6...v0.13.1-7) (2020-10-23) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-6"></a> +## [0.13.1-6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-5...v0.13.1-6) (2020-10-22) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-5"></a> +## [0.13.1-5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-4...v0.13.1-5) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-4"></a> +## [0.13.1-4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-3...v0.13.1-4) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-3"></a> +## [0.13.1-3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-2...v0.13.1-3) (2020-10-19) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-2"></a> +## [0.13.1-2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.13.1-2) (2020-10-19) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<a name="0.13.1-1"></a> +## [0.13.1-1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-3...v0.13.1-1) (2020-10-12) + + + + +**Note:** Version bump only for package @ali/lowcode-types + +<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) @@ -128,6 +283,16 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline * 🐛 use lowcode types ([b11425b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b11425b)) * fix test result ([7f6fbe8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7f6fbe8)) * miniAppBuildType config(temp) ([584b4c2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/584b4c2)) +* currentPage.id 返回 formUuid ([775725d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/775725d)) +* fix NextTable callback function ([ce77375](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ce77375)) +* panel visible time ([18ac1fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18ac1fa)) +* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c)) +* 低代码组件 props 显示 object 问题 ([116498e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/116498e)) +* 修复 slot 获取初始值异常的 bug ([63b19f1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/63b19f1)) +* 修复判断动态 setter 的逻辑 ([d195d7f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d195d7f)) +* 兼容事件绑定 ([f4c07af](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f4c07af)) +* 可以降级到历史的 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)) ### Features @@ -137,6 +302,14 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline * 🎸 根据低代码协议文档, 将 BlockSchema 也改成继承自 ContainerSchema ([7901c8e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7901c8e)) * 🎸 根据低代码协议文档, 完善UtilsMap的定义 ([7fe4bc0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7fe4bc0)) * 🎸 urlParams 类型的数据源不需要 options, 所以 options 改成可选为好 ([8114c6f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8114c6f)) +* 🎸 增加icon相关的判断函数 ([89064f5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/89064f5)) +* add filter reducer ([17c6ed3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/17c6ed3)) +* merge live mode ([92c3039](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/92c3039)) +* show value state ([bd49e50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd49e50)) +* 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)) +* 修复状态切换失效 ([2e3f60d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e3f60d)) +* 编辑器 hooks 能力实现 ([f3ac23b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f3ac23b)) diff --git a/packages/types/src/app-config.ts b/packages/types/src/app-config.ts new file mode 100644 index 000000000..6343645c9 --- /dev/null +++ b/packages/types/src/app-config.ts @@ -0,0 +1,19 @@ +export interface AppConfig { + sdkVersion?: string; + historyMode?: string; + targetRootID?: string; + layout?: Layout; + theme?: Theme; + [key: string]: any; +} + +interface Theme { + package: string; + version: string; + primary: string; +} + +interface Layout { + componentName?: string; + props?: Record<string, any>; +} diff --git a/packages/types/src/metadata.ts b/packages/types/src/metadata.ts index 39d250135..4fdefa653 100644 --- a/packages/types/src/metadata.ts +++ b/packages/types/src/metadata.ts @@ -65,6 +65,7 @@ export interface Experimental { autoruns?: AutorunItem[]; callbacks?: Callbacks; initialChildren?: NodeData[] | ((target: SettingTarget) => NodeData[]); + isAbsoluteLayoutContainer: boolean; // 样式 及 位置,handle上必须有明确的标识以便事件路由判断,或者主动设置事件独占模式 // NWSE 是交给引擎计算放置位置,ReactElement 必须自己控制初始位置 @@ -88,6 +89,7 @@ export interface Experimental { // 纯文本编辑:如果 children 内容是 // 文本编辑:配置 liveTextEditing?: LiveTextEditingConfig[]; + isTopFixed?: boolean; } // thinkof Array diff --git a/packages/types/src/schema.ts b/packages/types/src/schema.ts index 97df0921e..b43d775f1 100644 --- a/packages/types/src/schema.ts +++ b/packages/types/src/schema.ts @@ -9,6 +9,7 @@ import { } from './value-type'; import { I18nMap } from './i18n'; import { UtilsMap } from './utils'; +import { AppConfig } from './app-config'; // 搭建基础协议 - 单个组件树节点描述 // 转换成一个 .jsx 文件内 React Class 类 render 函数返回的 jsx 代码 @@ -28,6 +29,7 @@ export interface NodeSchema { ignore?: boolean; locked?: boolean; hidden?: boolean; + isTopFixed?: boolean; } export type PropsMap = CompositeObject; @@ -104,6 +106,7 @@ export interface ProjectSchema { constants?: JSONObject; css?: string; dataSource?: DataSource; + config?: AppConfig; id?: string; config?: Record<string, any>; meta?: Record<string, any>; diff --git a/packages/types/src/utils.ts b/packages/types/src/utils.ts index b8ac406ef..fbccf0844 100644 --- a/packages/types/src/utils.ts +++ b/packages/types/src/utils.ts @@ -14,4 +14,14 @@ export type ExternalUtils = { }; export type UtilItem = InternalUtils | ExternalUtils; -export type UtilsMap = UtilItem[]; +export type UtilsMap = Array< +| { + name: string; + type: 'npm'; + content: NpmInfo; +} +| { + name: string; + type: ''; +} +>; diff --git a/packages/types/src/value-type.ts b/packages/types/src/value-type.ts index 47904a6b7..8f05ae7a4 100644 --- a/packages/types/src/value-type.ts +++ b/packages/types/src/value-type.ts @@ -105,6 +105,10 @@ export function isJSFunction(x: any): x is JSFunction { return typeof x === 'object' && x && x.type === 'JSFunction'; } +<<<<<<< HEAD +======= + +>>>>>>> origin/refactor/vision-code-split export function isJSSlot(data: any): data is JSSlot { return data && data.type === 'JSSlot'; } diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index ee47951b4..f30d8e99d 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -75,13 +75,173 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @ali/lowcode-utils -<a name="1.0.12"></a> -## [1.0.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-utils@1.0.11...@ali/lowcode-utils@1.0.12) (2020-10-20) +<a name="0.13.1-18"></a> +## [0.13.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-15...v0.13.1-18) (2020-11-20) -### Features +### Bug Fixes -* 支持 FunctionComponent 选中 ([d2d44e6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d2d44e6)) +* 修复 setDevice 里获取 currentDocument 的逻辑 ([275b7aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/275b7aa)) + + + + +<a name="0.13.1-15"></a> +## [0.13.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-15) (2020-11-18) + + + +<a name="0.12.1-19"></a> +## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17) + + + +<a name="0.12.1-18"></a> +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + + +<a name="0.12.1-17"></a> +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + + +<a name="0.12.1-16"></a> +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + +<a name="0.12.1-15"></a> +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + + +<a name="0.12.1-14"></a> +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + + +<a name="0.12.1-13"></a> +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + + +<a name="0.12.1-12"></a> +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + + +<a name="0.12.1-11"></a> +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + +<a name="0.12.1-10"></a> +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + +<a name="0.12.1-9"></a> +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + +<a name="0.12.1-8"></a> +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + +<a name="0.12.1-7"></a> +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + + + +**Note:** Version bump only for package @ali/lowcode-utils + +<a name="0.13.1-12"></a> +## [0.13.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-11...v0.13.1-12) (2020-11-18) + + + +<a name="0.12.1-19"></a> +## [0.12.1-19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-18...v0.12.1-19) (2020-10-17) + + + +<a name="0.12.1-18"></a> +## [0.12.1-18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-17...v0.12.1-18) (2020-10-17) + + + +<a name="0.12.1-17"></a> +## [0.12.1-17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-16...v0.12.1-17) (2020-10-14) + + + +<a name="0.12.1-16"></a> +## [0.12.1-16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-15...v0.12.1-16) (2020-10-12) + + + +<a name="0.12.1-15"></a> +## [0.12.1-15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-1...v0.12.1-15) (2020-10-12) + + + +<a name="0.12.1-14"></a> +## [0.12.1-14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-13...v0.12.1-14) (2020-10-10) + + + +<a name="0.12.1-13"></a> +## [0.12.1-13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-12...v0.12.1-13) (2020-09-28) + + + +<a name="0.12.1-12"></a> +## [0.12.1-12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-11...v0.12.1-12) (2020-09-28) + + + +<a name="0.12.1-11"></a> +## [0.12.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-10...v0.12.1-11) (2020-09-27) + + + +<a name="0.12.1-10"></a> +## [0.12.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-9...v0.12.1-10) (2020-09-27) + + + +<a name="0.12.1-9"></a> +## [0.12.1-9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-8...v0.12.1-9) (2020-09-27) + + + +<a name="0.12.1-8"></a> +## [0.12.1-8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-7...v0.12.1-8) (2020-09-27) + + + +<a name="0.12.1-7"></a> +## [0.12.1-7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.12.1-2...v0.12.1-7) (2020-09-27) + + + + +**Note:** Version bump only for package @ali/lowcode-utils + +<a name="0.13.1-11"></a> +## [0.13.1-11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-10...v0.13.1-11) (2020-11-02) + + + + +**Note:** Version bump only for package @ali/lowcode-utils + +<a name="0.13.1-10"></a> +## [0.13.1-10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-9...v0.13.1-10) (2020-10-26) diff --git a/packages/utils/package.json b/packages/utils/package.json index a5fa2a836..faec9c0f3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -16,6 +16,7 @@ "dependencies": { "@ali/lowcode-types": "^1.0.20", "@alifd/next": "^1.19.16", + "lodash.get": "^4.4.2", "react": "^16" }, "devDependencies": { diff --git a/packages/utils/src/build-components.ts b/packages/utils/src/build-components.ts index 00d6e6b86..37c76580b 100644 --- a/packages/utils/src/build-components.ts +++ b/packages/utils/src/build-components.ts @@ -1,13 +1,14 @@ import { ComponentType, forwardRef, createElement, FunctionComponent } from 'react'; -import { NpmInfo } from '@ali/lowcode-types'; -import { isReactComponent, acceptsRef, wrapReactClass } from './is-react'; +import { NpmInfo, ComponentSchema } from '@ali/lowcode-types'; +import { Component } from '@ali/lowcode-designer'; import { isESModule } from './is-es-module'; +import { isReactComponent, acceptsRef, wrapReactClass } from './is-react'; interface LibraryMap { [key: string]: string; } -function accessLibrary(library: string | Record<string, unknown>) { +export function accessLibrary(library: string | Record<string, unknown>) { if (typeof library !== 'string') { return library; } @@ -75,12 +76,20 @@ function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmI return getSubComponent(library, paths); } -export function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> }) { - const components: any = { - }; +export function buildComponents(libraryMap: LibraryMap, + componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> | ComponentSchema }, + createComponent: (schema: ComponentSchema) => Component | null) { + const components: any = {}; Object.keys(componentsMap).forEach((componentName) => { let component = componentsMap[componentName]; - if (!isReactComponent(component)) { + if (component && (component as ComponentSchema).componentName === 'Component') { + components[componentName] = createComponent(component as ComponentSchema); + } else if (isReactComponent(component)) { + if (!acceptsRef(component)) { + component = wrapReactClass(component as FunctionComponent); + } + components[componentName] = component; + } else { component = findComponent(libraryMap, componentName, component); } if (component) { diff --git a/packages/utils/src/cursor.ts b/packages/utils/src/cursor.ts index 988c6ad03..fea4bce65 100644 --- a/packages/utils/src/cursor.ts +++ b/packages/utils/src/cursor.ts @@ -45,7 +45,7 @@ export class Cursor { } } - private addState(state: string) { + addState(state: string) { if (!this.states.has(state)) { this.states.add(state); document.documentElement.classList.add(`lc-cursor-${state}`); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 0f9fcad2a..e5dd40da3 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -20,3 +20,4 @@ export * from './svg-icon'; export * from './unique-id'; export * from './build-components'; export * from './appHelper'; +export * from './misc'; \ No newline at end of file diff --git a/packages/utils/src/is-form-event.ts b/packages/utils/src/is-form-event.ts index 30e0bdb5f..40d652fb1 100644 --- a/packages/utils/src/is-form-event.ts +++ b/packages/utils/src/is-form-event.ts @@ -7,7 +7,7 @@ export function isFormEvent(e: KeyboardEvent | MouseEvent) { if (t.form || /^(INPUT|SELECT|TEXTAREA)$/.test(t.tagName)) { return true; } - if (/write/.test(window.getComputedStyle(t).getPropertyValue('-webkit-user-modify'))) { + if (t instanceof HTMLElement && /write/.test(window.getComputedStyle(t).getPropertyValue('-webkit-user-modify'))) { return true; } return false; diff --git a/packages/utils/src/is-object.ts b/packages/utils/src/is-object.ts index 4693388e4..1d1646108 100644 --- a/packages/utils/src/is-object.ts +++ b/packages/utils/src/is-object.ts @@ -1,3 +1,7 @@ export function isObject(value: any): value is Record<string, unknown> { return value !== null && typeof value === 'object'; } + +export function isI18NObject(value: any): boolean { + return isObject(value) && value.type === 'i18n'; +} \ No newline at end of file diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts new file mode 100644 index 000000000..a0f8f13ff --- /dev/null +++ b/packages/utils/src/misc.ts @@ -0,0 +1,45 @@ + +import { isI18NObject } from './is-object'; +import get from 'lodash.get'; + +export function isUseI18NSetter(prototype: any, propName: string) { + const configure = prototype?.options?.configure; + if (Array.isArray(configure)) { + return configure.some(c => { + return c.name === propName && c?.setter?.type?.displayName === 'I18nSetter'; + }); + } + return false; +} + +export function convertToI18NObject(v: string | object, locale: string = 'zh_CN') { + if (isI18NObject(v)) return v; + return { type: 'i18n', use: locale, [locale]: v }; +} + +export function isString(v: any): v is string { + return typeof v === 'string'; +} + +function _innerWaitForThing(obj: any, path: string): Promise<any> { + const timeGap = 200; + return new Promise((resolve, reject) => { + setTimeout(() => { + const thing = get(obj, path); + if (thing) { + return resolve(thing); + } + reject(); + }, timeGap); + }).catch(() => { + return _innerWaitForThing(obj, path); + }); +} + +export function waitForThing(obj: any, path: string): Promise<any> { + const thing = get(obj, path); + if (thing) { + return Promise.resolve(thing); + } + return _innerWaitForThing(obj, path); +} diff --git a/scripts/start.sh b/scripts/start.sh index 6834dc5e8..8b9f399c3 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -# FIXME! do not run build -lerna exec --scope @ali/lowcode-datasource-engine -- npm run build -lerna exec --scope @ali/lowcode-rax-renderer -- npm run build -lerna exec --scope @ali/lowcode-react-renderer -- npm run build +lerna exec --scope @ali/lowcode-react-renderer -- npm build +lerna exec --scope @ali/lowcode-rax-renderer -- npm build lerna exec --scope @ali/lowcode-ignitor -- npm start