diff --git a/packages/plugin-outline-pane/.gitignore b/packages/plugin-outline-pane/.gitignore new file mode 100644 index 000000000..2073a217c --- /dev/null +++ b/packages/plugin-outline-pane/.gitignore @@ -0,0 +1,106 @@ +# project custom +build +dist +packages/*/lib/ +packages/*/es/ +packages/*/dist/ +packages/*/output/ +package-lock.json +yarn.lock +deploy-space/packages +deploy-space/.env + + +# IDE +.vscode +.idea + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release +lib + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# mac config files +.DS_Store + +# codealike +codealike.json diff --git a/packages/plugin-outline-pane/CHANGELOG.md b/packages/plugin-outline-pane/CHANGELOG.md new file mode 100644 index 000000000..e87015e18 --- /dev/null +++ b/packages/plugin-outline-pane/CHANGELOG.md @@ -0,0 +1,665 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [1.0.21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.20...@ali/lowcode-plugin-outline-pane@1.0.21) (2020-11-16) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.19...@ali/lowcode-plugin-outline-pane@1.0.20) (2020-11-10) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.18...@ali/lowcode-plugin-outline-pane@1.0.19) (2020-11-10) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.17...@ali/lowcode-plugin-outline-pane@1.0.18) (2020-11-05) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.16...@ali/lowcode-plugin-outline-pane@1.0.17) (2020-11-05) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.15...@ali/lowcode-plugin-outline-pane@1.0.16) (2020-11-05) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.14...@ali/lowcode-plugin-outline-pane@1.0.15) (2020-11-04) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.13...@ali/lowcode-plugin-outline-pane@1.0.14) (2020-11-04) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.12...@ali/lowcode-plugin-outline-pane@1.0.13) (2020-11-02) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.11...@ali/lowcode-plugin-outline-pane@1.0.12) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.10...@ali/lowcode-plugin-outline-pane@1.0.11) (2020-10-19) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.9...@ali/lowcode-plugin-outline-pane@1.0.10) (2020-09-29) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.8...@ali/lowcode-plugin-outline-pane@1.0.9) (2020-09-28) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.8-0...@ali/lowcode-plugin-outline-pane@1.0.8) (2020-09-28) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.61...@ali/lowcode-plugin-outline-pane@1.0.8-0) (2020-09-09) + + +### Bug Fixes + +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 兼容modal模式 ([1092ee9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1092ee9)) + + + + + +## [1.0.7-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.6-0...@ali/lowcode-plugin-outline-pane@1.0.7-0) (2020-09-02) + + + +## [0.8.61](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.60...@ali/lowcode-plugin-outline-pane@0.8.61) (2020-09-08) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.60](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.59...@ali/lowcode-plugin-outline-pane@0.8.60) (2020-09-03) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.59](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.58...@ali/lowcode-plugin-outline-pane@0.8.59) (2020-09-03) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.58](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.57...@ali/lowcode-plugin-outline-pane@0.8.58) (2020-08-27) + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.6-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.58...@ali/lowcode-plugin-outline-pane@1.0.6-0) (2020-09-02) + + +### Bug Fixes + +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 兼容modal模式 ([1092ee9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1092ee9)) + + + + + +## [1.0.5-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.4-0...@ali/lowcode-plugin-outline-pane@1.0.5-0) (2020-08-20) + +## [0.8.54](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.53...@ali/lowcode-plugin-outline-pane@0.8.54) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.4-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.3-0...@ali/lowcode-plugin-outline-pane@1.0.4-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.3-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.2-0...@ali/lowcode-plugin-outline-pane@1.0.3-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.2-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@1.0.1-0...@ali/lowcode-plugin-outline-pane@1.0.2-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [1.0.1-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.53...@ali/lowcode-plugin-outline-pane@1.0.1-0) (2020-08-20) + + +### Bug Fixes + +* 兼容modal模式 ([1092ee9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1092ee9)) + + + + + +# [1.0.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.13.0...@ali/lowcode-plugin-outline-pane@1.0.0) (2020-08-17) + +## [0.8.53](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.52...@ali/lowcode-plugin-outline-pane@0.8.53) (2020-08-19) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.52](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.51...@ali/lowcode-plugin-outline-pane@0.8.52) (2020-08-19) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.51](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.50...@ali/lowcode-plugin-outline-pane@0.8.51) (2020-08-19) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.49...@ali/lowcode-plugin-outline-pane@0.8.50) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.49](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.48...@ali/lowcode-plugin-outline-pane@0.8.49) (2020-08-14) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +# [0.13.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.12.0...@ali/lowcode-plugin-outline-pane@0.13.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +# [0.12.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.10.0...@ali/lowcode-plugin-outline-pane@0.12.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +# [0.11.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.10.0...@ali/lowcode-plugin-outline-pane@0.11.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +# [0.10.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.9.0...@ali/lowcode-plugin-outline-pane@0.10.0) (2020-08-16) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +# [0.9.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.43...@ali/lowcode-plugin-outline-pane@0.9.0) (2020-08-14) + + +### Bug Fixes + +* 兼容modal模式 ([1092ee9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1092ee9)) + + + + + +## [0.8.43](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.42...@ali/lowcode-plugin-outline-pane@0.8.43) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.42](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.40...@ali/lowcode-plugin-outline-pane@0.8.42) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.41](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.40...@ali/lowcode-plugin-outline-pane@0.8.41) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.40](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.39...@ali/lowcode-plugin-outline-pane@0.8.40) (2020-07-29) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.39](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.38...@ali/lowcode-plugin-outline-pane@0.8.39) (2020-07-28) + + +### Bug Fixes + +* 🐛 getPrototype is undefined ([95b3409](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/95b3409)) + + + + + +## [0.8.38](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.37...@ali/lowcode-plugin-outline-pane@0.8.38) (2020-07-23) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.37](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.36...@ali/lowcode-plugin-outline-pane@0.8.37) (2020-07-22) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.36](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.35...@ali/lowcode-plugin-outline-pane@0.8.36) (2020-07-21) + + +### Bug Fixes + +* 修复导入的组件拖入画布报错 ([caf9915](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/caf9915)) + + + + + +## [0.8.35](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.34...@ali/lowcode-plugin-outline-pane@0.8.35) (2020-07-21) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.34](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.33...@ali/lowcode-plugin-outline-pane@0.8.34) (2020-07-21) + + +### Bug Fixes + +* modal node locate ([9a72dd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9a72dd7)) +* 大纲树节点显示隐藏埋点 ([e91ab1f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e91ab1f)) +* 没有 modal node 时不显示模态视图 ([555824c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/555824c)) + + +### Features + +* 大纲树展开折叠埋点 ([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)) + + + + + +## [0.8.33](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.32...@ali/lowcode-plugin-outline-pane@0.8.33) (2020-07-14) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.32](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.31...@ali/lowcode-plugin-outline-pane@0.8.32) (2020-07-13) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.31](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.30...@ali/lowcode-plugin-outline-pane@0.8.31) (2020-07-12) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.29...@ali/lowcode-plugin-outline-pane@0.8.30) (2020-07-12) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.29](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.27...@ali/lowcode-plugin-outline-pane@0.8.29) (2020-06-23) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.26...@ali/lowcode-plugin-outline-pane@0.8.27) (2020-06-23) + + +### Features + +* 大纲树埋点 ([fa24821](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fa24821)) + + + + + +## [0.8.26](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.25...@ali/lowcode-plugin-outline-pane@0.8.26) (2020-06-16) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.24...@ali/lowcode-plugin-outline-pane@0.8.25) (2020-06-15) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.23...@ali/lowcode-plugin-outline-pane@0.8.24) (2020-05-20) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.22...@ali/lowcode-plugin-outline-pane@0.8.23) (2020-05-19) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.22](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.21...@ali/lowcode-plugin-outline-pane@0.8.22) (2020-05-18) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.20...@ali/lowcode-plugin-outline-pane@0.8.21) (2020-05-16) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.19...@ali/lowcode-plugin-outline-pane@0.8.20) (2020-05-16) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.18...@ali/lowcode-plugin-outline-pane@0.8.19) (2020-05-16) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.17...@ali/lowcode-plugin-outline-pane@0.8.18) (2020-05-15) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.16...@ali/lowcode-plugin-outline-pane@0.8.17) (2020-05-15) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.15...@ali/lowcode-plugin-outline-pane@0.8.16) (2020-05-15) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.14...@ali/lowcode-plugin-outline-pane@0.8.15) (2020-05-13) + + +### Features + +* 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)) + + + + + +## [0.8.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.13...@ali/lowcode-plugin-outline-pane@0.8.14) (2020-05-08) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.12...@ali/lowcode-plugin-outline-pane@0.8.13) (2020-05-07) + + +### Bug Fixes + +* intl ([8a061ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8a061ab)) + + + + + +## [0.8.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.11...@ali/lowcode-plugin-outline-pane@0.8.12) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.10...@ali/lowcode-plugin-outline-pane@0.8.11) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.9...@ali/lowcode-plugin-outline-pane@0.8.10) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.8...@ali/lowcode-plugin-outline-pane@0.8.9) (2020-04-16) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.7...@ali/lowcode-plugin-outline-pane@0.8.8) (2020-04-15) + + +### Bug Fixes + +* plugin-designer ([2dfbcd4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2dfbcd4)) + + + + + +## [0.8.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.6...@ali/lowcode-plugin-outline-pane@0.8.7) (2020-03-31) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.5...@ali/lowcode-plugin-outline-pane@0.8.6) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.4...@ali/lowcode-plugin-outline-pane@0.8.5) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-outline-pane@0.8.3...@ali/lowcode-plugin-outline-pane@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-plugin-outline-pane + + +## 0.8.3 (2020-03-30) + + +### Features + +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7)) + + + + + +## 0.8.2 (2020-03-30) + + +### Features + +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7)) diff --git a/packages/plugin-outline-pane/README.md b/packages/plugin-outline-pane/README.md new file mode 100644 index 000000000..6c8d94003 --- /dev/null +++ b/packages/plugin-outline-pane/README.md @@ -0,0 +1 @@ +大纲面板 diff --git a/packages/plugin-outline-pane/build.json b/packages/plugin-outline-pane/build.json new file mode 100644 index 000000000..e791d5b6b --- /dev/null +++ b/packages/plugin-outline-pane/build.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + "build-plugin-component", + "build-plugin-fusion", + ["build-plugin-moment-locales", { + "locales": ["zh-cn"] + }] + ] +} diff --git a/packages/plugin-outline-pane/package.json b/packages/plugin-outline-pane/package.json new file mode 100644 index 000000000..e9f441738 --- /dev/null +++ b/packages/plugin-outline-pane/package.json @@ -0,0 +1,50 @@ +{ + "name": "@ali/lowcode-plugin-outline-pane", + "version": "1.0.21", + "description": "Outline pane 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/lowcode-designer": "^1.0.22", + "@ali/lowcode-editor-core": "^1.0.22", + "@ali/lowcode-types": "^1.0.20", + "@ali/lowcode-utils": "^1.0.21", + "@alifd/next": "^1.19.16", + "classnames": "^2.2.6", + "react": "^16", + "react-dom": "^16.7.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.1", + "build-plugin-moment-locales": "^0.1.0" + }, + "ava": { + "compileEnhancements": false, + "snapshotDir": "test/fixtures/__snapshots__", + "extensions": [ + "ts" + ], + "require": [ + "ts-node/register" + ] + }, + "license": "MIT", + "publishConfig": { + "registry": "https://registry.npm.alibaba-inc.com" + } +} diff --git a/packages/plugin-outline-pane/src/README.md b/packages/plugin-outline-pane/src/README.md new file mode 100644 index 000000000..0171bb10e --- /dev/null +++ b/packages/plugin-outline-pane/src/README.md @@ -0,0 +1 @@ +大纲树 diff --git a/packages/plugin-outline-pane/src/helper/dwell-timer.ts b/packages/plugin-outline-pane/src/helper/dwell-timer.ts new file mode 100644 index 000000000..601ff6403 --- /dev/null +++ b/packages/plugin-outline-pane/src/helper/dwell-timer.ts @@ -0,0 +1,55 @@ +import { ParentalNode, DropLocation, isLocationChildrenDetail, LocateEvent } from '@ali/lowcode-designer'; + +/** + * 停留检查计时器 + */ +export default class DwellTimer { + private timer: number | undefined; + + private previous?: ParentalNode; + + private event?: LocateEvent; + + private decide: (node: ParentalNode, event: LocateEvent) => void; + + private timeout = 500; + + constructor(decide: (node: ParentalNode, event: LocateEvent) => void, timeout = 500) { + this.decide = decide; + this.timeout = timeout; + } + + focus(node: ParentalNode, event: LocateEvent) { + this.event = event; + if (this.previous === node) { + return; + } + this.reset(); + this.previous = node; + this.timer = setTimeout(() => { + this.previous && this.decide(this.previous, this.event!); + this.reset(); + }, this.timeout) as any; + } + + tryFocus(loc?: DropLocation | null) { + if (!loc || !isLocationChildrenDetail(loc.detail)) { + this.reset(); + return; + } + if (loc.detail.focus?.type === 'node') { + this.focus(loc.detail.focus.node, loc.event); + } else { + this.reset(); + } + } + + reset() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = undefined; + } + + this.previous = undefined; + } +} diff --git a/packages/plugin-outline-pane/src/helper/indent-track.ts b/packages/plugin-outline-pane/src/helper/indent-track.ts new file mode 100644 index 000000000..659bd272a --- /dev/null +++ b/packages/plugin-outline-pane/src/helper/indent-track.ts @@ -0,0 +1,54 @@ +import { DropLocation, ParentalNode, isLocationChildrenDetail } from '@ali/lowcode-designer'; + +const IndentSensitive = 15; +export class IndentTrack { + private indentStart: number | null = null; + + reset() { + this.indentStart = null; + } + + getIndentParent(lastLoc: DropLocation, loc: DropLocation): [ParentalNode, number] | null { + if ( + lastLoc.target !== loc.target || + !isLocationChildrenDetail(lastLoc.detail) || + !isLocationChildrenDetail(loc.detail) || + lastLoc.source !== loc.source || + lastLoc.detail.index !== loc.detail.index || + loc.detail.index == null + ) { + this.indentStart = null; + return null; + } + if (this.indentStart == null) { + this.indentStart = lastLoc.event.globalX; + } + const delta = loc.event.globalX - this.indentStart; + const indent = Math.floor(Math.abs(delta) / IndentSensitive); + if (indent < 1) { + return null; + } + this.indentStart = loc.event.globalX; + const direction = delta < 0 ? 'left' : 'right'; + + let parent = loc.target; + const { index } = loc.detail; + + if (direction === 'left') { + if (!parent.parent || index < parent.children.size || parent.isSlot()) { + return null; + } + return [(parent as any).parent, parent.index + 1]; + } else { + if (index === 0) { + return null; + } + parent = parent.children.get(index - 1) as any; + if (parent && parent.isContainer()) { + return [parent, parent.children.size]; + } + } + + return null; + } +} diff --git a/packages/plugin-outline-pane/src/icons/arrow-right.tsx b/packages/plugin-outline-pane/src/icons/arrow-right.tsx new file mode 100644 index 000000000..866b34cec --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/arrow-right.tsx @@ -0,0 +1,11 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconArrowRight(props: IconProps) { + return ( + + + + ); +} + +IconArrowRight.displayName = 'IconArrowRight'; diff --git a/packages/plugin-outline-pane/src/icons/cond.tsx b/packages/plugin-outline-pane/src/icons/cond.tsx new file mode 100644 index 000000000..915efebec --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/cond.tsx @@ -0,0 +1,11 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconCond(props: IconProps) { + return ( + + + + ); +} + +IconCond.displayName = 'IconCond'; diff --git a/packages/plugin-outline-pane/src/icons/eye-close.tsx b/packages/plugin-outline-pane/src/icons/eye-close.tsx new file mode 100644 index 000000000..4502d1f93 --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/eye-close.tsx @@ -0,0 +1,12 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconEyeClose(props: IconProps) { + return ( + + + + + ); +} + +IconEyeClose.displayName = 'IconEyeClose'; diff --git a/packages/plugin-outline-pane/src/icons/eye.tsx b/packages/plugin-outline-pane/src/icons/eye.tsx new file mode 100644 index 000000000..5490e11a5 --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/eye.tsx @@ -0,0 +1,12 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconEye(props: IconProps) { + return ( + + + + + ); +} + +IconEye.displayName = 'IconEye'; diff --git a/packages/plugin-outline-pane/src/icons/lock.tsx b/packages/plugin-outline-pane/src/icons/lock.tsx new file mode 100644 index 000000000..7a12db4a7 --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/lock.tsx @@ -0,0 +1,11 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconLock(props: IconProps) { + return ( + + + + ); +} + +IconLock.displayName = 'IconLock'; diff --git a/packages/plugin-outline-pane/src/icons/loop.tsx b/packages/plugin-outline-pane/src/icons/loop.tsx new file mode 100644 index 000000000..ba86e1c86 --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/loop.tsx @@ -0,0 +1,11 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconLoop(props: IconProps) { + return ( + + + + ); +} + +IconLoop.displayName = 'IconLoop'; diff --git a/packages/plugin-outline-pane/src/icons/outline.tsx b/packages/plugin-outline-pane/src/icons/outline.tsx new file mode 100644 index 000000000..6e5a0e517 --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/outline.tsx @@ -0,0 +1,12 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconOutline(props: IconProps) { + return ( + + + + ); +} + +IconOutline.displayName = 'IconOutline'; + diff --git a/packages/plugin-outline-pane/src/icons/radio-active.tsx b/packages/plugin-outline-pane/src/icons/radio-active.tsx new file mode 100644 index 000000000..15fe2ffea --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/radio-active.tsx @@ -0,0 +1,12 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconRadioActive(props: IconProps) { + return ( + + + + ); +} + +IconRadioActive.displayName = 'IconRadioActive'; + diff --git a/packages/plugin-outline-pane/src/icons/radio.tsx b/packages/plugin-outline-pane/src/icons/radio.tsx new file mode 100644 index 000000000..f724a94c5 --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/radio.tsx @@ -0,0 +1,12 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconRadio(props: IconProps) { + return ( + + + + ); +} + +IconRadio.displayName = 'IconRadio'; + diff --git a/packages/plugin-outline-pane/src/icons/unlock.tsx b/packages/plugin-outline-pane/src/icons/unlock.tsx new file mode 100644 index 000000000..5695db95d --- /dev/null +++ b/packages/plugin-outline-pane/src/icons/unlock.tsx @@ -0,0 +1,12 @@ +import { SVGIcon, IconProps } from '@ali/lowcode-utils'; + +export function IconUnlock(props: IconProps) { + return ( + + + + + ); +} + +IconUnlock.displayName = 'IconUnlock'; diff --git a/packages/plugin-outline-pane/src/index.ts b/packages/plugin-outline-pane/src/index.ts new file mode 100644 index 000000000..32161abb5 --- /dev/null +++ b/packages/plugin-outline-pane/src/index.ts @@ -0,0 +1,16 @@ +import { OutlinePane } from './views/pane'; +import { OutlineBackupPane } from './views/backup-pane'; +import { IconOutline } from './icons/outline'; +import { intlNode } from './locale'; +import { getTreeMaster } from './tree-master'; + +export default { + name: 'outline-pane', + props: { + icon: IconOutline, + description: intlNode('Outline Tree'), + }, + content: OutlinePane, +}; + +export { OutlinePane, OutlineBackupPane, getTreeMaster }; diff --git a/packages/plugin-outline-pane/src/locale/en-US.json b/packages/plugin-outline-pane/src/locale/en-US.json new file mode 100644 index 000000000..9d04defc0 --- /dev/null +++ b/packages/plugin-outline-pane/src/locale/en-US.json @@ -0,0 +1,14 @@ +{ + "Initializing": "Initializing", + "Hide": "Hide", + "Show": "Show", + "Lock": "Lock", + "Unlock": "Unlock", + "Expand": "Expand", + "Collapse": "Collapse", + "Conditional": "Condition", + "Loop": "Loop", + "Slots": "Slots", + "Slot for {prop}": "Slot for {prop}", + "Outline Tree": "Outline Tree" +} diff --git a/packages/plugin-outline-pane/src/locale/index.ts b/packages/plugin-outline-pane/src/locale/index.ts new file mode 100644 index 000000000..26507f0ef --- /dev/null +++ b/packages/plugin-outline-pane/src/locale/index.ts @@ -0,0 +1,10 @@ +import { createIntl } from '@ali/lowcode-editor-core'; +import en_US from './en-US.json'; +import zh_CN from './zh-CN.json'; + +const { intl, intlNode, getLocale, setLocale } = createIntl({ + 'en-US': en_US, + 'zh-CN': zh_CN, +}); + +export { intl, intlNode, getLocale, setLocale }; diff --git a/packages/plugin-outline-pane/src/locale/zh-CN.json b/packages/plugin-outline-pane/src/locale/zh-CN.json new file mode 100644 index 000000000..08138b3b4 --- /dev/null +++ b/packages/plugin-outline-pane/src/locale/zh-CN.json @@ -0,0 +1,14 @@ +{ + "Initializing": "正在初始化", + "Hide": "隐藏", + "Show": "显示", + "Lock": "锁定", + "Unlock": "解锁", + "Expand": "展开", + "Collapse": "收起", + "Conditional": "条件式", + "Loop": "循环", + "Slots": "插槽", + "Slot for {prop}": "属性 {prop} 的插槽", + "Outline Tree": "大纲树" +} diff --git a/packages/plugin-outline-pane/src/main.ts b/packages/plugin-outline-pane/src/main.ts new file mode 100644 index 000000000..ae0f7bd32 --- /dev/null +++ b/packages/plugin-outline-pane/src/main.ts @@ -0,0 +1,667 @@ +import { computed, obx } from '@ali/lowcode-editor-core'; +import { + Designer, + ISensor, + LocateEvent, + isDragNodeObject, + isDragAnyObject, + DragObject, + Scroller, + ScrollTarget, + IScrollable, + DropLocation, + isLocationChildrenDetail, + LocationChildrenDetail, + LocationDetailType, + ParentalNode, + contains, + Node, +} from '@ali/lowcode-designer'; +import TreeNode from './tree-node'; +import { IndentTrack } from './helper/indent-track'; +import DwellTimer from './helper/dwell-timer'; +import { uniqueId } from '@ali/lowcode-utils'; +import { Backup } from './views/backup-pane'; +import { IEditor } from '@ali/lowcode-types'; +import { ITreeBoard, TreeMaster, getTreeMaster } from './tree-master'; + +export class OutlineMain implements ISensor, ITreeBoard, IScrollable { + private _designer?: Designer; + + @obx.ref private _master?: TreeMaster; + + get master() { + return this._master; + } + + @computed get currentTree() { + return this._master?.currentTree; + } + + readonly id = uniqueId('outline'); + + @obx.ref _visible = false; + + get visible() { + return this._visible; + } + + readonly editor: IEditor; + + readonly at: string | symbol; + + constructor(editor: IEditor, at: string | symbol) { + this.editor = editor; + this.at = at; + let inited = false; + const setup = async () => { + if (inited) { + return false; + } + inited = true; + const designer = await editor.onceGot(Designer); + this.setupDesigner(designer); + }; + + if (at === Backup) { + setup(); + } else { + editor.on('skeleton.panel.show', (key: string) => { + if (key === at) { + setup(); + this._visible = true; + } + }); + editor.on('skeleton.panel.hide', (key: string) => { + if (key === at) { + this._visible = false; + } + }); + } + } + + /** + * @see ISensor + */ + fixEvent(e: LocateEvent): LocateEvent { + if (e.fixed) { + return e; + } + + const notMyEvent = e.originalEvent.view?.document !== document; + + if (!e.target || notMyEvent) { + e.target = document.elementFromPoint(e.canvasX!, e.canvasY!); + } + + // documentModel : 目标文档 + e.documentModel = this._designer?.currentDocument; + + // 事件已订正 + e.fixed = true; + return e; + } + + private indentTrack = new IndentTrack(); + + private dwell = new DwellTimer((target, event) => { + const { document } = target; + const { designer } = document; + let index: any; + let focus: any; + let valid = true; + if (target.hasSlots()) { + index = null; + focus = { type: 'slots' }; + } else { + index = 0; + valid = document.checkNesting(target, event.dragObject as any); + } + designer.createLocation({ + target, + source: this.id, + event, + detail: { + type: LocationDetailType.Children, + index, + focus, + valid, + }, + }); + }); + + /** + * @see ISensor + */ + locate(e: LocateEvent): DropLocation | undefined | null { + this.sensing = true; + this.scroller?.scrolling(e); + const { globalY, dragObject } = e; + const { nodes } = dragObject; + + const tree = this._master?.currentTree; + if (!tree || !this._shell) { + return null; + } + + const operationalNodes = nodes?.filter((node: any) => { + const onMoveHook = node.componentMeta?.getMetadata()?.experimental?.callbacks?.onMoveHook; + const canMove = onMoveHook && typeof onMoveHook === 'function' ? onMoveHook() : true; + + return canMove; + }); + + if (!operationalNodes || operationalNodes.length === 0) { + return; + } + + const { document } = tree; + const { designer } = document; + const pos = getPosFromEvent(e, this._shell); + const irect = this.getInsertionRect(); + const originLoc = document.dropLocation; + + const componentMeta = e.dragObject.nodes ? e.dragObject.nodes[0].componentMeta : null; + if (e.dragObject.type === 'node' && componentMeta && componentMeta.isModal) { + return designer.createLocation({ + target: document.rootNode, + detail: { + type: LocationDetailType.Children, + index: 0, + valid: true, + }, + source: this.id, + event: e, + }); + } + + if (originLoc && ((pos && pos === 'unchanged') || (irect && globalY >= irect.top && globalY <= irect.bottom))) { + const loc = originLoc.clone(e); + const indented = this.indentTrack.getIndentParent(originLoc, loc); + if (indented) { + const [parent, index] = indented; + if (checkRecursion(parent, dragObject)) { + if (tree.getTreeNode(parent).expanded) { + this.dwell.reset(); + return designer.createLocation({ + target: parent, + source: this.id, + event: e, + detail: { + type: LocationDetailType.Children, + index, + valid: document.checkNesting(parent, e.dragObject as any), + }, + }); + } + + (originLoc.detail as LocationChildrenDetail).focus = { + type: 'node', + node: parent, + }; + // focus try expand go on + this.dwell.focus(parent, e); + } else { + this.dwell.reset(); + } + // FIXME: recreate new location + } else if ((originLoc.detail as LocationChildrenDetail).near) { + (originLoc.detail as LocationChildrenDetail).near = undefined; + this.dwell.reset(); + } + return; + } + + this.indentTrack.reset(); + + if (pos && pos !== 'unchanged') { + let treeNode = tree.getTreeNodeById(pos.nodeId); + if (treeNode) { + let { focusSlots } = pos; + let { node } = treeNode; + if (isDragNodeObject(dragObject)) { + const newNodes = operationalNodes; + let i = newNodes.length; + let p: any = node; + while (i-- > 0) { + if (contains(newNodes[i], p)) { + p = newNodes[i].parent; + } + } + if (p !== node) { + node = p || document.rootNode; + treeNode = tree.getTreeNode(node); + focusSlots = false; + } + } + + if (focusSlots) { + this.dwell.reset(); + return designer.createLocation({ + target: node as ParentalNode, + source: this.id, + event: e, + detail: { + type: LocationDetailType.Children, + index: null, + valid: false, + focus: { type: 'slots' }, + }, + }); + } + + if (!treeNode.isRoot()) { + const loc = this.getNear(treeNode, e); + this.dwell.tryFocus(loc); + return loc; + } + } + } + + const loc = this.drillLocate(tree.root, e); + this.dwell.tryFocus(loc); + return loc; + } + + private getNear(treeNode: TreeNode, e: LocateEvent, index?: number, rect?: DOMRect) { + const { document } = treeNode.tree; + const { designer } = document; + const { globalY, dragObject } = e; + // TODO: check dragObject is anyData + const { node, expanded } = treeNode; + if (!rect) { + rect = this.getTreeNodeRect(treeNode); + if (!rect) { + return null; + } + } + if (index == null) { + index = node.index; + } + + if (node.isSlot()) { + // 是个插槽根节点 + if (!treeNode.isContainer() && !treeNode.hasSlots()) { + return designer.createLocation({ + target: node.parent!, + source: this.id, + event: e, + detail: { + type: LocationDetailType.Children, + index: null, + near: { node, pos: 'replace' }, + valid: true, // TODO: future validation the slot limit + }, + }); + } + const loc1 = this.drillLocate(treeNode, e); + if (loc1) { + return loc1; + } + + return designer.createLocation({ + target: node.parent!, + source: this.id, + event: e, + detail: { + type: LocationDetailType.Children, + index: null, + valid: false, + focus: { type: 'slots' }, + }, + }); + } + + let focusNode: Node | undefined; + // focus + if (!expanded && (treeNode.isContainer() || treeNode.hasSlots())) { + focusNode = node; + } + + // before + const titleRect = this.getTreeTitleRect(treeNode) || rect; + if (globalY < titleRect.top + titleRect.height / 2) { + return designer.createLocation({ + target: node.parent!, + source: this.id, + event: e, + detail: { + type: LocationDetailType.Children, + index, + valid: document.checkNesting(node.parent!, dragObject as any), + near: { node, pos: 'before' }, + focus: checkRecursion(focusNode, dragObject) ? { type: 'node', node: focusNode } : undefined, + }, + }); + } + + if (globalY > titleRect.bottom) { + focusNode = undefined; + } + + if (expanded) { + // drill + const loc = this.drillLocate(treeNode, e); + if (loc) { + return loc; + } + } + + // after + return designer.createLocation({ + target: node.parent!, + source: this.id, + event: e, + detail: { + type: LocationDetailType.Children, + index: index + 1, + valid: document.checkNesting(node.parent!, dragObject as any), + near: { node, pos: 'after' }, + focus: checkRecursion(focusNode, dragObject) ? { type: 'node', node: focusNode } : undefined, + }, + }); + } + + private drillLocate(treeNode: TreeNode, e: LocateEvent): DropLocation | null { + const { document } = treeNode.tree; + const { designer } = document; + const { dragObject, globalY } = e; + + if (!checkRecursion(treeNode.node, dragObject)) { + return null; + } + + if (isDragAnyObject(dragObject)) { + // TODO: future + return null; + } + + const container = treeNode.node as ParentalNode; + const detail: LocationChildrenDetail = { + type: LocationDetailType.Children, + }; + const locationData: any = { + target: container, + detail, + source: this.id, + event: e, + }; + const isSlotContainer = treeNode.hasSlots(); + const isContainer = treeNode.isContainer(); + + if (container.isSlot() && !treeNode.expanded) { + // 未展开,直接定位到内部第一个节点 + if (isSlotContainer) { + detail.index = null; + detail.focus = { type: 'slots' }; + detail.valid = false; + } else { + detail.index = 0; + detail.valid = document.checkNesting(container, dragObject); + } + } + + let items: TreeNode[] | null = null; + let slotsRect: DOMRect | undefined; + let focusSlots = false; + // isSlotContainer + if (isSlotContainer) { + slotsRect = this.getTreeSlotsRect(treeNode); + if (slotsRect) { + if (globalY <= slotsRect.bottom) { + focusSlots = true; + items = treeNode.slots; + } else if (!isContainer) { + // 不在 slots 范围,又不是 container 的情况,高亮 slots 区 + detail.index = null; + detail.focus = { type: 'slots' }; + detail.valid = false; + return designer.createLocation(locationData); + } + } + } + + if (!items && isContainer) { + items = treeNode.children; + } + + if (!items) { + return null; + } + + const l = items.length; + let index = 0; + let before = l < 1; + let current: TreeNode | undefined; + let currentIndex = index; + for (; index < l; index++) { + current = items[index]; + currentIndex = index; + const rect = this.getTreeNodeRect(current); + if (!rect) { + continue; + } + + // rect + if (globalY < rect.top) { + before = true; + break; + } + + if (globalY > rect.bottom) { + continue; + } + + const loc = this.getNear(current, e, index, rect); + if (loc) { + return loc; + } + } + + if (focusSlots) { + detail.focus = { type: 'slots' }; + detail.valid = false; + detail.index = null; + } else { + if (current) { + detail.index = before ? currentIndex : currentIndex + 1; + detail.near = { node: current.node, pos: before ? 'before' : 'after' }; + } else { + detail.index = l; + } + detail.valid = document.checkNesting(container, dragObject); + } + + return designer.createLocation(locationData); + } + + /** + * @see ISensor + */ + isEnter(e: LocateEvent): boolean { + if (!this._shell) { + return false; + } + const rect = this._shell.getBoundingClientRect(); + return e.globalY >= rect.top && e.globalY <= rect.bottom && e.globalX >= rect.left && e.globalX <= rect.right; + } + + private tryScrollAgain: number | null = null; + + /** + * @see IScrollBoard + */ + scrollToNode(treeNode: TreeNode, detail?: any, tryTimes = 0) { + if (tryTimes < 1 && this.tryScrollAgain) { + (window as any).cancelIdleCallback(this.tryScrollAgain); + this.tryScrollAgain = null; + } + if (this.sensing || !this.bounds || !this.scroller || !this.scrollTarget) { + // is a active sensor + return; + } + + let rect: ClientRect | undefined; + if (detail && isLocationChildrenDetail(detail)) { + rect = this.getInsertionRect(); + } else { + rect = this.getTreeNodeRect(treeNode); + } + + if (!rect) { + if (tryTimes < 3) { + this.tryScrollAgain = (window as any).requestIdleCallback(() => this.scrollToNode(treeNode, detail, tryTimes + 1)); + } + return; + } + const { scrollHeight, top: scrollTop } = this.scrollTarget; + const { height, top, bottom } = this.bounds; + if (rect.top < top || rect.bottom > bottom) { + const opt: any = {}; + opt.top = Math.min(rect.top + rect.height / 2 + scrollTop - top - height / 2, scrollHeight - height); + if (rect.height >= height) { + opt.top = Math.min(scrollTop + rect.top - top, opt.top); + } + this.scroller.scrollTo(opt); + } + // make tail scroll be sure + if (tryTimes < 4) { + this.tryScrollAgain = (window as any).requestIdleCallback(() => this.scrollToNode(treeNode, detail, 4)); + } + } + + private sensing = false; + + /** + * @see ISensor + */ + deactiveSensor() { + this.sensing = false; + this.scroller?.cancel(); + this.dwell.reset(); + this.indentTrack.reset(); + } + + /** + * @see IScrollable + */ + get bounds(): DOMRect | null { + if (!this._shell) { + return null; + } + return this._shell.getBoundingClientRect(); + } + + private _scrollTarget?: ScrollTarget; + + /** + * @see IScrollable + */ + get scrollTarget() { + return this._scrollTarget; + } + + private scroller?: Scroller; + + private setupDesigner(designer: Designer) { + this._designer = designer; + this._master = getTreeMaster(designer); + this._master.addBoard(this); + designer.dragon.addSensor(this); + this.scroller = designer.createScroller(this); + } + + purge() { + this._designer?.dragon.removeSensor(this); + this._master?.removeBoard(this); + // todo purge treeMaster if needed + } + + private _sensorAvailable = false; + + /** + * @see ISensor + */ + get sensorAvailable() { + return this._sensorAvailable; + } + + private _shell: HTMLDivElement | null = null; + + mount(shell: HTMLDivElement | null) { + if (this._shell === shell) { + return; + } + this._shell = shell; + if (shell) { + this._scrollTarget = new ScrollTarget(shell); + this._sensorAvailable = true; + } else { + this._scrollTarget = undefined; + this._sensorAvailable = false; + } + } + + private getInsertionRect(): DOMRect | undefined { + if (!this._shell) { + return undefined; + } + return this._shell.querySelector('.insertion')?.getBoundingClientRect(); + } + + private getTreeNodeRect(treeNode: TreeNode): DOMRect | undefined { + if (!this._shell) { + return undefined; + } + return this._shell.querySelector(`.tree-node[data-id="${treeNode.id}"]`)?.getBoundingClientRect(); + } + + private getTreeTitleRect(treeNode: TreeNode): DOMRect | undefined { + if (!this._shell) { + return undefined; + } + return this._shell.querySelector(`.tree-node-title[data-id="${treeNode.id}"]`)?.getBoundingClientRect(); + } + + private getTreeSlotsRect(treeNode: TreeNode): DOMRect | undefined { + if (!this._shell) { + return undefined; + } + return this._shell.querySelector(`.tree-node-slots[data-id="${treeNode.id}"]`)?.getBoundingClientRect(); + } +} + +function checkRecursion(parent: Node | undefined | null, dragObject: DragObject): parent is ParentalNode { + if (!parent) { + return false; + } + if (isDragNodeObject(dragObject)) { + const { nodes } = dragObject; + if (nodes.some((node) => node.contains(parent))) { + return false; + } + } + return true; +} + +function getPosFromEvent( + { target }: LocateEvent, + stop: Element, +): null | 'unchanged' | { nodeId: string; focusSlots: boolean } { + if (!target || !stop.contains(target)) { + return null; + } + if (target.matches('.insertion')) { + return 'unchanged'; + } + target = target.closest('[data-id]'); + if (!target || !stop.contains(target)) { + return null; + } + + const nodeId = (target as HTMLDivElement).dataset.id!; + return { + focusSlots: target.matches('.tree-node-slots'), + nodeId, + }; +} diff --git a/packages/plugin-outline-pane/src/tree-master.ts b/packages/plugin-outline-pane/src/tree-master.ts new file mode 100644 index 000000000..4e4049d0f --- /dev/null +++ b/packages/plugin-outline-pane/src/tree-master.ts @@ -0,0 +1,122 @@ +import { computed, obx } from '@ali/lowcode-editor-core'; +import { Designer, isLocationChildrenDetail } from '@ali/lowcode-designer'; +import TreeNode from './tree-node'; +import { Tree } from './tree'; +import { Backup } from './views/backup-pane'; + +export interface ITreeBoard { + readonly visible: boolean; + readonly at: string | symbol; + scrollToNode(treeNode: TreeNode, detail?: any): void; +} + +export class TreeMaster { + readonly designer: Designer; + + constructor(designer: Designer) { + this.designer = designer; + let startTime: any; + designer.dragon.onDragstart(() => { + startTime = Date.now() / 1000; + // needs? + this.toVision(); + }); + designer.activeTracker.onChange(({ node, detail }) => { + const tree = this.currentTree; + if (!tree || node.document !== tree.document) { + return; + } + + const treeNode = tree.getTreeNode(node); + if (detail && isLocationChildrenDetail(detail)) { + treeNode.expand(true); + } else { + treeNode.expandParents(); + } + + this.boards.forEach((board) => { + board.scrollToNode(treeNode, detail); + }); + }); + designer.dragon.onDragend(() => { + const endTime: any = Date.now() / 1000; + const editor = designer?.editor; + const nodes = designer.currentSelection?.getNodes(); + editor?.emit('outlinePane.drag', { + selected: nodes + ?.map((n) => { + if (!n) { + return; + } + const npm = n?.componentMeta?.npm; + return ( + [npm?.package, npm?.componentName].filter((item) => !!item).join('-') || n?.componentMeta?.componentName + ); + }) + .join('&'), + time: (endTime - startTime).toFixed(2), + }); + }); + designer.editor.on('designer.document.remove', ({ id }) => { + this.treeMap.delete(id); + }); + } + + private toVision() { + const tree = this.currentTree; + if (tree) { + tree.document.selection.getTopNodes().forEach((node) => { + tree.getTreeNode(node).setExpanded(false); + }); + } + } + + @obx.val private boards = new Set(); + + addBoard(board: ITreeBoard) { + this.boards.add(board); + } + + removeBoard(board: ITreeBoard) { + this.boards.delete(board); + } + + @computed hasVisibleTreeBoard() { + for (const item of this.boards) { + if (item.visible && item.at !== Backup) { + return true; + } + } + return false; + } + + purge() { + // todo others purge + } + + private treeMap = new Map(); + + @computed get currentTree(): Tree | null { + const doc = this.designer?.currentDocument; + if (doc) { + const { id } = doc; + if (this.treeMap.has(id)) { + return this.treeMap.get(id)!; + } + const tree = new Tree(doc); + this.treeMap.set(id, tree); + return tree; + } + return null; + } +} + +const mastersMap = new Map(); +export function getTreeMaster(designer: Designer): TreeMaster { + let master = mastersMap.get(designer); + if (!master) { + master = new TreeMaster(designer); + mastersMap.set(designer, master); + } + return master; +} diff --git a/packages/plugin-outline-pane/src/tree-node.ts b/packages/plugin-outline-pane/src/tree-node.ts new file mode 100644 index 000000000..e021d6b65 --- /dev/null +++ b/packages/plugin-outline-pane/src/tree-node.ts @@ -0,0 +1,235 @@ +import { TitleContent, isI18nData } from '@ali/lowcode-types'; +import { computed, obx, intl } from '@ali/lowcode-editor-core'; +import { Node, DocumentModel, isLocationChildrenDetail, LocationChildrenDetail, Designer } from '@ali/lowcode-designer'; +import { Tree } from './tree'; + +export default class TreeNode { + get id(): string { + return this.node.id; + } + + /** + * 是否可以展开 + */ + @computed get expandable(): boolean { + return this.hasChildren() || this.hasSlots() || this.dropDetail?.index != null; + } + + /** + * 插入"线"位置信息 + */ + @computed get dropDetail(): LocationChildrenDetail | undefined | null { + const loc = this.node.document.dropLocation; + return loc && this.isResponseDropping() && isLocationChildrenDetail(loc.detail) ? loc.detail : null; + } + + @computed get depth(): number { + return this.node.zLevel; + } + + isRoot() { + return this.tree.root === this; + } + + /** + * 是否是响应投放区 + */ + @computed isResponseDropping(): boolean { + const loc = this.node.document.dropLocation; + if (!loc) { + return false; + } + return loc.target === this.node; + } + + @computed isFocusingNode(): boolean { + const loc = this.node.document.dropLocation; + if (!loc) { + return false; + } + return ( + isLocationChildrenDetail(loc.detail) && loc.detail.focus?.type === 'node' && loc.detail.focus.node === this.node + ); + } + + /** + * 默认为折叠状态 + * 在初始化根节点时,设置为展开状态 + */ + @obx.ref private _expanded = false; + + get expanded(): boolean { + return this.isRoot() || (this.expandable && this._expanded); + } + + setExpanded(value: boolean) { + this._expanded = value; + } + + @computed get detecting() { + return this.designer.detecting.current === this.node; + } + + @computed get hidden(): boolean { + const cv = this.node.isConditionalVisible(); + if (cv == null) { + return !this.node.getVisible(); + } + return !cv; + } + + setHidden(flag: boolean) { + if (this.node.conditionGroup) { + return; + } + this.node.setVisible(!flag); + } + + @computed get locked(): boolean { + return this.node.getExtraProp('locked', false)?.getValue() === true; + } + + setLocked(flag: boolean) { + if (flag) { + this.node.getExtraProp('locked', true)?.setValue(true); + } else { + this.node.getExtraProp('locked', false)?.remove(); + } + } + + @computed get selected(): boolean { + // TODO: check is dragging + const { selection } = this.document; + return selection.has(this.node.id); + } + + @computed get title(): TitleContent { + return this.node.title; + } + + @computed get titleLabel() { + let { title } = this; + if (!title) { + return ''; + } + if ((title as any).label) { + title = (title as any).label; + } + if (typeof title === 'string') { + return title; + } + if (isI18nData(title)) { + return intl(title) as string; + } + return this.node.componentName; + } + + setTitleLabel(label: string) { + const origLabel = this.titleLabel; + if (label === origLabel) { + return; + } + if (label === '') { + this.node.getExtraProp('title', false)?.remove(); + } else { + this.node.getExtraProp('title', true)?.setValue(label); + } + } + + get icon() { + return this.node.componentMeta.icon; + } + + @computed get parent() { + const { parent } = this.node; + if (parent) { + return this.tree.getTreeNode(parent); + } + return null; + } + + @computed get slots(): TreeNode[] { + // todo: shallowEqual + return this.node.slots.map((node) => this.tree.getTreeNode(node)); + } + + @computed get children(): TreeNode[] | null { + return this.node.children?.map((node) => this.tree.getTreeNode(node)) || null; + } + + /** + * 是否是容器,允许子节点拖入 + */ + isContainer(): boolean { + return this.node.isContainer(); + } + + /** + * 判断是否有"插槽" + */ + hasSlots(): boolean { + return this.node.hasSlots(); + } + + hasChildren(): boolean { + return !!(this.isContainer() && this.node.children?.notEmpty()); + } + + select(isMulti: boolean) { + const { node } = this; + + const { selection } = node.document; + if (isMulti) { + selection.add(node.id); + } else { + selection.select(node.id); + } + } + + /** + * 展开节点,支持依次展开父节点 + */ + expand(tryExpandParents = false) { + // 这边不能直接使用 expanded,需要额外判断是否可以展开 + // 如果只使用 expanded,会漏掉不可以展开的情况,即在不可以展开的情况下,会触发展开 + if (this.expandable && !this._expanded) { + this.setExpanded(true); + } + if (tryExpandParents) { + this.expandParents(); + } + } + + expandParents() { + let p = this.node.parent; + while (p) { + this.tree.getTreeNode(p).setExpanded(true); + p = p.parent; + } + } + + readonly designer: Designer; + + readonly document: DocumentModel; + + @obx.ref private _node: Node; + + get node() { + return this._node; + } + + readonly tree: Tree; + + constructor(tree: Tree, node: Node) { + this.tree = tree; + this.document = node.document; + this.designer = this.document.designer; + this._node = node; + } + + setNode(node: Node) { + if (this._node !== node) { + this._node = node; + } + } +} diff --git a/packages/plugin-outline-pane/src/tree.ts b/packages/plugin-outline-pane/src/tree.ts new file mode 100644 index 000000000..a34bdfd21 --- /dev/null +++ b/packages/plugin-outline-pane/src/tree.ts @@ -0,0 +1,34 @@ +import { DocumentModel, Node } from '@ali/lowcode-designer'; +import TreeNode from './tree-node'; + +export class Tree { + private treeNodesMap = new Map(); + + readonly root: TreeNode; + + readonly id: string; + + readonly document: DocumentModel; + + constructor(document: DocumentModel) { + this.document = document; + this.root = this.getTreeNode(document.rootNode); + this.id = document.id; + } + + getTreeNode(node: Node): TreeNode { + if (this.treeNodesMap.has(node.id)) { + const tnode = this.treeNodesMap.get(node.id)!; + tnode.setNode(node); + return tnode; + } + + const treeNode = new TreeNode(this, node); + this.treeNodesMap.set(node.id, treeNode); + return treeNode; + } + + getTreeNodeById(id: string) { + return this.treeNodesMap.get(id); + } +} diff --git a/packages/plugin-outline-pane/src/views/backup-pane.tsx b/packages/plugin-outline-pane/src/views/backup-pane.tsx new file mode 100644 index 000000000..eb933149f --- /dev/null +++ b/packages/plugin-outline-pane/src/views/backup-pane.tsx @@ -0,0 +1,18 @@ +import { PureComponent } from 'react'; +import { PluginProps } from '@ali/lowcode-types'; +import { OutlinePane } from './pane'; + +export const Backup = Symbol.for('backup-outline'); + +export class OutlineBackupPane extends PureComponent { + render() { + return ( + + ); + } +} diff --git a/packages/plugin-outline-pane/src/views/pane.tsx b/packages/plugin-outline-pane/src/views/pane.tsx new file mode 100644 index 000000000..d17ee1929 --- /dev/null +++ b/packages/plugin-outline-pane/src/views/pane.tsx @@ -0,0 +1,40 @@ +import React, { Component } from 'react'; +import { observer } from '@ali/lowcode-editor-core'; +import { intl } from '../locale'; +import { OutlineMain } from '../main'; +import TreeView from './tree'; +import './style.less'; +import { IEditor } from '@ali/lowcode-types'; + +@observer +export class OutlinePane extends Component<{ config: any; editor: IEditor }> { + private main = new OutlineMain(this.props.editor, this.props.config.name || this.props.config.pluginKey); + + shouldComponentUpdate() { + return false; + } + + componentWillUnmount() { + this.main.purge(); + } + + render() { + const tree = this.main.currentTree; + + if (!tree) { + return ( +
+

{intl('Initializing')}

+
+ ); + } + + return ( +
+
this.main.mount(shell)} className="lc-outline-tree-container"> + +
+
+ ); + } +} diff --git a/packages/plugin-outline-pane/src/views/root-tree-node.tsx b/packages/plugin-outline-pane/src/views/root-tree-node.tsx new file mode 100644 index 000000000..efed91d78 --- /dev/null +++ b/packages/plugin-outline-pane/src/views/root-tree-node.tsx @@ -0,0 +1,94 @@ +import { Component } from 'react'; +import classNames from 'classnames'; +import { observer } from '@ali/lowcode-editor-core'; +import TreeNode from '../tree-node'; +import TreeTitle from './tree-title'; +import TreeBranches from './tree-branches'; +import { ModalNodesManager } from '@ali/lowcode-designer'; +import { IconEyeClose } from '../icons/eye-close'; + +@observer +class ModalTreeNodeView extends Component<{ treeNode: TreeNode }> { + private modalNodesManager: ModalNodesManager; + + constructor(props: any) { + super(props); + + // 模态管理对象 + this.modalNodesManager = props.treeNode.document.modalNodesManager; + } + + shouldComponentUpdate() { + return false; + } + + hideAllNodes() { + this.modalNodesManager.hideModalNodes(); + } + + render() { + const { treeNode } = this.props; + const modalNodes = treeNode.children?.filter((item) => { + return item.node.componentMeta.isModal; + }); + if (!modalNodes || modalNodes.length === 0) { + return null; + } + + const hasVisibleModalNode = !!this.modalNodesManager.getVisibleModalNode(); + return ( +
+
+ 模态视图层 +
+ {hasVisibleModalNode ? : null} +
+
+
+ +
+
+ ); + } +} + +@observer +export default class RootTreeNodeView extends Component<{ treeNode: TreeNode }> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode } = this.props; + const className = classNames('tree-node', { + // 是否展开 + expanded: treeNode.expanded, + // 是否悬停中 + detecting: treeNode.detecting, + // 是否选中的 + selected: treeNode.selected, + // 是否隐藏的 + hidden: treeNode.hidden, + // 是否忽略的 + // ignored: treeNode.ignored, + // 是否锁定的 + locked: treeNode.locked, + // 是否投放响应 + dropping: treeNode.dropDetail?.index != null, + 'is-root': treeNode.isRoot(), + 'condition-flow': treeNode.node.conditionGroup != null, + highlight: treeNode.isFocusingNode(), + }); + + return ( +
+ + + +
+ ); + } +} diff --git a/packages/plugin-outline-pane/src/views/style.less b/packages/plugin-outline-pane/src/views/style.less new file mode 100644 index 000000000..c910258d8 --- /dev/null +++ b/packages/plugin-outline-pane/src/views/style.less @@ -0,0 +1,388 @@ +.lc-outline-pane { + height: 100%; + width: 100%; + position: relative; + z-index: 2; + background-color: white; + + > .lc-outline-tree-container { + top: 0; + left: 0; + bottom: 0; + right: 0; + position: absolute; + overflow: auto; + } +} + +.lc-outline-tree { + @treeNodeHeight: 30px; + overflow: hidden; + margin-bottom: @treeNodeHeight; + user-select: none; + + .tree-node-modal { + margin: 5px; + border: 1px solid rgba(31, 56, 88, 0.2); + border-radius: 3px; + box-shadow: 0 1px 4px 0 rgba(31, 56, 88, 0.15); + + .tree-node-modal-title { + position: relative; + background: rgba(31, 56, 88, 0.04); + padding: 0 10px; + height: 32px; + line-height: 32px; + border-bottom: 1px solid rgba(31, 56, 88, 0.2); + + .tree-node-modal-title-visible-icon { + position: absolute; + top: 4px; + right: 12px; + cursor: pointer; + } + } + + .tree-pane-modal-content { + & > .tree-node-branches::before { + display: none; + } + } + + .tree-node-modal-radio, .tree-node-modal-radio-active { + margin-right: 4px; + opacity: 0.8; + position: absolute; + top: 7px; + left: 6px; + } + .tree-node-modal-radio-active { + color: #006cff; + } + } + + .tree-node-branches::before { + position: absolute; + display: block; + width: 0; + border-left: 1px solid transparent; + height: 100%; + top: 0; + left: 6px; + content: ' '; + z-index: 2; + pointer-events: none; + } + + &:hover { + .tree-node-branches::before { + border-left-color: #ddd; + } + } + + .insertion { + pointer-events: all !important; + border: 1px dashed var(--color-brand-light); + height: @treeNodeHeight; + box-sizing: border-box; + transform: translateZ(0); + transition: all 0.2s ease-in-out; + &.invalid { + border-color: red; + background-color: rgba(240, 154, 154, 0.719); + } + } + + .condition-group-container { + border-bottom: 1px solid #7b605b; + position: relative; + + &:before { + position: absolute; + display: block; + width: 0; + border-left: 0.5px solid #7b605b; + height: 100%; + top: 0; + left: 0; + content: ' '; + z-index: 2; + } + >.condition-group-title { + text-align: center; + background-color: #7b605b; + height: 14px; + > .lc-title { + font-size: 12px; + transform: scale(0.8); + transform-origin: top; + color: white; + text-shadow: 0px 0px 2px black; + display: block; + } + } + } + .tree-node-slots { + border-bottom: 1px solid rgb(144, 94, 190); + position: relative; + &::before { + position: absolute; + display: block; + width: 0; + border-left: 0.5px solid rgb(144, 94, 190); + height: 100%; + top: 0; + left: 0; + content: ' '; + z-index: 2; + } + >.tree-node-slots-title { + text-align: center; + background-color: rgb(144, 94, 190); + height: 14px; + > .lc-title { + font-size: 12px; + transform: scale(0.8); + transform-origin: top; + color: white; + text-shadow: 0px 0px 2px black; + display: block; + } + } + &.insertion-at-slots { + padding-bottom: @treeNodeHeight; + border-bottom-color: rgb(182, 55, 55); + >.tree-node-slots-title { + background-color: rgb(182, 55, 55); + } + &::before { + border-left-color: rgb(182, 55, 55); + } + } + } + + .tree-node { + .tree-node-expand-btn { + width: 12px; + line-height: 0; + align-self: stretch; + display: flex; + align-items: center; + transition: color 200ms ease; + color: var(--color-icon-normal); + &:hover { + color: var(--color-icon-hover); + } + > svg { + transform-origin: center; + transform: rotate(-90deg); + transition: transform 100ms ease; + } + margin-right: 4px; + } + .tree-node-expand-placeholder { + width: 12px; + height: 12px; + margin-right: 4px; + } + + .tree-node-icon { + transform: translateZ(0); + display: flex; + align-items: center; + margin-right: 4px; + color: var(--color-text); + + & > svg { + width: 16px; + height: 16px; + * { + fill: var(--color-icon-normal, rgba(31, 56, 88, 0.4)); + } + } + & > img { + width: 16px; + height: 16px; + * { + fill: var(--color-icon-normal, rgba(31, 56, 88, 0.4)); + } + } + } + + .tree-node-title { + font-size: var(--font-size-text); + cursor: pointer; + background: var(--color-pane-background); + border-bottom: 1px solid var(--color-line-normal, rgba(31, 56, 88, 0.1)); + display: flex; + align-items: center; + height: @treeNodeHeight; + box-sizing: border-box; + position: relative; + transform: translateZ(0); + padding-right: 5px; + & > :first-child { + margin-left: 2px; + } + + .tree-node-title-label { + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: flex; + align-items: center; + align-self: stretch; + overflow: visible; + margin-right: 5px; + + .tree-node-title-input { + flex: 1; + border: 1px solid var(--color-brand-light); + background-color: var(--color-pane-background); + color: var(--color-text); + line-height: 18px; + padding: 2px; + outline: none; + margin-left: -3px; + border-radius: 2px; + } + } + + .tree-node-hide-btn, .tree-node-lock-btn { + opacity: 0; + color: var(--color-text); + line-height: 0; + align-self: stretch; + display: flex; + align-items: center; + justify-content: center; + width: 22px; + &:hover { + opacity: 1 !important; + } + } + &:hover { + .tree-node-hide-btn, .tree-node-lock-btn { + opacity: 0.5; + } + } + html.lc-cursor-dragging & { + // FIXME: only hide hover shows + .tree-node-hide-btn, .tree-node-lock-btn { + display: none; + } + } + &.editing { + & > .tree-node-hide-btn, & >.tree-node-lock-btn { + display: none; + } + } + + .tree-node-tag { + margin-left: 5px; + display: flex; + align-items: center; + line-height: 0; + &.cond { + color: rgb(179, 52, 6); + } + &.loop { + color: rgb(103, 187, 187); + } + &.slot { + color: rgb(211, 90, 211); + } + } + } + + &.is-root { + > .tree-node-title { + padding-left: 5px; + } + } + + &.expanded { + & > .tree-node-title > .tree-node-expand-btn > svg { + transform: rotate(0); + } + } + + &.detecting > .tree-node-title { + background: var(--color-block-background-light); + } + + // 选中节点处理 + &.selected { + & > .tree-node-title { + background: var(--color-block-background-shallow); + } + + & > .tree-node-branches::before { + border-left-color: var(--color-brand-light); + } + } + + &.hidden { + .tree-node-title-label { + color: #9b9b9b; + } + & > .tree-node-title > .tree-node-hide-btn { + opacity: 0.8; + } + .tree-node-branches { + .tree-node-hide-btn { + opacity: 0; + } + } + } + + &.condition-flow { + & > .tree-node-title > .tree-node-hide-btn { + opacity: 1; + } + &.hidden > .tree-node-title > .tree-node-hide-btn { + opacity: 0; + } + } + + &.locked { + & > .tree-node-title > .tree-node-lock-btn { + opacity: 0.8; + } + .tree-node-branches { + .tree-node-lock-btn, .tree-node-hide-btn { + opacity: 0; + } + } + } + + // 处理拖入节点 + &.dropping { + & > .tree-node-branches::before { + border-left: 1px solid var(--color-brand); + } + & > .tree-node-title { + .tree-node-expand-btn { + color: var(--color-brand); + } + .tree-node-icon { + color: var(--color-brand); + } + .tree-node-title-label > .lc-title { + color: var(--color-brand); + } + } + } + &.highlight { + & > .tree-node-title { + background: var(--color-block-background-shallow); + } + } + + .tree-node-branches { + padding-left: 12px; + position: relative; + } + } +} diff --git a/packages/plugin-outline-pane/src/views/tree-branches.tsx b/packages/plugin-outline-pane/src/views/tree-branches.tsx new file mode 100644 index 000000000..8cbf9db48 --- /dev/null +++ b/packages/plugin-outline-pane/src/views/tree-branches.tsx @@ -0,0 +1,140 @@ +import { Component } from 'react'; +import classNames from 'classnames'; +import { observer, Title } from '@ali/lowcode-editor-core'; +import { ExclusiveGroup } from '@ali/lowcode-designer'; +import TreeNode from '../tree-node'; +import TreeNodeView from './tree-node'; +import { intlNode } from '../locale'; + +@observer +export default class TreeBranches extends Component<{ + treeNode: TreeNode; + isModal?: boolean; +}> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode, isModal } = this.props; + const { expanded } = treeNode; + + if (!expanded) { + return null; + } + + return ( +
+ { + !isModal && + } + +
+ ); + } +} + +@observer +class TreeNodeChildren extends Component<{ + treeNode: TreeNode; + isModal?: boolean; + }> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode, isModal } = this.props; + const children: any = []; + let groupContents: any[] = []; + let currentGrp: ExclusiveGroup; + const endGroup = () => { + if (groupContents.length > 0) { + children.push( +
+
+ + </div> + {groupContents} + </div>, + ); + groupContents = []; + } + }; + const { dropDetail } = treeNode; + const dropIndex = dropDetail?.index; + const insertion = ( + <div + key="insertion" + className={classNames('insertion', { + invalid: dropDetail?.valid === false, + })} + /> + ); + treeNode.children?.forEach((child, index) => { + const childIsModal = child.node.componentMeta.isModal || false; + if (isModal != childIsModal) { + return; + } + const { conditionGroup } = child.node; + if (conditionGroup !== currentGrp) { + endGroup(); + } + + if (conditionGroup) { + currentGrp = conditionGroup; + if (index === dropIndex) { + if (groupContents.length > 0) { + groupContents.push(insertion); + } else { + children.push(insertion); + } + } + groupContents.push(<TreeNodeView key={child.id} treeNode={child} isModal={isModal} />); + } else { + if (index === dropIndex) { + children.push(insertion); + } + children.push(<TreeNodeView key={child.id} treeNode={child} isModal={isModal} />); + } + }); + endGroup(); + const length = treeNode.children?.length || 0; + if (dropIndex != null && dropIndex >= length) { + children.push(insertion); + } + + return <div className="tree-node-children">{children}</div>; + } +} + +@observer +class TreeNodeSlots extends Component<{ + treeNode: TreeNode; + }> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode } = this.props; + if (!treeNode.hasSlots()) { + return null; + } + return ( + <div + className={classNames('tree-node-slots', { + 'insertion-at-slots': treeNode.dropDetail?.focus?.type === 'slots', + })} + data-id={treeNode.id} + > + <div className="tree-node-slots-title"> + <Title title={{ type: 'i18n', intl: intlNode('Slots') }} /> + </div> + {treeNode.slots.map(tnode => ( + <TreeNodeView key={tnode.id} treeNode={tnode} /> + ))} + </div> + ); + } +} diff --git a/packages/plugin-outline-pane/src/views/tree-node.tsx b/packages/plugin-outline-pane/src/views/tree-node.tsx new file mode 100644 index 000000000..a22015cd5 --- /dev/null +++ b/packages/plugin-outline-pane/src/views/tree-node.tsx @@ -0,0 +1,46 @@ +import { Component } from 'react'; +import classNames from 'classnames'; +import { observer } from '@ali/lowcode-editor-core'; +import TreeNode from '../tree-node'; +import TreeTitle from './tree-title'; +import TreeBranches from './tree-branches'; + +@observer +export default class TreeNodeView extends Component<{ + treeNode: TreeNode; + isModal?: boolean; +}> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode, isModal } = this.props; + const className = classNames('tree-node', { + // 是否展开 + expanded: treeNode.expanded, + // 是否悬停中 + detecting: treeNode.detecting, + // 是否选中的 + selected: treeNode.selected, + // 是否隐藏的 + hidden: treeNode.hidden, + // 是否忽略的 + // ignored: treeNode.ignored, + // 是否锁定的 + locked: treeNode.locked, + // 是否投放响应 + dropping: treeNode.dropDetail?.index != null, + 'is-root': treeNode.isRoot(), + 'condition-flow': treeNode.node.conditionGroup != null, + highlight: treeNode.isFocusingNode(), + }); + + return ( + <div className={className} data-id={treeNode.id}> + <TreeTitle treeNode={treeNode} isModal={isModal} /> + <TreeBranches treeNode={treeNode} isModal={false} /> + </div> + ); + } +} diff --git a/packages/plugin-outline-pane/src/views/tree-title.tsx b/packages/plugin-outline-pane/src/views/tree-title.tsx new file mode 100644 index 000000000..165b88534 --- /dev/null +++ b/packages/plugin-outline-pane/src/views/tree-title.tsx @@ -0,0 +1,295 @@ +import { Component, KeyboardEvent, FocusEvent, Fragment } from 'react'; +import classNames from 'classnames'; +import { observer, Title, Tip, globalContext, Editor } from '@ali/lowcode-editor-core'; +import { IconArrowRight } from '../icons/arrow-right'; +import { IconEyeClose } from '../icons/eye-close'; +import { intl, intlNode } from '../locale'; +import TreeNode from '../tree-node'; +import { IconEye } from '../icons/eye'; +import { IconCond } from '../icons/cond'; +import { IconLoop } from '../icons/loop'; +import { IconRadioActive } from '../icons/radio-active'; +import { IconRadio } from '../icons/radio'; +import { createIcon } from '@ali/lowcode-utils'; + +function emitOutlineEvent(type: string, treeNode: TreeNode, rest?: Record<string, unknown>) { + const editor = globalContext.get(Editor); + const node = treeNode?.node; + const npm = node?.componentMeta?.npm; + const selected = + [npm?.package, npm?.componentName].filter((item) => !!item).join('-') || node?.componentMeta?.componentName || ''; + editor?.emit(`outlinePane.${type}`, { + selected, + ...rest, + }); +} + +@observer +export default class TreeTitle extends Component<{ + treeNode: TreeNode; + isModal?: boolean; +}> { + state: { + editing: boolean; + } = { + editing: false, + }; + + private enableEdit = () => { + this.setState({ + editing: true, + }); + }; + + private cancelEdit() { + this.setState({ + editing: false, + }); + this.lastInput = undefined; + } + + private saveEdit = (e: FocusEvent<HTMLInputElement> | KeyboardEvent<HTMLInputElement>) => { + const { treeNode } = this.props; + const value = (e.target as HTMLInputElement).value || ''; + treeNode.setTitleLabel(value); + emitOutlineEvent('rename', treeNode, { value }); + this.cancelEdit(); + }; + + private handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => { + if (e.keyCode === 13) { + this.saveEdit(e); + } + if (e.keyCode === 27) { + this.cancelEdit(); + } + }; + + private lastInput?: HTMLInputElement; + + private setCaret = (input: HTMLInputElement | null) => { + if (!input || this.lastInput === input) { + return; + } + input.focus(); + input.select(); + // 光标定位最后一个 + // input.selectionStart = input.selectionEnd; + }; + + render() { + const { treeNode, isModal } = this.props; + const { editing } = this.state; + const isCNode = !treeNode.isRoot(); + const { node } = treeNode; + const isNodeParent = node.isParental(); + let style: any; + if (isCNode) { + const { depth } = treeNode; + const indent = depth * 12; + style = { + paddingLeft: indent + (isModal ? 12 : 0), + marginLeft: -indent, + }; + } + + return ( + <div + className={classNames('tree-node-title', { + editing, + })} + style={style} + data-id={treeNode.id} + onClick={() => { + if (isModal) { + node.document.modalNodesManager.setVisible(node); + return; + } + if (node.conditionGroup) { + node.setConditionalVisible(); + } + }} + > + {isModal && node.getVisible() && ( + <div onClick={() => { + node.document.modalNodesManager.setInvisible(node); + }} + > + <IconRadioActive className="tree-node-modal-radio-active" /> + </div> + )} + {isModal && !node.getVisible() && ( + <div onClick={() => { + node.document.modalNodesManager.setVisible(node); + }} + > + <IconRadio className="tree-node-modal-radio" /> + </div> + )} + {isCNode && <ExpandBtn treeNode={treeNode} />} + <div className="tree-node-icon">{createIcon(treeNode.icon)}</div> + <div className="tree-node-title-label" onDoubleClick={isNodeParent ? this.enableEdit : undefined}> + {editing ? ( + <input + className="tree-node-title-input" + defaultValue={treeNode.titleLabel} + onBlur={this.saveEdit} + ref={this.setCaret} + onKeyUp={this.handleKeyUp} + /> + ) : ( + <Fragment> + <Title title={treeNode.title} /> + {node.slotFor && ( + <a className="tree-node-tag slot"> + {/* todo: click redirect to prop */} + <Tip>{intlNode('Slot for {prop}', { prop: node.slotFor.key })}</Tip> + </a> + )} + {node.hasLoop() && ( + <a className="tree-node-tag loop"> + {/* todo: click todo something */} + <IconLoop /> + <Tip>{intlNode('Loop')}</Tip> + </a> + )} + {node.hasCondition() && !node.conditionGroup && ( + <a className="tree-node-tag cond"> + {/* todo: click todo something */} + <IconCond /> + <Tip>{intlNode('Conditional')}</Tip> + </a> + )} + </Fragment> + )} + </div> + {isCNode && isNodeParent && !isModal && <HideBtn treeNode={treeNode} />} + {/* isCNode && isNodeParent && <LockBtn treeNode={treeNode} /> */} + </div> + ); + } +} + +// @observer +// class LockBtn extends Component<{ treeNode: TreeNode }> { +// shouldComponentUpdate() { +// return false; +// } + +// render() { +// const { treeNode } = this.props; +// return ( +// <div +// className="tree-node-lock-btn" +// onClick={(e) => { +// e.stopPropagation(); +// treeNode.setLocked(!treeNode.locked); +// }} +// > +// {treeNode.locked ? <IconLock /> : <IconUnlock />} +// <Tip>{treeNode.locked ? intl('Unlock') : intl('Lock')}</Tip> +// </div> +// ); +// } +// } + +@observer +class HideBtn extends Component<{ treeNode: TreeNode }> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode } = this.props; + return ( + <div + className="tree-node-hide-btn" + onClick={(e) => { + e.stopPropagation(); + emitOutlineEvent(treeNode.hidden ? 'show' : 'hide', treeNode); + treeNode.setHidden(!treeNode.hidden); + }} + > + {treeNode.hidden ? <IconEyeClose /> : <IconEye />} + <Tip>{treeNode.hidden ? intl('Show') : intl('Hide')}</Tip> + </div> + ); + } +} + +@observer +class ExpandBtn extends Component<{ treeNode: TreeNode }> { + shouldComponentUpdate() { + return false; + } + + render() { + const { treeNode } = this.props; + if (!treeNode.expandable) { + return <i className="tree-node-expand-placeholder" />; + } + return ( + <div + className="tree-node-expand-btn" + onClick={(e) => { + if (treeNode.expanded) { + e.stopPropagation(); + } + emitOutlineEvent(treeNode.expanded ? 'collapse' : 'expand', treeNode); + treeNode.setExpanded(!treeNode.expanded); + }} + > + <IconArrowRight size="small" /> + </div> + ); + } +} + +/* +interface Point { + clientX: number; + clientY: number; +} + +function setCaret(point: Point) { + debugger; + const range = getRangeFromPoint(point); + if (range) { + selectRange(range); + setTimeout(() => selectRange(range), 1); + } +} + +function getRangeFromPoint(point: Point): Range | undefined { + const x = point.clientX; + const y = point.clientY; + let range; + let pos: CaretPosition | null = null; + if (document.caretRangeFromPoint) { + range = document.caretRangeFromPoint(x, y); + } else if ((pos = document.caretPositionFromPoint(x, y))) { + range = document.createRange(); + range.setStart(pos.offsetNode, pos.offset); + range.collapse(true); + + } + return range; +} + +function selectRange(range: Range) { + const selection = document.getSelection(); + if (selection) { + selection.removeAllRanges(); + selection.addRange(range); + } +} + +function setCaretAfter(elem) { + const range = document.createRange(); + const node = elem.lastChild; + if (!node) return; + range.setStartAfter(node); + range.setEndAfter(node); + selectRange(range); +} +*/ diff --git a/packages/plugin-outline-pane/src/views/tree.tsx b/packages/plugin-outline-pane/src/views/tree.tsx new file mode 100644 index 000000000..4f7871e96 --- /dev/null +++ b/packages/plugin-outline-pane/src/views/tree.tsx @@ -0,0 +1,165 @@ +import { Component, MouseEvent as ReactMouseEvent } from 'react'; +import { observer, Editor, globalContext } from '@ali/lowcode-editor-core'; +import { isRootNode, Node, DragObjectType, isShaken } from '@ali/lowcode-designer'; +import { isFormEvent } from '@ali/lowcode-utils'; +import { Tree } from '../tree'; +import RootTreeNodeView from './root-tree-node'; + +function getTreeNodeIdByEvent(e: ReactMouseEvent, stop: Element): null | string { + let target: Element | null = e.target as Element; + if (!target || !stop.contains(target)) { + return null; + } + target = target.closest('[data-id]'); + if (!target || !stop.contains(target)) { + return null; + } + + return (target as HTMLDivElement).dataset.id || null; +} + +@observer +export default class TreeView extends Component<{ tree: Tree }> { + private shell: HTMLDivElement | null = null; + + private hover(e: ReactMouseEvent) { + const { tree } = this.props; + + const doc = tree.document; + const { detecting } = doc.designer; + if (!detecting.enable) { + return; + } + const node = this.getTreeNodeFromEvent(e)?.node; + detecting.capture(node || null); + } + + private onClick = (e: ReactMouseEvent) => { + if (this.ignoreUpSelected) { + this.boostEvent = undefined; + return; + } + if (this.boostEvent && isShaken(this.boostEvent, e.nativeEvent)) { + this.boostEvent = undefined; + return; + } + this.boostEvent = undefined; + const treeNode = this.getTreeNodeFromEvent(e); + if (!treeNode) { + return; + } + const { node } = treeNode; + const { designer } = treeNode; + const doc = node.document; + const { selection } = doc; + const { id } = node; + const isMulti = e.metaKey || e.ctrlKey || e.shiftKey; + designer.activeTracker.track(node); + if (isMulti && !isRootNode(node) && selection.has(id)) { + if (!isFormEvent(e.nativeEvent)) { + selection.remove(id); + } + } else { + selection.select(id); + const editor = globalContext.get(Editor); + const selectedNode = designer.currentSelection?.getNodes()?.[0]; + const npm = selectedNode?.componentMeta?.npm; + const selected = + [npm?.package, npm?.componentName].filter((item) => !!item).join('-') || + selectedNode?.componentMeta?.componentName || + ''; + editor?.emit('outlinePane.select', { + selected, + }); + } + }; + + private onMouseOver = (e: ReactMouseEvent) => { + this.hover(e); + }; + + private getTreeNodeFromEvent(e: ReactMouseEvent) { + if (!this.shell) { + return; + } + const id = getTreeNodeIdByEvent(e, this.shell); + if (!id) { + return; + } + + const { tree } = this.props; + return tree.getTreeNodeById(id); + } + + private ignoreUpSelected = false; + + private boostEvent?: MouseEvent; + + private onMouseDown = (e: ReactMouseEvent) => { + if (isFormEvent(e.nativeEvent)) { + return; + } + const treeNode = this.getTreeNodeFromEvent(e); + if (!treeNode) { + return; + } + + const { node } = treeNode; + const { designer } = treeNode; + const doc = node.document; + const { selection } = doc; + + // TODO: shift selection + const isMulti = e.metaKey || e.ctrlKey || e.shiftKey; + const isLeftButton = e.button === 0; + + if (isLeftButton && !isRootNode(node)) { + let nodes: Node[] = [node]; + this.ignoreUpSelected = false; + if (isMulti) { + // multi select mode, directily add + if (!selection.has(node.id)) { + designer.activeTracker.track(node); + selection.add(node.id); + this.ignoreUpSelected = true; + } + selection.remove(doc.rootNode.id); + // 获得顶层 nodes + nodes = selection.getTopNodes(); + } else if (selection.has(node.id)) { + nodes = selection.getTopNodes(); + } + this.boostEvent = e.nativeEvent; + designer.dragon.boost( + { + type: DragObjectType.Node, + nodes, + }, + this.boostEvent, + ); + } + }; + + private onMouseLeave = () => { + const { tree } = this.props; + const doc = tree.document; + doc.designer.detecting.leave(doc); + }; + + render() { + const { tree } = this.props; + const { root } = tree; + return ( + <div + className="lc-outline-tree" + ref={(shell) => { this.shell = shell; }} + onMouseDownCapture={this.onMouseDown} + onMouseOver={this.onMouseOver} + onClick={this.onClick} + onMouseLeave={this.onMouseLeave} + > + <RootTreeNodeView key={root.id} treeNode={root} /> + </div> + ); + } +}