Compare commits

...

No commits in common. "master" and "dev" have entirely different histories.
master ... dev

975 changed files with 129537 additions and 24515 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,3 +1,3 @@
**/node_modules
**/build/
build/
*.d.ts

View File

@ -22,7 +22,7 @@ module.exports = {
],
root: true,
plugins: ['jest', 'no-function-declare-after-return', 'react', '@typescript-eslint'],
plugins: ['jest', 'no-for-of-loops', 'no-function-declare-after-return', 'react', '@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
@ -56,8 +56,8 @@ module.exports = {
'comma-dangle': ['error', 'only-multiline'],
'no-constant-condition': 'off',
'no-for-of-loops/no-for-of-loops': 'error',
'no-function-declare-after-return/no-function-declare-after-return': 'error',
'@typescript-eslint/ban-ts-comment': 'warn'
},
globals: {
isDev: true,

View File

@ -1,24 +0,0 @@
---
name: 模板名称在新建issue时候能看到
about: 模板描述对应的issue模板卡片展示时候能看到介绍模板
---
**标题:** [请在此处填写 issue 标题]
**问题描述:** [请描述问题背景、可能的原因、如何重现问题以及相关解决方案]
**关联的 Issues:** [请列出与此问题相关的 issue 编号]
**检查项:**
- [ ] 该问题的存在已经确认
- [ ] 这个问题的重要性和紧迫性已经确认
- [ ] 该问题的责任人已经确认
- [ ] 该问题的解决方案已经确认
- [ ] 该问题的测试方法已经确认
**标签:** [请为该问题添加合适的标签]
**责任人:** [请为该问题分配责任人]
**优先级:** [请给出该问题的优先级]

View File

@ -1,24 +0,0 @@
---
name: The template name can be seen when creating a new issue
about: Template description, which can be seen when displaying the corresponding issue template card
---
**Title:** [Please fill in the issue title here]
**Description:** [Please describe the background, possible causes, how to reproduce the issue, and any necessary solutions]
**Related Issues:** [List the issue numbers related to this issue]
**Checklist:**
- [ ] The existence of this issue has been confirmed
- [ ] The importance and urgency of this issue have been confirmed
- [ ] The responsible person for this issue has been confirmed
- [ ] The solution for this issue has been confirmed
- [ ] The testing method for this issue has been confirmed
**Labels:** [Add appropriate labels]
**Assignees:** [Assign the responsible person for this issue]
**Priority:** [Set the priority for this issue]

View File

@ -1,12 +0,0 @@
**PR 描述:** [请描述提交此 PR 的背景、目的、所做的更改以及如何测试此 PR]
**关联的 Issues:** [请列出与此 PR 相关的 issue 编号]
**检查项(无需修改,提交后界面上可勾选):**
- [ ] 代码已经被审查
- [ ] 代码符合项目的代码标准和最佳实践
- [ ] 代码已经通过所有测试用例
- [ ] 代码不影响现有功能的正常使用
- [ ] 文档已经同步更新
**截图(可选):** [提供相关的截图或 gif 动画]

View File

@ -1,12 +0,0 @@
**Description:** [Please describe the background, purpose, changes made, and how to test this PR]
**Related Issues:** [List the issue numbers related to this PR]
**Checklist:**
- [ ] Code has been reviewed
- [ ] Code complies with the project's code standards and best practices
- [ ] Code has passed all tests
- [ ] Code does not affect the normal use of existing features
- [ ] Documentation has been updated
**Screenshots(optional):** [Provide relevant screenshots or GIF animations]

5
.gitignore vendored
View File

@ -4,8 +4,3 @@
package-lock.json
pnpm-lock.yaml
/packages/**/node_modules
/packages/inula-cli/lib
build
/packages/inula-router/connectRouter
/packages/inula-router/router
.inula-max

View File

@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run commitlint

View File

@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run lint-commit

View File

@ -1,3 +0,0 @@
**/build
*.md
*.html

View File

@ -16,17 +16,17 @@
'use strict';
module.exports = {
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'auto', // 仅限换行(\n
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'lf', // 仅限换行(\n
};

View File

@ -1,51 +0,0 @@
version: '1.0'
name: branch-pipeline
displayName: BranchPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@nodejs
name: build_nodejs
displayName: Nodejs 构建
# 支持8.16.2、10.17.0、12.16.1、14.16.0、15.12.0五个版本
nodeVersion: 14.16.0
# 构建命令:安装依赖 -> 清除上次打包产物残留 -> 执行构建 【请根据项目实际产出进行填写】
commands:
- npm install && rm -rf ./dist && npm run build
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径,是指代码编译完毕之后构建物的所在路径
path:
- ./dist
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_nodejs
- stage:
name: release
displayName: 发布
steps:
- step: publish@release_artifacts
name: publish_release_artifacts
displayName: '发布'
# 上游上传制品任务的产出
dependArtifact: output
# 发布制品版本号
version: '1.0.0.0'
# 是否开启版本号自增,默认开启
autoIncrement: true
triggers:
push:
branches:
exclude:
- master
include:
- .*

View File

@ -1,49 +0,0 @@
version: '1.0'
name: master-pipeline
displayName: MasterPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@nodejs
name: build_nodejs
displayName: Nodejs 构建
# 支持8.16.2、10.17.0、12.16.1、14.16.0、15.12.0五个版本
nodeVersion: 14.16.0
# 构建命令:安装依赖 -> 清除上次打包产物残留 -> 执行构建 【请根据项目实际产出进行填写】
commands:
- npm install && rm -rf ./dist && npm run build
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径,是指代码编译完毕之后构建物的所在路径
path:
- ./dist
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_nodejs
- stage:
name: release
displayName: 发布
steps:
- step: publish@release_artifacts
name: publish_release_artifacts
displayName: '发布'
# 上游上传制品任务的产出
dependArtifact: output
# 发布制品版本号
version: '1.0.0.0'
# 是否开启版本号自增,默认开启
autoIncrement: true
triggers:
push:
branches:
include:
- master

View File

@ -1,36 +0,0 @@
version: '1.0'
name: pr-pipeline
displayName: PRPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@nodejs
name: build_nodejs
displayName: Nodejs 构建
# 支持8.16.2、10.17.0、12.16.1、14.16.0、15.12.0五个版本
nodeVersion: 14.16.0
# 构建命令:安装依赖 -> 清除上次打包产物残留 -> 执行构建 【请根据项目实际产出进行填写】
commands:
- npm install && rm -rf ./dist && npm run build
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径,是指代码编译完毕之后构建物的所在路径
path:
- ./dist
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_nodejs
triggers:
pr:
branches:
include:
- master

View File

@ -1,3 +0,0 @@
# Inula Contributing Guide
查看[贡献指南](https://docs.openinula.net/docs/%E8%B4%A1%E7%8C%AE%E6%8C%87%E5%8D%97)获取完整指南。

127
LICENSE
View File

@ -1,127 +0,0 @@
木兰宽松许可证, 第2版
木兰宽松许可证, 第2版
2020年1月 http://license.coscl.org.cn/MulanPSL2
您对“软件”的复制、使用、修改及分发受木兰宽松许可证第2版“本许可证”的如下条款的约束
0. 定义
“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
“法人实体”是指提交贡献的机构及其“关联实体”。
“关联实体”是指对“本许可证”下的行为方而言控制、受控制或与其共同受控制的机构此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
1. 授予版权许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
2. 授予专利许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
3. 无商标许可
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可但您为满足第4条规定的声明义务而必须使用除外。
4. 分发限制
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
5. 免责声明与责任限制
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
6. 语言
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
条款结束
如何将木兰宽松许可证第2版应用到您的软件
如果您希望将木兰宽松许可证第2版应用到您的新软件为了方便接收者查阅建议您完成如下三步
1 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
2 请您在软件包的一级目录下创建以“LICENSE”为名的文件将整个许可证文本放入该文件中
3 请将如下声明文本放入每个源文件的头部注释中。
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
Mulan Permissive Software LicenseVersion 2
Mulan Permissive Software LicenseVersion 2 (Mulan PSL v2)
January 2020 http://license.coscl.org.cn/MulanPSL2
Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
0. Definition
Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
Contribution means the copyrightable work licensed by a particular Contributor under this License.
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
Legal Entity means the entity making a Contribution and all its Affiliates.
Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, control means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
1. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
2. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
3. No Trademark License
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
4. Distribution Restriction
You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
5. Disclaimer of Warranty and Limitation of Liability
THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW ITS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
6. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS
How to Apply the Mulan Permissive Software LicenseVersion 2 (Mulan PSL v2) to Your Software
To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
iii Attach the statement to the appropriate annotated syntax at the beginning of each source file.
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.

View File

@ -1,498 +0,0 @@
THIRD PARTY OPEN SOURCE SOFTWARE NOTICE
Please note we provide an open source software notice for the third party open source software along with this software and/or this software component contributed by Huawei (in the following just “this SOFTWARE”). The open source software licenses are granted by the respective right holders.
Warranty Disclaimer
The open source software in this software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the applicable licenses for more details.
Copyright Notice and License Texts
Software: glob 10.3.4
Copyright notice: Isaac Z. Schlueter <i@izs.me> (https://blog.izs.me/)
License: ISC License
ISC License
Copyright <YEAR> <OWNER>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Software: inquirer 8.2.6
Copyright notice: Simon Boudrias <admin@simonboudrias.com>
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: mkdirp 3.0.1
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: yargs-parser 21.1.1
Copyright notice: Ben Coe <ben@npmjs.com>
License: ISC License
ISC LICENSE
COPYRIGHT <YEAR> <OWNER>
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Software: yeoman-environment 3.19.3
Copyright notice: Yeoman
License: BSD-2-Clause
Copyright <YEAR> <COPYRIGHT HOLDER>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Software: yeoman-generator 5.9.0
Copyright notice: Yeoman
License: BSD-2-Clause
COPYRIGHT <YEAR> <COPYRIGHT HOLDER>
REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS, WITH OR WITHOUT MODIFICATION, ARE PERMITTED PROVIDED THAT THE FOLLOWING CONDITIONS ARE MET:
1. REDISTRIBUTIONS OF SOURCE CODE MUST RETAIN THE ABOVE COPYRIGHT NOTICE, THIS LIST OF CONDITIONS AND THE FOLLOWING DISCLAIMER.
2. REDISTRIBUTIONS IN BINARY FORM MUST REPRODUCE THE ABOVE COPYRIGHT NOTICE, THIS LIST OF CONDITIONS AND THE FOLLOWING DISCLAIMER IN THE DOCUMENTATION AND/OR OTHER MATERIALS PROVIDED WITH THE DISTRIBUTION.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Software: @babel/runtime 7.22.15
Copyright notice: The Babel Team (https://babel.dev/team)
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: babel-eslint 10.1.0
Copyright notice: Sebastian McKenzie <sebmck@gmail.com>
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: chalk 4.1.2
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: chokidar 3.5.3
Copyright notice: Paul Miller (https://paulmillr.com)
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: crequire 1.8.1
Copyright notice: army8735 <army8735@qq.com>
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: deepmerge 4.3.1
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: dotenv 16.3.1
License: BSD-2-Clause
COPYRIGHT <YEAR> <COPYRIGHT HOLDER>
REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS, WITH OR WITHOUT MODIFICATION, ARE PERMITTED PROVIDED THAT THE FOLLOWING CONDITIONS ARE MET:
1. REDISTRIBUTIONS OF SOURCE CODE MUST RETAIN THE ABOVE COPYRIGHT NOTICE, THIS LIST OF CONDITIONS AND THE FOLLOWING DISCLAIMER.
2. REDISTRIBUTIONS IN BINARY FORM MUST REPRODUCE THE ABOVE COPYRIGHT NOTICE, THIS LIST OF CONDITIONS AND THE FOLLOWING DISCLAIMER IN THE DOCUMENTATION AND/OR OTHER MATERIALS PROVIDED WITH THE DISTRIBUTION.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Software: esbuild 0.18.20
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: express 4.18.2
Copyright notice: TJ Holowaychuk <tj@vision-media.ca>
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: http-proxy-middleware 2.0.6
Copyright notice: Steven Chim
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: install 0.13.0
Copyright notice: Ben Newman < bn@cs.stanford.edu>
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: jest 29.7.0
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: lodash 4.17.21
Copyright notice: John-David Dalton <john.david.dalton@gmail.com>
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: resolve 1.22.6
Copyright notice: James Halliday<mail@substack.net>(http://substack.net)
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: ts-jest 29.1.1
Copyright notice: Kulshekhar Kabra <kulshekhar@users.noreply.github.com> (https://github.com/kulshekhar)
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: ts-node 10.9.1
Copyright notice: Blake Embrey <hello@blakeembrey.com>(http://blakeembrey.me)
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: vite 4.4.2
Copyright notice: Evan You
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: webpack 5.88.2
Copyright notice: Tobias Koppers @sokra
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Software: webpack-dev-server 4.15.1
Copyright notice: Tobias Koppers @sokra
License: MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,87 +0,0 @@
# openInula 开源项目
## 项目介绍
单词 Inula发音为[ˈɪnjʊlə]意为一类旋覆花属菊科的植物。openInula 是一款用于构建用户界面的 JavaScript 库,提供响应式 API 帮助开发者简单高效构建 Web 页面,比传统虚拟 DOM 方式渲染效率提升30%以上!同时 openInula 提供与 React 保持一致的 API并且提供5大常用功能组件状态管理器、路由、国际化、请求组件、应用脚手架以便开发者高效、高质量的构筑基于 openInula 的前端产品。
## 技术架构
![](https://openinula-website.obs.ap-southeast-1.myhuaweicloud.com/misc/structure.png)
## 核心能力
### 响应式API
openInula 通过监听状态变量的变化,以细粒度的依赖追踪机制来实现响应式更新,避免了虚拟 DOM 的开销。通过最小化重新渲染的范围从而进行高效的UI渲染。无需用户过度关注性能优化。
>(实验性功能,可在 `reactive` 分支查看代码或使用 npm 仓中 experimental 版本体验)
### 兼容 React API
提供与 React 一致的 API完全支持 React 生态,可将 React 应用可零修改切换至 openInula。
### openInula 配套组件
#### 状态管理器 inula-X
inula-X 是 openInula 默认提供的状态管理器。无需额外引入三方库,就可以简单实现跨组件/页面共享状态。
inula-X 与 Redux 相比,可创建多个 Store不需要在 Reducer 中返回 state 并且简化了 Action 和 Reducer 的创建步骤原生支持异步能力组件能做到精准重渲染。inula-X 均可使用函数组件、class 组件,能提供 redux 的适配接口及支持响应式的特点。
#### 路由 inula-router
inula-router 为 openInula 提供前端路由的能力,是构建大型应用必要组件,涵盖 react-router、history、connect-react-router 的功能。
#### 请求 inula-request
inula-request 是 openInula 的网络请求组件,不仅涵盖常见的网络请求方式,还提供动态轮询钩子函数给用户更便捷的定制化请求体验。
#### 国际化 inula-intl
inula-intl 是基于 openInula 的国际化组件,涵盖了基本的国际化组件和钩子函数,允许用户更方便地构建国际化能力。
#### 调试工具 inula-dev-tools
inula-dev-tools 是一个为 openInula 开发者提供的强大工具集,能够方便地查看和编辑组件树、管理应用状态以及进行性能分析,极大提高了开发效率和诊断问题的便捷性。
#### 脚手架 create-inula
create-inula 是一套用于创建 openInula 项目的脚手架工具。它预置了一系列项目模板,允许开发者通过命令行按需快速生成可运行的项目代码。
## 参与贡献
我们鼓励开发者以各种方式参与代码贡献、生态拓展或文档反馈,献您的原创内容,详细请参考[贡献指南](https://docs.openinula.net/docs/%E8%B4%A1%E7%8C%AE%E6%8C%87%E5%8D%97)。
### 官方链接
欢迎访问 openInula 官网与文档仓库,参与 openInula 开发者文档开源项目,与我们一起完善开发者文档。
* openInula 官网:[https://www.openinula.net/](https://www.openinula.net/)
* openInula 文档:[https://docs.openinula.net/](https://docs.openinula.net/)
* openInula 仓库地址:[https://gitee.com/openinula/inula](https://gitee.com/openinula/inula)
* openInula 社提案备忘录RFC[https://gitee.com/openInula/rfcs](https://gitee.com/openInula/rfcs)
### 社区贡献者案例
**[`umi-inula`](https://gitee.com/congxiaochen/inula)**
基于 umijs 与 openInula 的开发框架集成官方组件与UI、AIGC等功能开箱即用。
**[`VoerkaI18n`](https://github.com/zhangfisher/voerka-i18n/)**
适用于多框架的 JavaScript 国际化解决方案,提供对 openInula 的适配。
- [适配示例](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fzhangfisher%2Fvoerka-i18n%2Ftree%2Fmaster%2Fexamples%2Fopeninula)
- [适配文档](https://gitee.com/link?target=https%3A%2F%2Fzhangfisher.github.io%2Fvoerka-i18n%2F%23%2Fzh%2Fguide%2Fintegration%2Fopeninula)
## 许可协议
openInula 主要遵循 [Mulan Permissive Software License v2](http://license.coscl.org.cn/MulanPSL2) 协议,详情请参考各代码仓 LICENSE 声明。
## 联系方式
* 官方邮箱: [team@inulajs.org](mailto:team@inulajs.org)
* 微信公众号:
![](https://www.openinula.net/assets/qrcode.inula-02f99d58.jpg)

View File

@ -1,28 +1,3 @@
# 0.0.2 版本
## 新特性
- **inula-request** 新增响应体中获取完整 URL 能力。
## API变更
## Bug修复
- **inula** 解决事件卸载失败问题。
- **inula** 解决 mouseover 重复触发 mouseEnter 事件问题。
- **inula** 大数组合并使用 concat。
- **inula** 事件支持 defaultPrevented 属性
## CVE漏洞修复
## 已知问题
# 0.0.1 版本
## 新特性

View File

@ -1,90 +1,69 @@
{
"name": "inula",
"description": "OpenInula is a JavaScript framework library.",
"name": "inulajs",
"description": "InulaJS is a JavaScript framework library.",
"version": "0.0.1",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts --fix",
"lint-commit": "lint-staged",
"prettier": "prettier .prettierrc.js -w packages/**/*.{ts,tsx,js,jsx}",
"build:inula": "pnpm -F openinula build",
"test:inula": "pnpm -F openinula test",
"test:inula-intl": "pnpm -F inula-intl test",
"test:inula-request": "pnpm -F inula-request test",
"test:inula-router": "pnpm -F inula-router test",
"prettier": "prettier -w libs/**/*.ts",
"build:inula": "pnpm -F inulajs build",
"test:inula": "pnpm -F inulajs test",
"build:inula-cli": "pnpm -F inula-cli build",
"build:inula-intl": "pnpm -F inula-intl build",
"build:inula-intl": "pnpm -F inula-intl rollup-build",
"build:inula-request": "pnpm -F inula-request build",
"build:inula-router": "pnpm -F inula-router build",
"commitlint": "commitlint --config commitlint.config.js -e",
"postinstall": "husky install"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier .prettierrc.js -w"
]
"build:inula-router": "pnpm -F inula-router build"
},
"devDependencies": {
"@babel/core": "7.23.7",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-proposal-private-methods": "7.18.6",
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
"@babel/plugin-syntax-jsx": "7.23.3",
"@babel/plugin-transform-arrow-functions": "7.23.3",
"@babel/plugin-transform-block-scoped-functions": "7.23.3",
"@babel/plugin-transform-block-scoping": "7.23.4",
"@babel/plugin-transform-classes": "7.23.8",
"@babel/plugin-transform-computed-properties": "7.23.3",
"@babel/plugin-transform-destructuring": "7.23.3",
"@babel/plugin-transform-for-of": "7.23.6",
"@babel/plugin-transform-literals": "7.23.3",
"@babel/plugin-transform-object-assign": "7.23.3",
"@babel/plugin-transform-object-super": "7.23.3",
"@babel/plugin-transform-parameters": "7.23.3",
"@babel/plugin-transform-react-jsx": "7.23.4",
"@babel/plugin-transform-react-jsx-source": "^7.23.3",
"@babel/plugin-transform-runtime": "7.23.7",
"@babel/plugin-transform-shorthand-properties": "7.23.3",
"@babel/plugin-transform-spread": "7.23.3",
"@babel/plugin-transform-template-literals": "7.23.3",
"@babel/preset-env": "7.23.8",
"@babel/preset-typescript": "7.23.3",
"@babel/runtime": "7.23.8",
"@commitlint/cli": "^17.8.1",
"@commitlint/config-conventional": "^17.8.1",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^15.2.3",
"@babel/core": "7.16.7",
"@babel/plugin-proposal-class-properties": "7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
"@babel/plugin-proposal-object-rest-spread": "7.16.7",
"@babel/plugin-proposal-optional-chaining": "7.16.7",
"@babel/plugin-proposal-private-methods": "7.16.7",
"@babel/plugin-proposal-private-property-in-object": "7.16.7",
"@babel/plugin-syntax-jsx": "7.16.7",
"@babel/plugin-transform-arrow-functions": "7.16.7",
"@babel/plugin-transform-block-scoped-functions": "7.16.7",
"@babel/plugin-transform-block-scoping": "7.16.7",
"@babel/plugin-transform-classes": "7.16.7",
"@babel/plugin-transform-computed-properties": "7.16.7",
"@babel/plugin-transform-destructuring": "7.16.7",
"@babel/plugin-transform-for-of": "7.16.7",
"@babel/plugin-transform-literals": "7.16.7",
"@babel/plugin-transform-object-assign": "7.16.7",
"@babel/plugin-transform-object-super": "7.16.7",
"@babel/plugin-transform-parameters": "7.16.7",
"@babel/plugin-transform-react-jsx": "7.16.7",
"@babel/plugin-transform-react-jsx-source": "^7.16.7",
"@babel/plugin-transform-runtime": "7.16.7",
"@babel/plugin-transform-shorthand-properties": "7.16.7",
"@babel/plugin-transform-spread": "7.16.7",
"@babel/plugin-transform-template-literals": "7.16.7",
"@babel/preset-env": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"@babel/runtime": "7.16.7",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0",
"@types/jest": "^29.5.11",
"@types/jest": "^26.0.24",
"@types/node": "^17.0.18",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "6.18.1",
"@babel/parser": "^7.24.7",
"magic-string": "^0.30.10",
"babel-jest": "^29.7.0",
"@typescript-eslint/eslint-plugin": "4.8.0",
"@typescript-eslint/parser": "4.8.0",
"babel-jest": "^27.5.1",
"ejs": "^3.1.8",
"eslint": "^8.56.0",
"eslint": "7.13.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-jest": "^22.15.0",
"eslint-plugin-no-for-of-loops": "^1.0.0",
"eslint-plugin-no-function-declare-after-return": "^1.0.0",
"eslint-plugin-react": "7.14.3",
"husky": "^8.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.2.0",
"openinula": "workspace:*",
"prettier": "^3.1.1",
"rollup": "^2.79.1",
"rollup-plugin-dts": "^6.1.0",
"jest": "^25.5.4",
"jest-environment-jsdom-sixteen": "^1.0.3",
"prettier": "2.6.2",
"rollup": "^2.75.5",
"rollup-plugin-execute": "^1.1.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-polyfill-node": "^0.13.0",
"ts-jest": "^29.1.1",
"typescript": "^4.9.5"
"typescript": "4.2.3"
},
"engines": {
"node": ">=10.x",

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
'use strict';
module.exports = {
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'lf', // 仅限换行(\n
};

View File

@ -128,9 +128,9 @@ class BasicGenerator extends Generator {
if (fs.existsSync(fullpath)) {
this.traverseDirCapture(fullpath, dirCallback, fileCallback);
}
} else {
fileCallback(fullpath);
continue;
}
fileCallback(fullpath);
}
}
@ -140,9 +140,9 @@ class BasicGenerator extends Generator {
if (fs.lstatSync(fullpath).isDirectory()) {
this.traverseDirBubble(fullpath, dirCallback, fileCallback);
dirCallback(fullpath);
} else {
fileCallback(fullpath);
continue;
}
fileCallback(fullpath);
}
}

View File

@ -1,4 +0,0 @@
# openinula + vite
该模板提供了 `openinula` 工作在 `vite`的基础配置。
> 请注意由于Vite插件有node版本限制请使用`node -v`命令确认node版本大于等于node v18。

View File

@ -11,7 +11,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"openinula": "^0.1.1"
"inulajs": "^0.0.11"
},
"devDependencies": {
"@babel/core": "^7.21.4",

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import Inula from 'openinula';
import Inula from 'inulajs';
import './index.css';
function App() {
@ -34,9 +34,7 @@ function App() {
<h2>了解更多</h2>
<p>
要了解 Inula查看{' '}
<a href="https://openinula.net/" target="_blank">
Inula 官网
</a>
<a href="https://openinula.com/" target="_blank">Inula 官网</a>
</p>
</div>
</div>

View File

@ -16,9 +16,9 @@
import react from '@vitejs/plugin-react';
let alias = {
react: 'openinula', // 新增
'react-dom': 'openinula', // 新增
'react/jsx-dev-runtime': 'openinula/jsx-dev-runtime',
react: 'inulajs', // 新增
'react-dom': 'inulajs', // 新增
'react/jsx-dev-runtime': 'inulajs/jsx-dev-runtime',
};
export default {

View File

@ -10,7 +10,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"openinula": "^0.1.1"
"inulajs": "^0.0.11"
},
"devDependencies": {
"@babel/core": "^7.21.4",

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import Inula from 'openinula';
import Inula from 'inulajs';
import './styles.css';
class App extends Inula.Component {
@ -35,9 +35,7 @@ class App extends Inula.Component {
<h2>了解更多</h2>
<p>
要了解 Inula查看{' '}
<a href="https://openinula.org" target="_blank">
Inula 官网
</a>
<a href="https://inulajs.org" target="_blank">Inula 官网</a>
</p>
</div>
</div>

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import Inula from 'openinula';
import Inula from 'inulajs';
import App from './App';
Inula.render(<App />, document.getElementById('root'));

View File

@ -17,7 +17,7 @@ const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.jsx',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
@ -36,7 +36,7 @@ module.exports = {
'@babel/preset-react',
{
runtime: 'automatic', // 新增
importSource: 'openinula', // 新增
importSource: 'inulajs', // 新增
},
],
],
@ -78,7 +78,4 @@ module.exports = {
port: 9000,
open: true,
},
resolve: {
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'],
},
};

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
const BasicGenerator = require('../../BasicGenerator');
class Generator extends BasicGenerator {
prompting() {
return this.prompt([
{
type: 'list',
name: 'bundlerType',
message: 'Please select the build type',
choices: ['webpack', 'vite'],
},
]).then(props => {
this.prompts = props;
});
}
writing() {
const src = this.templatePath(this.prompts.bundlerType);
const dest = this.destinationPath();
this.writeFiles(src, dest, {
context: {
...this.prompts,
},
});
}
}
module.exports = Generator;

View File

@ -1,3 +0,0 @@
{
"description": "simple reactive app template."
}

View File

@ -1,4 +0,0 @@
# openinula + vite
该模板提供了 `openinula` 工作在 `vite`的基础配置。
> 请注意由于Vite插件有node版本限制请使用`node -v`命令确认node版本大于等于node v18。

View File

@ -1,11 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>My Inula App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>

View File

@ -1,25 +0,0 @@
{
"name": "inula-vite-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "vite",
"build": "vite build"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"openinula": "0.0.0-experimental-20231201"
},
"devDependencies": {
"@babel/core": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@babel/preset-react": "^7.18.6",
"@vitejs/plugin-react": "^3.1.0",
"@vitejs/plugin-react-refresh": "^1.3.6",
"babel-plugin-import": "^1.13.6",
"vite": "^4.2.1"
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { useComputed, useReactive, useRef } from 'openinula';
function ReactiveComponent() {
const renderCount = ++useRef(0).current;
const data = useReactive({ count: 0 });
const countText = useComputed(() => {
return `计时: ${data.count.get()}`;
});
setInterval(() => {
data.count.set(c => c + 1);
}, 1000);
return (
<div>
<div>{countText}</div>
<div>组件渲染次数:{renderCount}</div>
</div>
);
}
export default ReactiveComponent;

View File

@ -1,57 +0,0 @@
* {
box-sizing: border-box;
}
body,
html {
margin: 0;
padding: 0;
font-family: 'Montserrat', sans-serif;
line-height: 1.6;
color: #fff;
background: linear-gradient(120deg, #6a11cb 0%, #2575fc 100%);
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
text-align: center;
}
.hero-title {
font-size: 3em;
margin-bottom: 20px;
}
.hero-subtitle {
font-size: 1.5em;
margin-bottom: 50px;
}
.content {
display: flex;
justify-content: space-between;
align-items: center;
margin: 1vh;
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
padding: 20px;
width: 100%;
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(4px);
}
.card h2,
.card p {
color: #fff;
}
.card a {
color: #fff;
text-decoration: underline;
}

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula from 'openinula';
import ReactiveComponent from './ReactiveComponent';
import './index.css';
function App() {
return (
<div class="container">
<div class="hero">
<h1 class="hero-title animate__animated animate__bounceInDown">欢迎来到 Inula 项目!</h1>
<p class="hero-subtitle animate__animated animate__bounceInUp">你已成功创建你的第一个响应式 Inula 项目</p>
</div>
<div className="content">
<div className="card animate__animated animate__zoomIn">
<ReactiveComponent />
</div>
</div>
<div class="content">
<div class="card animate__animated animate__zoomIn">
<h2>了解更多</h2>
<p>
要了解 Inula查看{' '}
<a href="https://openinula.net/" target="_blank">
Inula 官网
</a>
</p>
</div>
</div>
</div>
);
}
Inula.render(<App />, document.getElementById('root'));

View File

@ -1,29 +0,0 @@
{
"name": "inula-webpack-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"author": "",
"license": "ISC",
"dependencies": {
"openinula": "0.0.0-experimental-20231201"
},
"devDependencies": {
"@babel/core": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.2",
"css-loader": "^6.7.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.2",
"url-loader": "^4.1.1",
"webpack": "^5.77.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.2"
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula from 'openinula';
import ReactiveComponent from './ReactiveComponent';
import './styles.css';
class App extends Inula.Component {
render() {
return (
<div class="container">
<div class="hero">
<h1 class="hero-title animate__animated animate__bounceInDown">欢迎来到 Inula 项目!</h1>
<p class="hero-subtitle animate__animated animate__bounceInUp">你已成功创建你的第一个响应式 Inula 项目</p>
</div>
<div className="content">
<div className="card animate__animated animate__zoomIn">
<ReactiveComponent />
</div>
</div>
<div class="content">
<div class="card animate__animated animate__zoomIn">
<h2>了解更多</h2>
<p>
要了解 Inula查看{' '}
<a href="https://openinula.org" target="_blank">
Inula 官网
</a>
</p>
</div>
</div>
</div>
);
}
}
export default App;

View File

@ -1,38 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { useComputed, useReactive, useRef } from 'openinula';
function ReactiveComponent() {
const renderCount = ++useRef(0).current;
const data = useReactive({ count: 0 });
const countText = useComputed(() => {
return `计时: ${data.count.get()}`;
});
setInterval(() => {
data.count.set(c => c + 1);
}, 1000);
return (
<div>
<div>{countText}</div>
<div>组件渲染次数:{renderCount}</div>
</div>
);
}
export default ReactiveComponent;

View File

@ -1,11 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Inula App</title>
</head>
<body>
<div id="root"></div>
<script src="../dist/bundle.js"></script>
</body>
</html>

View File

@ -1,57 +0,0 @@
* {
box-sizing: border-box;
}
body,
html {
margin: 0;
padding: 0;
font-family: 'Montserrat', sans-serif;
line-height: 1.6;
color: #fff;
background: linear-gradient(120deg, #6a11cb 0%, #2575fc 100%);
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
text-align: center;
}
.hero-title {
font-size: 3em;
margin-bottom: 20px;
}
.hero-subtitle {
font-size: 1.5em;
margin-bottom: 50px;
}
.content {
display: flex;
justify-content: space-between;
align-items: center;
margin: 1vh;
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
padding: 20px;
width: 100%;
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(4px);
}
.card h2,
.card p {
color: #fff;
}
.card a {
color: #fff;
text-decoration: underline;
}

View File

@ -1,84 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.jsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
[
'@babel/preset-react',
{
runtime: 'automatic', // 新增
importSource: 'openinula', // 新增
},
],
],
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
publicPath: 'images/',
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ['file-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'src/index.html'),
filename: 'index.html',
}),
],
devServer: {
static: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
open: true,
},
resolve: {
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'],
},
};

View File

@ -32,8 +32,8 @@ const generatorType = fs
});
const runGenerator = async (templatePath, { name = '', cwd = process.cwd(), args = {} }) => {
let currentPath;
return new Promise(resolve => {
let currentPath;
if (name) {
mkdirp.sync(name);
currentPath = path.join(cwd, name);
@ -46,7 +46,7 @@ const runGenerator = async (templatePath, { name = '', cwd = process.cwd(), args
const generator = new Generator({
name,
env,
resolved: require.resolve(templatePath),
resolved: path.join(__dirname, templatePath),
args,
});
return generator.run(() => {
@ -62,17 +62,7 @@ const run = async config => {
}
process.emit('message', { type: 'prompt' });
let { type, name } = config;
if (!name) {
const answers = await inquirer.prompt([
{
name: 'projectName',
message: 'Project name',
type: 'input',
},
]);
config.name = answers.projectName;
}
let { type } = config;
if (!type) {
const answers = await inquirer.prompt([
{

View File

@ -1,14 +1,11 @@
{
"name": "create-inula",
"version": "0.0.8",
"version": "1.0.22",
"description": "",
"main": "index.js",
"bin": {
"create-inula": "bin/cli.js"
},
"engines": {
"node": ">= 18.0.0"
},
"files": [
"bin",
"lib",
@ -16,17 +13,17 @@
"package.json"
],
"author": "",
"license": "MulanPSL2",
"license": "ISC",
"dependencies": {
"glob": "^10.3.3",
"inquirer": "^8.0.0",
"mkdirp": "^3.0.1",
"yargs-parser": "^21.1.1",
"yeoman-environment": "^3.15.0",
"yeoman-generator": "^5.8.0",
"chalk": "^4.1.2"
"yeoman-generator": "^5.8.0"
},
"devDependencies": {
"chalk": "^4.1.2",
"lodash": "^4.17.21"
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
module.exports = {
'parser': 'babel-eslint',
'env': {
'amd': true,
'es6': true,
'browser': true,
'node': false
},
'parserOptions': {
'ecmaVersion': 6,
'sourceType': 'module',
'ecmaFeatures': {
'jsx': true
}
},
'ignorePatterns': [
"src/template"
],
'rules': {
'indent': [
'error',
4,
{
SwitchCase: 1,
flatTernaryExpressions: true
}
],
'no-unused-vars': 'off', // 允许变量声明后未使用
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
"no-underscore-dangle": ["off", "always"], // 允许私有变量 _xxx的变量命名方式
'filenames/match-exported': 0,
'consistent-return': 0,
"comma-dangle": [2, "never"], // 组和对象键值对最后一个逗号, never参数不能带末尾的逗号, always参数必须带末尾的逗号
'global-require': 0, // 允许require语句不出现在顶层中
'no-nested-ternary': 0, // 允许嵌套三元表达式
'no-unused-expressions': 0, // 允许使用未执行的表达式。比如fn是一个函数允许 fn && fn()
'no-throw-literal': 0, // 允许throw抛出对象格式
'@typescript-eslint/member-ordering': 0 // 禁用TypeScript声明规范
}
}

View File

@ -0,0 +1,3 @@
node_modules/
webpack/
public/

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export default {
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
jsxBracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'lf', // 仅限换行(\n
};

View File

@ -2,23 +2,23 @@
## 一、安装使用
### 安装Node.js
### 安装Nodejs
inula-cli的运行需要依赖Node.js使用前请确保您的电脑已安装Node.js并且版本在16以上。您可以通过在控制台执行以下命令来确认您的版本。
inula-cli的运行需要依赖Nodejs使用前请确保您的电脑已安装Nodejs并且版本在16以上。您可以通过在控制台执行以下命令来确认您的版本。
```shell
```
>node -v
v16.4.0
```
如果您没有安装Node.js或者Node.js版本不满足条件推荐使用nvm工具安装和管理Node.js版本。
如果您没有安装Nodejs或者Nodejs版本不满足条件推荐使用nvm工具安装和管理Nodejs版本。
nvm最新版本下载: [https://github.com/coreybutler/nvm-windows/releases](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fcoreybutler%2Fnvm-windows%2Freleases)
安装nvm之后可以通过如下命令安装Node.js:
安装nvm之后可以通过如下命令安装Nodejs:
```shell
```
>node install 16
>node use 16
@ -28,15 +28,15 @@ nvm最新版本下载: [https://github.com/coreybutler/nvm-windows/releases](htt
### 安装inula-cli
为了方便使用inula-cli的功能推荐您全局安装inula-cli。Node.js安装会自带npm工具用于管理模块您可以直接运行如下命令
为了方便使用inula-cli的功能推荐您全局安装inula-cli。Nodejs安装会自带npm工具用于管理模块您可以直接运行如下命令
```shell
```
>npm install -g inula-cli
```
安装完成后使用inula-cli version命令确认安装是否完成。
```shell
```
>inula-cli version
1.1.0
```
@ -93,7 +93,7 @@ inula-cli支持用户通过项目根目录下的.inula.ts或者.inula.js文件
在配置文件中,您需要默认导出一个配置,以下为一个简单的配置文件示例:
```typescript
```
// .inula.ts
export default {
@ -113,7 +113,7 @@ export default {
对于TypeScript类型我们也提供了类型定义以供开发时自动补全
```typescript
```
// .inula.ts
import { defineConfig } from "inula-cli"
@ -165,7 +165,7 @@ inula-cli的所有功能都围绕插件展开插件可以很方便地让用
内置插件在inula-cli运行时会自动加载用户可以直接调用这些内置命令当前支持的内置插件功能如下
| 序号 | 插件功能 | 触发命令 |
| ---- | :----------------------- | ----------------- |
| ---- | :----------------------- | ------------------- |
| 1、 | 本地开发构建 | inula-cli dev |
| 2、 | 生产构建 | inula-cli build |
| 3、 | 接口mock能力 | inula-cli dev |
@ -180,13 +180,13 @@ inula-cli支持用户集成已发布在npm仓库的插件用户可以按需
安装可以通过npm安装这里以插件@inula/add为例
```shell
```
npm i --save-dev @inula/add
```
如果需要运行插件,需要在配置文件中配置对应的插件路径
```typescript
```
// .inula.ts
export default {
@ -205,7 +205,7 @@ export default {
1、编写命令插件文件这里我们自定义了一个conf命令用于展示当前项目的配置信息。
```typescript
```
// conf.ts
import { API } from "inula-cli";
@ -224,7 +224,7 @@ export default (api: API) => {
2、在配置文件中加入对插件的引用
```typescript
```
// .inula.ts
export default {
@ -232,9 +232,9 @@ export default {
}
```
3、在项目根目录下执行`inula-cli conf`即可触发插件运行。
3、在项目根目录下执行inula-cli conf即可触发插件运行。
```shell
```
> inula-cli conf
current user config is: {
plugins: [ './conf', './showConf' ],
@ -247,7 +247,7 @@ inula-cli提供了hook机制可以让开发者在执行命令时实现事件监
1、使用插件注册hook
```typescript
```
// modifyConfig.ts
import { API } from "inula-cli";
@ -267,7 +267,7 @@ export default (api: API) => {
2、在插件中触发hook
```typescript
```
// conf.ts
import { API } from "inula-cli";
@ -287,7 +287,7 @@ export default (api: API) => {
3、在配置文件中加入插件
```typescript
```
// .inula.ts
export default {
@ -297,7 +297,7 @@ export default {
4、触发命令
```shell
```
> inula-cli conf
current user config is: {
plugins: [ './conf', './showConf' ],
@ -327,7 +327,7 @@ current user config is: {
registerCommand方法允许用户自定义inula-cli的执行命令
```typescript
```
api.registerCommand({
name: string,
description?: string,
@ -343,15 +343,15 @@ registerCommand方法允许用户自定义inula-cli的执行命令
使用示例:
```typescript
```
import { API } from "inula-cli";
export default (api: API) => {
api.registerCommand({
name: "conf",
description: "show user config",
initialState: api.userConfig,
fn: async function (args: any, state: any) {
initalState: api.userConfig,
fn: async function (args: any, config: any) {
console.log("current user config is: ", state);
}
})
@ -374,7 +374,7 @@ api.registerHook({
使用示例:
```typescript
```
import { API } from "inula-cli";
export default (api: API) => {
@ -403,7 +403,7 @@ applyHook(name: string, value?: any})
使用示例:
```typescript
```
import { API } from "inula-cli";
export default (api: API) => {
@ -427,7 +427,7 @@ export default (api: API) => {
inula-cli默认集成生产构建能力用户可以通过在.inula.ts中配置buildConfig字段启用功能。配置示例如下
```typescript
```
// .inula.ts
// 使用webpack构建
@ -460,7 +460,7 @@ export default {
生产构建支持传入多个配置文件路径使用webpack构建还支持配置文件以函数方式导出inula-cli会将配置中的env和args作为参数传递到函数中执行以获取最后的构建配置。
```typescript
```
// webpack.config.js
module.exports = function (env, argv) {
@ -497,7 +497,7 @@ export default {
inula-cli默认也支持项目本地构建用户可以通过在.inula.ts中配置devBuildConfig字段启用功能。配置示例如下
```typescript
```
// .inula.ts
// 使用webpack构建
@ -552,7 +552,7 @@ inula-cli自动将项目根路径里/Mock目录下所有文件视为mock文件
如果您想修改Mock目录位置可以在配置文件中修改mockPath。如果不配置该参数默认使用"./mock"。
```typescript
```
// .inula.ts
export default {
...
@ -569,7 +569,7 @@ export default {
Mock文件需要默认导出一个对象key为"请求方式 接口名",值为接口实现。示例如下:
```typescript
```
export default {
"GET /api/user": (req, res) => {
res.status(200).json("admin")
@ -579,7 +579,7 @@ export default {
如果想要一次mock多个接口可以在导出对象中设置多个key例如
```typescript
```
export default {
"GET /api/user": (req, res) => {
res.status(200).json("admin");
@ -597,7 +597,7 @@ export default {
Mock文件默认导出一个数组数组每一个成员示例如下
```typescript
```
export default [
{
url: '/api/get',
@ -654,7 +654,7 @@ export default [
在框架配置文件中开发者需要配置远端服务器地址以及编写自定义的matcher函数提供给框架
```typescript
```
// .inula.ts
const matcher = (pathname, request) => {
@ -689,7 +689,7 @@ export default {
用户可以在.inula.ts中配置remoteProxy字段开启远端静态接口代理能力完成配置后使用后执行inula-cli proxy启动该功能。
```typescript
```
// .inula.ts
export default {
@ -710,3 +710,6 @@ export default {
}
}
```

View File

@ -1,6 +1,6 @@
{
"name": "inula-cli",
"version": "0.0.4",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
@ -14,11 +14,10 @@
"lib",
"template",
"package.json",
"tsconfig.json",
"README.md"
"tsconfig.json"
],
"author": "",
"license": "MulanPSL2",
"license": "ISC",
"type": "module",
"devDependencies": {
"@types/body-parser": "^1.19.2",

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import webpack from 'webpack';
import { build } from 'vite';
export default (api: any) => {
api.registerCommand({
name: 'build',
description: 'build application for production',
initialState: api.buildConfig,
fn: async function (args: any, state: any) {
switch (api.compileMode) {
case 'webpack':
if (state) {
api.applyHook({ name: 'beforeCompile', args: state });
state.forEach((s: any) => {
webpack(s.config, (err: any, stats: any) => {
if (err || stats.hasErrors()) {
api.logger.error(`Build failed.err: ${err}, stats:${stats}`);
}
});
});
} else {
api.logger.error(`Build failed. Can't find build config.`);
}
break;
case 'vite':
if (state) {
api.applyHook({ name: 'beforeCompile' });
build(state);
} else {
api.logger.error(`Build failed. Can't find build config.`);
}
break;
}
},
});
};

View File

@ -19,6 +19,7 @@ import { createServer } from 'vite';
import { API } from '../../../types/types';
import setupProxy from '../../../utils/setupProxy.js';
export default (api: API) => {
api.registerCommand({
name: 'dev',
@ -45,7 +46,7 @@ export default (api: API) => {
if (api.userConfig.devBuildConfig.devProxy) {
devServerOptions.onBeforeSetupMiddleware = (devServer: WebpackDevServer) => {
setupProxy(devServer.app, api);
};
}
}
api.applyHook({

View File

@ -33,17 +33,15 @@ export default (api: API) => {
args._.shift();
}
if (args._.length === 0) {
api.logger.warn('Can\'t find any generate options.');
api.logger.warn("Can't find any generate options.");
return;
}
switch (args._[0]) {
case 'jest':
{
args._.shift();
const isESM = api.packageJson['type'] === 'module';
await generateJest(args, api.cwd, isESM);
}
break;
default:
}
@ -52,7 +50,7 @@ export default (api: API) => {
};
const generateJest = async (args: yargsParser.Arguments, cwd: string, isESM: boolean) => {
let isTs = false;
let isTs: boolean = false;
if (args['ts']) {
isTs = true;
} else {

View File

@ -25,7 +25,7 @@ export default (api: any) => {
initialState: api.userConfig.remoteProxy,
fn: async function (args: any, state: any) {
if (!state) {
api.logger.error('Invalid proxy config!');
api.logger.error(`Invalid proxy config!`);
return;
}
const app = express();

View File

@ -46,7 +46,7 @@ export default async function run() {
initializeEnv();
if (command === 'version' || command === 'help') {
process.env.INNER_COMMAND = 'true';
process.env.INNER_COMMAND = "true"
}
switch (command) {
@ -61,9 +61,9 @@ export default async function run() {
break;
}
let enableDebug = false;
let enableDebug: boolean = false;
if (process.env.DEBUG === 'true') {
if (process.env.DEBUG === "true") {
enableDebug = true;
}

View File

@ -89,7 +89,7 @@ export default class Config {
getConfigFile(): string | null {
const configFileList: string[] = DEFAULT_CONFIG_FILES.map(f => join(this.cwd, f));
for (const configFile of configFileList) {
for (let configFile of configFileList) {
if (existsSync(configFile)) {
return configFile;
}

View File

@ -46,11 +46,11 @@ export default class Hub {
userConfig: UserConfig = {};
packageJson: PackageJSON;
stage: ServiceStage = ServiceStage.uninitialized;
buildConfig: { name: string; config: Record<string, unknown> }[] = [];
buildConfig: {name:string, config: object}[] = [];
pluginManager: Plugin;
buildConfigPath: BuildConfig[] = [];
devBuildConfig: Record<string, unknown> = {};
compileMode = '';
devBuildConfig: object = {};
compileMode: string = '';
builtInPlugins: string[] = [];
pluginPaths: string[] = [];
devProxy: DevProxy | null = null;
@ -95,7 +95,7 @@ export default class Hub {
this.userConfig = await this.configManager.getUserConfig();
// 设置编译模式
this.setCompileMode();
this.setCompileMode()
// 获取编译配置
await this.analyzeBuildConfig();
@ -135,8 +135,8 @@ export default class Hub {
: this.pluginManager.commands[command];
if (commands === undefined) {
this.logger.error(`Invalid command ${command}`);
return;
this.logger.error(`Invalid command ${command}`)
return
}
const { fn } = commands as ICommand;
@ -150,22 +150,21 @@ export default class Hub {
async analyzeBuildConfig() {
if (this.userConfig.devBuildConfig) {
let { path } = this.userConfig.devBuildConfig;
const { env } = this.userConfig.devBuildConfig;
let { name, path, env } = this.userConfig.devBuildConfig;
path = isAbsolute(path) ? path : join(process.cwd(), path);
if (!existsSync(path)) {
this.logger.warn(`Cant't find dev build config. Path is ${path}`);
return;
}
this.logger.debug(`Find dev build config. Path is ${path}`);
const bc = await loadModule<Record<string, unknown> | ((...args: any[]) => any)>(path);
let bc = await loadModule<object | Function>(path);
if (bc == undefined) {
return;
}
let finalBc = {};
if (typeof bc === 'function') {
finalBc = bc(env);
finalBc = bc(env)
this.devBuildConfig = finalBc;
return;
}
@ -176,54 +175,55 @@ export default class Hub {
}
}
if (!this.userConfig.buildConfig) {
switch (this.compileMode) {
case 'webpack':
this.buildConfigPath.push({ name: 'default', path: './webpack.config.js' });
this.buildConfigPath.push({name:'default', path:'./webpack.config.js'})
break;
case 'vite':
this.buildConfigPath.push({ name: 'default', path: './vite.config.js' });
this.buildConfigPath.push({name:'default', path:'./vite.config.js'})
break;
default:
this.logger.warn(`Unknown compile mode ${this.compileMode}`);
break;
}
} else {
this.userConfig.buildConfig.forEach(userBuildConfig => {
this.userConfig.buildConfig.forEach((userBuildConfig) => {
if (typeof userBuildConfig === 'object') {
this.buildConfigPath.push(userBuildConfig);
}
});
})
}
this.buildConfigPath.forEach(async config => {
let { path } = config;
const { name } = config;
this.buildConfigPath.forEach(async (config) => {
let {name, path} = config;
path = isAbsolute(path) ? path : join(process.cwd(), path);
if (!existsSync(path)) {
this.logger.debug(`Cant't find build config. Path is ${path}`);
return;
}
this.logger.debug(`Find build config. Path is ${path}`);
const bc = await loadModule<Record<string, unknown> | ((...args: any[]) => any)>(path);
let bc = await loadModule<object | Function >(path);
if (bc == undefined) {
return;
}
let finalBc = {};
if (typeof bc === 'function') {
finalBc = bc(config.env);
this.buildConfig.push({ name: name, config: finalBc });
finalBc = bc(config.env)
this.buildConfig.push({name: name, config: finalBc});
return;
}
this.buildConfig.push({ name: name, config: bc });
});
this.buildConfig.push({name: name, config: bc});
})
}
getConfigName(name: string): string {
name = name.replace('webpack.', '');
name = name.replace('.js', '');
name = name.replace('.ts', '');
return name;
return name
}
}

View File

@ -36,7 +36,7 @@ export interface IPlugin {
id: string;
key: string;
path: string;
apply: (...args: any[]) => any;
apply: Function;
}
export default class Plugin {
@ -57,7 +57,7 @@ export default class Plugin {
} = {};
hub: Hub;
logger: Logger;
registerFunction: ((...args: any[]) => any)[] = [];
registerFunction: Function[] = [];
// 解决调用this[props]时ts提示属性未知
[key: string]: any;
@ -110,7 +110,7 @@ export default class Plugin {
});
for (const obj of objs) {
const module: ((...args: any[]) => any) | undefined = await loadModule(obj.path);
const module: Function | undefined = await loadModule(obj.path);
if (module) {
try {
module(obj.api);
@ -135,11 +135,15 @@ export default class Plugin {
return new Proxy(pluginAPI, {
get: (target: PluginAPI, prop: string) => {
if (['userConfig', 'devBuildConfig', 'buildConfig', 'compileMode', 'packageJson', 'cwd'].includes(prop)) {
return typeof this.hub[prop] === 'function' ? this.hub[prop].bind(this.hub) : this.hub[prop];
return typeof this.hub[prop] === 'function'
? this.hub[prop].bind(this.hub)
: this.hub[prop];
}
if (['setStore', 'logger', 'commands'].includes(prop)) {
return typeof this[prop] === 'function' ? this[prop].bind(this) : this[prop];
return typeof this[prop] === 'function'
? this[prop].bind(this)
: this[prop];
}
return target[prop];

View File

@ -55,11 +55,11 @@ export default class PluginAPI {
this.register(hook);
}
registerMethod(fn: (...args: any[]) => any) {
registerMethod(fn: Function) {
this.manager.registerFunction.push(fn);
}
async applyHook(name: string, args?: any) {
async applyHook(name: string, args?: any ) {
const hooks: IHook[] = this.manager.hooks[name] || [];
let config: any = undefined;
for (const hook of hooks) {

View File

@ -19,8 +19,11 @@ import { Logger } from '../utils/logger.js';
import type * as http from 'http';
import type * as express from 'express';
type Request = express.Request;
type Response = express.Response;
interface Request extends express.Request {
}
interface Response extends express.Response {
}
export interface IDep {
[name: string]: string;
@ -37,7 +40,7 @@ export interface IPlugin {
id: string;
key: string;
path: string;
apply: (...args: any[]) => any;
apply: Function;
config?: IPluginConfig;
isPreset?: boolean;
@ -45,7 +48,7 @@ export interface IPlugin {
export interface IPluginConfig {
default?: any;
onChange?: string | ((...args: any[]) => any);
onChange?: string | Function;
}
export interface IHook {
@ -94,8 +97,8 @@ export interface API {
(hook: IHook): void;
};
registerMethod: {
(method: (...args: any[]) => any): void;
};
(method: Function): void;
}
applyHook: {
(opts: applyHookConfig): void;
};
@ -133,22 +136,22 @@ export interface MockConfig {
export interface DevBuildConfig {
name: string;
path: string;
args?: Record<string, unknown>;
env?: Record<string, unknown>;
args?: object;
env?: object;
devProxy?: DevProxy;
}
export interface DevProxy {
target: string;
matcher: (pathname: string, req: Request) => boolean;
matcher: ((pathname: string, req: Request) => boolean);
onProxyRes: (proxyRes: http.IncomingMessage, req: Request, res: Response) => void;
}
export interface BuildConfig {
name: string;
path: string;
args?: Record<string, unknown>;
env?: Record<string, unknown>;
args?: object;
env?: object;
}
export type ExportUserConfig = UserConfig | Promise<UserConfig>;
@ -162,3 +165,4 @@ export interface Arguments {
'--'?: Array<string | number>;
[argName: string]: any;
}

View File

@ -52,7 +52,7 @@ const buildConfig = async (fileName: string, format: 'esm' | 'cjs' = 'esm'): Pro
return {
loader: args.path.endsWith('.ts') ? 'ts' : 'js',
contents: contents,
contents: contents
};
});
},

View File

@ -27,6 +27,7 @@ export async function loadModule<T>(filePath: string): Promise<T | undefined> {
const isTsFile: boolean = filePath.endsWith('ts');
const isJsFile: boolean = filePath.endsWith('js');
let content: T | undefined;
// js文件可以直接通过import引用

View File

@ -15,7 +15,7 @@
import chokidar from 'chokidar';
import bodyParser from 'body-parser';
import { globSync } from 'glob';
import {globSync} from 'glob';
import { join } from 'path';
import { createRequire } from 'module';
@ -38,15 +38,13 @@ function getMocksFile() {
const mockFiles = globSync('**/*.js', {
cwd: mockDir,
});
const ret = mockFiles.reduce((mocks: any, mockFile: string) => {
let ret = mockFiles.reduce((mocks: any, mockFile: string) => {
if (!mockFile.startsWith('_')) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const file = require(join(mockDir, mockFile));
mocks = {
...mocks,
...file,
...require(join(mockDir, mockFile)),
};
console.log('mockFile', file);
console.log('mockFile', require(join(mockDir, mockFile)));
}
return mocks;
@ -56,8 +54,8 @@ function getMocksFile() {
}
function generateRoutes(app: any) {
const mockStartIndex = app._router.stack.length;
let mocks: Mock = {};
let mockStartIndex = app._router.stack.length,
mocks: Mock = {};
try {
mocks = getMocksFile();

View File

@ -18,13 +18,11 @@ import { API } from '../types/types';
export default (app: any, api: API) => {
const { devProxy } = api.userConfig.devBuildConfig;
app.use(
createProxyMiddleware(devProxy.matcher, {
app.use(createProxyMiddleware(devProxy.matcher, {
target: devProxy.target,
secure: false,
changeOrigin: true,
ws: false,
onProxyRes: devProxy.onProxyRes,
})
);
};
onProxyRes: devProxy.onProxyRes
}));
}

View File

@ -16,7 +16,8 @@
import { dirname } from 'path';
import { readFileSync, writeFileSync } from 'fs';
import resolve from 'resolve';
import crequire from 'crequire';
// @ts-ignore
import crequire from 'crequire'
import { createRequire } from 'module';
const require = createRequire(import.meta.url);

View File

@ -11,15 +11,8 @@
"moduleResolution": "node",
"esModuleInterop": true,
},
"include": [
"src/**/*",
"./externals.d.ts"
],
"exclude": [
"node_modules",
"**/*.spec.ts",
"./src/template/**/*"
],
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts", "./src/template/**/*"],
"ts-node": {
"esm": true,
},

View File

@ -1,21 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/*
*/
declare var isDev: boolean;
declare var isTest: boolean;
declare const __VERSION__: string;

View File

@ -1,54 +0,0 @@
{
"name": "inula-dev-tools",
"version": "0.0.1",
"description": "Inula chrome dev extension",
"main": "",
"scripts": {
"build": "webpack --config ./webpack.config.js",
"watch": "webpack --config ./webpack.config.js --watch",
"build-dev": "webpack --config ./webpack.dev.js",
"start": "npm run build && webpack serve --config ./webpack.dev.js",
"test": "jest"
},
"keywords": ["inula-dev-tools"],
"license": "MulanPSL2",
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-transform-react-jsx-source": "^7.18.6",
"@babel/preset-env": "^7.21.1",
"@babel/preset-react": "^7.12.1",
"@babel/preset-typescript": "^7.16.7",
"@types/chrome": "0.0.190",
"@types/jest": "27.4.1",
"@typescript-eslint/eslint-plugin": "4.8.0",
"@typescript-eslint/parser": "4.8.0",
"babel-jest": "^27.5.1",
"eslint": "7.13.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-jest": "^22.15.0",
"eslint-plugin-no-for-of-loops": "^1.0.0",
"eslint-plugin-no-function-declare-after-return": "^1.0.0",
"eslint-plugin-react": "7.14.3",
"babel-loader": "8.1.0",
"css-loader": "^6.7.1",
"html-webpack-plugin": "5.5.0",
"jest": "27.5.1",
"less": "4.1.2",
"less-loader": "10.2.0",
"style-loader": "^3.3.1",
"ts-jest": "27.1.4",
"ts-loader": "^9.3.1",
"typescript": "4.2.3",
"webpack": "5.70.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"openinula": "^0.1.1",
"flatted-object": "^0.1.2",
"json-decycle": "^2.0.1",
"lodash": "^4.17.21",
"object-assign": "^4.1.1"
}
}

View File

@ -1,80 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { checkMessage, packagePayload, changeSource } from '../utils/transferUtils';
import { RequestAllVNodeTreeInfos, InitDevToolPageConnection, DevToolBackground } from '../utils/constants';
import { DevToolPanel, DevToolContentScript } from '../utils/constants';
// 多个页面 tab 页共享一个 background需要建立连接池给每个 tab 建立连接
export const connections = {};
// panel 代码中调用 let backgroundPageConnection = chrome.runtime.connect({...}) 会触发回调函数
chrome.runtime.onConnect.addListener(function (port) {
function extensionListener(message) {
const isInulaMessage = checkMessage(message, DevToolPanel);
if (isInulaMessage) {
const { payload } = message;
// tabId 值指当前浏览器分配给 web_page 的 id 值。是 panel 页面查询得到,指定向页面发送消息
const { type, data, tabId } = payload;
let passMessage;
if (type === InitDevToolPageConnection) {
// 记录 panel 所在 tab 页的 tabId如果已经记录了覆盖原有 port因为原有 port 可能关闭了
// 可能这次是 panel 发起的重新建立请求
connections[tabId] = port;
passMessage = packagePayload({ type: RequestAllVNodeTreeInfos }, DevToolBackground);
} else {
passMessage = packagePayload({ type, data }, DevToolBackground);
}
chrome.tabs.sendMessage(tabId, passMessage);
}
}
// 监听 dev tools 页面发送的消息
port.onMessage.addListener(extensionListener);
port.onDisconnect.addListener(function (port) {
port.onMessage.removeListener(extensionListener);
const tabs = Object.keys(connections);
for (let i = 0, len = tabs.length; i < len; i++) {
if (connections[tabs[i]] == port) {
delete connections[tabs[i]];
break;
}
}
});
});
// 监听来自 content script 的消息,并将消息发送给对应的 dev tools 页面
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
// 来自 content script 的消息需要预先设置 sender.tab
if (sender.tab) {
const tabId = sender.tab.id;
if (message.payload.type.startsWith('inulax')) {
return;
}
if (tabId && tabId in connections && checkMessage(message, DevToolContentScript)) {
changeSource(message, DevToolBackground);
connections[tabId].postMessage(message);
} else {
// TODO: 如果查询失败,发送 chrome message ,请求 panel 主动建立连接
console.log('Tab is not found in connection');
}
} else {
console.log('sender.tab is not defined');
}
// 需要返回消息告知完成通知,否则会出现报错 message port closed before a response was received
sendResponse({ status: 'ok' });
});

View File

@ -1,283 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { DevToolBackground, DevToolContentScript, DevToolPanel } from '../utils/constants';
import { connections } from './index';
import { packagePayload } from '../utils/transferUtils';
// 监听来自 content script 的消息,并将消息发送给对应的 dev tools page
const eventsPerTab = {};
const storesPerTab = {};
const observedComponents = {};
const eventPersistencePerTab = {};
let idGenerator = 1;
// 当 tab 重新加载,需要对该 tab 所监听的 stores 进行重置
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo) {
if (changeInfo.status === 'loading') {
if (!eventPersistencePerTab[tabId]) {
eventsPerTab[tabId] = [];
}
storesPerTab[tabId] = [];
}
});
function sendTo(connectionId, message) {
if (connections[connectionId]) {
connections[connectionId].postMessage(message);
}
}
function requestObservedComponents(tabId) {
setTimeout(() => {
chrome.tabs.sendMessage(
tabId,
packagePayload(
{
type: 'inulax request observed components',
data: {},
},
'dev tool background'
)
);
}, 1);
}
function executeAction(tabId, storeId, action, params) {
chrome.tabs.sendMessage(
tabId,
packagePayload(
{
type: 'inulax execute action',
data: {
action,
storeId,
params,
},
},
'dev tool background'
)
);
}
function queueAction(tabId, storeId, action, params) {
chrome.tabs.sendMessage(
tabId,
packagePayload(
{
type: 'inulax queue action',
data: {
action,
storeId,
params,
},
},
'sev tool background'
)
);
}
function getObservedComponents(storeId, tabId) {
if (!observedComponents[tabId]) {
observedComponents[tabId] = {};
}
if (!observedComponents[tabId][storeId]) {
return [];
}
return observedComponents[tabId][storeId];
}
// 来自 content script 的消息
chrome.runtime.onMessage.addListener(function (message, sender) {
if (message.payload.type.startsWith('inulax')) {
console.log('inulaXHandler message from content script', {
payload: { ...message.payload },
});
if (message.from === DevToolContentScript && sender.tab?.id) {
if (message.payload.type === 'inulax observed components') {
observedComponents[sender.tab.id] = message.payload.data;
sendTo(sender.tab.id, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax observed components',
data: message.payload.data,
},
from: DevToolBackground,
});
return;
}
requestObservedComponents(sender.tab.id);
// content script -> inulaXHandler
if (!eventsPerTab[sender.tab.id]) {
eventsPerTab[sender.tab.id] = [];
}
eventsPerTab[sender.tab.id].push({
id: idGenerator++,
timestamp: Date.now(),
message: message.payload,
});
sendTo(sender.tab.id, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax events',
events: eventsPerTab[sender.tab.id],
},
from: DevToolBackground,
});
// 如果当前 tab 没有 store data则初始化
if (!storesPerTab[sender.tab.id]) {
storesPerTab[sender.tab.id] = [];
}
let found = false;
storesPerTab[sender.tab.id]?.some((store, index) => {
if (store.id === message.payload.data.store.id) {
found = true;
storesPerTab[sender.tab!.id!][index] = message.payload.data.store;
requestObservedComponents(sender.tab?.id);
return true;
}
return false;
});
if (!found) {
const tabId = sender.tab.id;
if (!storesPerTab[tabId]) {
storesPerTab[tabId] = [];
}
storesPerTab[tabId].push(message.payload.data.store);
sendTo(tabId, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax stores',
stores:
storesPerTab[tabId]?.map(store => {
// 连接被监测的组件
requestObservedComponents(tabId);
const observedComponents = getObservedComponents(store, tabId);
return { ...store, observedComponents };
}) || [],
newStore: message.payload.data.store.id,
},
from: DevToolBackground,
});
return;
}
sendTo(sender.tab.id, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax stores',
stores:
storesPerTab[sender.tab.id]?.map(store => {
// 连接被监测的组件
const observedComponents = getObservedComponents(store, sender.tab?.id);
return { ...store, observedComponents };
}) || [],
updated: message.payload.data.store.id,
},
from: DevToolBackground,
});
requestObservedComponents(message.payload.tabId);
}
if (message.from === DevToolPanel) {
// panel -> inulaXHandler
if (message.payload.type === 'inulax run action') {
executeAction(message.payload.tabId, message.payload.storeId, message.payload.action, message.payload.args);
return;
}
if (message.payload.type === 'inulax change state') {
chrome.tabs.sendMessage(message.payload.tabId, packagePayload(message.payload, 'dev tool background'));
return;
}
if (message.payload.type === 'inulax queue action') {
queueAction(message.payload.tabId, message.payload.storeId, message.payload.action, message.payload.args);
return;
}
if (message.payload.type === 'inulax resetEvents') {
eventsPerTab[message.payload.tabId] = [];
sendTo(message.payload.tabId, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax events',
events: eventsPerTab[message.payload.tabId],
},
from: DevToolBackground,
});
return;
}
if (message.payload.type === 'inula setPersistent') {
const { tabId, persistent } = message.payload;
eventPersistencePerTab[tabId] = persistent;
return;
}
if (message.payload.type === 'inulax getPersistence') {
sendTo(message.payload.tabId, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax persistence',
persistent: !!eventPersistencePerTab[message.payload.tabId],
},
from: DevToolBackground,
});
return;
}
if (message.payload.type === 'inulax getEvents') {
if (!eventsPerTab[message.payload.tabId]) {
eventsPerTab[message.payload.tabId] = [];
}
sendTo(message.payload.tabId, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax events',
events: eventsPerTab[message.payload.tabId],
},
from: DevToolBackground,
});
return;
}
if (message.payload.type === 'inulax getStores') {
sendTo(message.payload.tabId, {
type: 'INULA_DEV_TOOLS',
payload: {
type: 'inulax stores',
stores:
storesPerTab[message.payload.tabId]?.map(store => {
requestObservedComponents(message.payload.tabId);
const observedComponents = getObservedComponents(store.id, message.payload.tabId);
return { ...store, observedComponents };
}) || [],
},
from: DevToolBackground,
});
return;
}
}
}
});

View File

@ -1,279 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
@import 'assets.less';
.infoContainer {
display: flex;
flex-direction: column;
height: 100%;
.button {
border: none;
padding: 0;
border-radius: 0.25rem;
flex: 0 0 auto;
cursor: pointer;
color: #5f6673;
}
.button :hover {
color: #23272f;
}
.componentInfoHead {
flex: 0 0 @top-height;
display: flex;
align-items: center;
border-bottom: @divider-style;
.name {
flex: 1 1 auto;
padding: 0 1rem 0 1rem;
.text {
display: block;
}
}
.eye {
flex: 0 0 1rem;
cursor: pointer;
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem 0.25rem 0.25rem;
}
.debug {
flex: 0 0 1rem;
cursor: pointer;
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem 0.25rem 0;
}
.location {
flex: 0 0 1rem;
cursor: pointer;
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem 0.25rem 0;
}
}
.componentInfoMain {
overflow-y: auto;
> :last-child {
border-bottom: unset;
}
> div {
border-bottom: @divider-style;
}
.attrContainer {
flex: 0 0;
.attrHead {
display: flex;
flex-direction: row;
align-items: center;
padding: 0.5rem 0.5rem 0 0.5rem;
.attrType {
flex: 1 1 0;
}
.attrCopy {
flex: 0 0 1rem;
padding-right: 1rem;
}
}
.attrDetail {
padding-bottom: 0.5rem;
.attrArrow {
color: @arrow-color;
width: 12px;
display: inline-block;
}
.attrName {
margin-top: 1px;
color: @attr-name-color;
font-family: @attr-name-font-family;
}
.colon {
margin-top: 1px;
transform: translateY(-8%);
margin-right: 0.5rem;
}
.info {
display: flex;
&:hover {
.operation {
visibility: visible;
.operationIcon :hover {
border: none;
border-radius: 5px;
background-color: lightskyblue;
}
}
}
}
.attrValue {
width: 26rem;
height: 1rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo,
Courier, monospace;
&:focus {
color: unset;
background-color: #f0f0f0;
}
}
.attrValue[data-type='string'] {
color: #009906;
}
.attrValue[data-type='function'] {
color: royalblue;
}
.attrValue[data-type='number'] {
color: #ff5722;
}
.attrValue[data-type='boolean'] {
color: #03a9f4;
}
.operation {
cursor: pointer;
visibility: hidden;
}
.checkBox {
margin: 2px 3px 0 auto;
justify-content: flex-end;
}
}
}
.dropdown.active {
display: unset;
top: var(--content-top);
left: var(--content-left);
position: absolute;
ul {
margin-block-start: 0;
padding-inline-start: 0;
li {
padding: 10px;
border-top: 1px lighten(#333, 2%) solid;
height: auto;
overflow: auto;
opacity: 1;
}
}
}
.dropdown {
display: none;
ul {
display: block;
position: relative;
list-style: none;
}
li {
padding: 0 10px;
background: darken(#333, 2%);
color: darken(#EEE, 40%);
text-align: left;
border: 0;
width: 100%;
height: 0;
overflow: hidden;
cursor: pointer;
opacity: 0;
transition-property: all, background-color;
transition-duration: 0.2s, 0.4s;
&:hover, &.selected {
background-color: darken(#333, 10%);
}
&:active {
background: #03a9f4;
}
&:first-child {
border-radius: 5px 5px 0 0;
}
&:last-child {
border-radius: 0 0 5px 5px;
}
&:before {
margin-top: -2px;
margin-right: 10px;
display: inline-block;
border-radius: 5px;
vertical-align: middle;
width: 16px;
height: 16px;
}
&:nth-child(1) {
&:before {
content: url('../svgs/copy.svg');
}
}
&:nth-child(2) {
&:before {
content: url('../svgs/storage.svg');
}
}
}
}
}
.parentsInfo {
flex: 1 1 0;
.parentName {
padding: 0.5rem 0.5rem 0 0.5rem;
}
.parent {
margin-left: 1.4rem;
display: block;
cursor: pointer;
text-align: left;
color: @component-name-color;
&:hover {
background-color: @select-color;
}
}
}
}

View File

@ -1,440 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import styles from './ComponentInfo.less';
import Eye from '../svgs/Eye';
import Debug from '../svgs/Debug';
import Location from '../svgs/Location';
import Triangle from '../svgs/Triangle';
import { memo, useContext, useEffect, useState, useRef, useMemo, createRef } from 'openinula';
import { IData } from './VTree';
import { buildAttrModifyData, IAttr } from '../parser/parseAttr';
import { postMessageToBackground } from '../panelConnection';
import { CopyToConsole, InspectDom, LogComponentData, ModifyAttrs, StorageValue } from '../utils/constants';
import type { Source } from '../../../inula/src/renderer/Types';
import ViewSourceContext from '../utils/ViewSource';
import PickElementContext from '../utils/PickElement';
import Operation from '../svgs/Operation';
type IComponentInfo = {
name: string;
attrs: {
parsedProps?: IAttr[];
parsedState?: IAttr[];
parsedHooks?: IAttr[];
};
parents: IData[];
id: number;
source?: Source;
onClickParent: (item: IData) => void;
};
const ComponentAttr = memo(function ComponentAttr({
attrsName,
attrsType,
attrs,
id,
dropdownRef,
}: {
attrsName: string;
attrsType: string;
attrs: IAttr[];
id: number;
dropdownRef: null | HTMLElement;
}) {
const [editableAttrs, setEditableAttrs] = useState(attrs);
const [expandNodes, setExpandNodes] = useState([]);
useEffect(() => {
setEditableAttrs(attrs);
}, [attrs]);
const handleCollapse = (item: IAttr) => {
const nodes = [...expandNodes];
const expandItem = `${item.name}_${editableAttrs.indexOf(item)}`;
const i = nodes.indexOf(expandItem);
if (i === -1) {
nodes.push(expandItem);
} else {
nodes.splice(i, 1);
}
setExpandNodes(nodes);
};
// props 展示的 key: value 中的 value 值
const getShowName = item => {
let retStr;
if (item === undefined) {
retStr = String(item);
} else if (typeof item === 'number') {
retStr = item;
} else if (typeof item === 'string') {
retStr = item.endsWith('>') ? `<${item}` : item;
} else {
retStr = `"${item}"`;
}
return retStr;
};
/**
* props hooks VNode
*
* @param {Array<IAttr>} editableAttrs props hooks
* @param {number} index editableAttrs
* @param {string} attrsType props hooks
* @return {Array} vNode
*/
const getPath = (editableAttrs: IAttr[], index: number, attrsType: string): Array<string | number> => {
const path: Array<string | number> = [];
let local = editableAttrs[index].indentation;
if (local === 1) {
path.push(attrsType === 'Hooks' ? editableAttrs[index].hIndex : editableAttrs[index].name);
} else {
let location = local;
let id = index;
while (location > 0) {
// local === 1 时处于 vNode.hooks 的子元素最外层
if (location < local || id === index || local === 1) {
if (local === 1) {
attrsType === 'Hooks'
? path.unshift(editableAttrs[id + 1].hIndex, 'state')
: path.unshift(editableAttrs[id + 1].name);
break;
} else {
if (editableAttrs[id]?.indentation === 1) {
if (editableAttrs[id]?.name === 'State') {
path.unshift('stateValue');
}
if (editableAttrs[id]?.name === 'Ref') {
path.unshift('current');
}
} else {
path.unshift(editableAttrs[id].name);
}
}
// 跳过同级
local = location;
}
location = id >= 1 ? editableAttrs[id - 1].indentation : -1;
id = -1;
}
}
return path;
};
const showAttr = [];
let currentIndentation = null;
// 为每一行数据添加一个 ref
const refsById = useMemo(() => {
const refs = {};
editableAttrs.forEach((item, index) => {
refs[index] = createRef();
});
return refs;
}, [editableAttrs]);
editableAttrs.forEach((item, index) => {
const operationRef = refsById[index];
const indentation = item.indentation;
if (currentIndentation !== null) {
if (indentation > currentIndentation) {
return;
} else {
currentIndentation = null;
}
}
const nextItem = editableAttrs[index + 1];
const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false;
const isCollapsed = !expandNodes.includes(`${item.name}_${index}`);
// 按钮点击事件
const operationClick = (e: Event, operationRef: any) => {
// 防止点击按钮触发展开或者合起数据
e.stopPropagation();
if (operationRef.current) {
const operationRect = operationRef.current.getBoundingClientRect();
// 19.2 为图标按钮高度85 为弹框高度的一半
dropdownRef.style.setProperty('--content-top', `${operationRect.top + 19.2}px`);
dropdownRef.style.setProperty('--content-left', `${operationRect.left - 85}px`);
}
dropdownRef.classList.toggle(styles['active']);
const attrInfo = {
id: { id },
itemName: item.name,
attrsName: attrsName,
path: getPath(editableAttrs, index, attrsName),
};
(dropdownRef as any).attrInfo = attrInfo;
console.log(dropdownRef);
};
showAttr.push(
<div
className={styles.info}
style={{ paddingLeft: item.indentation * 10 }}
key={index}
onclick={() => handleCollapse(item)}
>
<span className={styles.attrArrow}>{hasChild && <Triangle director={isCollapsed ? 'right' : 'down'} />}</span>
<span className={styles.attrName}>{`${item.name}`}</span>
<div className={styles.colon}>{':'}</div>
{item.type === 'string' || item.type === 'number' || item.type === 'undefined' || item.type === 'null' ? (
<>
<input
value={getShowName(item.value)}
data-type={item.type}
className={styles.attrValue}
onChange={event => {
const nextAttrs = [...editableAttrs];
const nextItem = { ...item };
nextItem.value = event.target.value;
nextAttrs[index] = nextItem;
setEditableAttrs(nextAttrs);
}}
onKeyUp={event => {
const value = (event.target as HTMLInputElement).value;
if (event.key === 'Enter') {
if (isDev) {
console.log('post attr change', value);
} else {
const data = buildAttrModifyData(attrsType, attrs, value, item, index, id);
postMessageToBackground(ModifyAttrs, data);
}
}
}}
/>
<div className={styles.operation} ref={operationRef}>
<span className={styles.operationIcon} onclick={event => operationClick(event, operationRef)}>
<Operation />
</span>
</div>
</>
) : item.type === 'boolean' ? (
<>
<span data-type={item.type} className={styles.attrValue}>
{item.value.toString()}
</span>
<input
type={'checkbox'}
checked={item.value}
className={styles.checkBox}
onChange={event => {
const nextAttrs = [...editableAttrs];
const nextItem = { ...item };
nextItem.value = event.target.checked;
nextAttrs[index] = nextItem;
setEditableAttrs(nextAttrs);
if (!isDev) {
const data = buildAttrModifyData(attrsType, attrs, nextItem.value, item, index, id);
postMessageToBackground(ModifyAttrs, data);
}
}}
/>
</>
) : (
<>
<span data-type={item.type} className={styles.attrValue}>
{item.value}
</span>
<div className={styles.operation} ref={operationRef}>
<span className={styles.operationIcon} onClick={event => operationClick(event, operationRef)}>
<Operation />
</span>
</div>
</>
)}
</div>
);
if (isCollapsed) {
currentIndentation = indentation;
}
});
return (
<div className={styles.attrContainer}>
<div className={styles.attrHead}>
<span className={styles.attrType}>{attrsName}</span>
</div>
<div className={styles.attrDetail}>{showAttr}</div>
</div>
);
});
function ComponentInfo({ name, attrs, parents, id, source, onClickParent }: IComponentInfo) {
const view = useContext(ViewSourceContext) as any;
const viewSource = view.viewSourceFunction.viewSource;
const pick = useContext(PickElementContext) as any;
const inspectVNode = pick.pickElementFunction.inspectVNode;
const dropdownRef = useRef<null | HTMLElement>(null);
const doViewSource = (id: number) => {
postMessageToBackground(InspectDom, { id });
setTimeout(function () {
inspectVNode();
}, 100);
};
const doInspectDom = (id: number) => {
postMessageToBackground(InspectDom, { id });
setTimeout(function () {
inspectVNode();
}, 100);
};
const sourceFormatted = (fileName: string, lineNumber: number) => {
const pathWithoutLastName = /^(.*)[\\/]/;
let realName = fileName.replace(pathWithoutLastName, '');
if (/^index\./.test(realName)) {
const fileNameMatch = fileName.match(pathWithoutLastName);
if (fileNameMatch) {
const pathBeforeName = fileNameMatch[1];
if (pathBeforeName) {
const folderName = pathBeforeName.replace(pathWithoutLastName, '');
realName = folderName + '/' + realName;
}
}
}
return `${realName}:${lineNumber}`;
};
const copyToConsole = (itemName: string | number, attrsName: string, path: Array<string | number>) => {
postMessageToBackground(CopyToConsole, { id, itemName, attrsName, path });
dropdownRef.current.classList.toggle(styles['active']);
};
const storeVariable = (attrsName: string, path: Array<string | number>) => {
postMessageToBackground(StorageValue, { id, attrsName, path });
dropdownRef.current.classList.toggle(styles['active']);
};
return (
<div className={styles.infoContainer}>
<div className={styles.componentInfoHead}>
{name && (
<>
<div className={styles.name}>
<div className={styles.text}>{name}</div>
</div>
<button className={styles.button}>
<span
className={styles.eye}
title={'Inspect dom element'}
onClick={() => {
doInspectDom(id);
}}
>
<Eye />
</span>
</button>
<button className={styles.button} disabled={false}>
<span
className={styles.location}
onClick={() => {
doViewSource(id);
}}
title={'View source for this element'}
>
<Location />
</span>
</button>
<button className={styles.button}>
<span
className={styles.debug}
title={'Log this component data'}
onClick={() => {
postMessageToBackground(LogComponentData, id);
}}
>
<Debug />
</span>
</button>
</>
)}
</div>
<div className={styles.componentInfoMain}>
{Object.keys(attrs).map(attrsType => {
const parsedAttrs = attrs[attrsType];
if (parsedAttrs && parsedAttrs.length !== 0) {
const attrsName = attrsType.slice(6); // parsedState => State
return (
<ComponentAttr
attrsName={attrsName}
attrsType={attrsType}
attrs={parsedAttrs}
id={id}
dropdownRef={dropdownRef.current}
/>
);
}
return null;
})}
<div className={styles.parentsInfo}>
{name && (
<div>
<div className={styles.parentName}>Parents</div>
{parents.map(item => (
<button className={styles.parent} onClick={() => onClickParent(item)}>
{`<${item.name.itemName}>`}
</button>
))}
</div>
)}
</div>
<div className={styles.parentsInfo}>
{source && (
<>
<div>source: {''}</div>
<div style={{ marginLeft: '1rem' }}>{sourceFormatted(source.fileName, source.lineNumber)}</div>
</>
)}
</div>
<div ref={dropdownRef} className={styles.dropdown}>
<ul>
<li
onClick={() =>
copyToConsole(
(dropdownRef.current as any).attrInfo.itemName,
(dropdownRef.current as any).attrInfo.attrsName,
(dropdownRef.current as any).attrInfo.path
)
}
>
<b>Copy value to console</b>
</li>
<li
onClick={() =>
storeVariable(
(dropdownRef.current as any).attrInfo.attrsName,
(dropdownRef.current as any).attrInfo.path
)
}
>
<b>Store as global variable</b>
</li>
</ul>
</div>
</div>
</div>
);
}
export default memo(ComponentInfo);

View File

@ -1,44 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import styles from './Search.less';
interface SearchProps {
onKeyUp: () => void;
onChange: (event: any) => void;
value: string;
}
export default function Search(props: SearchProps) {
const { onChange, value, onKeyUp } = props;
const handleChange = event => {
onChange(event.target.value);
};
const handleKeyUp = event => {
if (event.key === 'Enter') {
onKeyUp();
}
};
return (
<input
onkeyup={handleKeyUp}
onchange={handleChange}
className={styles.search}
value={value}
placeholder="Search Component"
/>
);
}

View File

@ -1,48 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { useEffect, useState, useRef } from 'openinula';
import { addResizeListener, removeResizeListener } from './resizeEvent';
export function SizeObserver(props) {
const { children, ...rest } = props;
const containerRef = useRef<HTMLDivElement>();
const [size, setSize] = useState<{ width: number; height: number }>();
const notifyChild = element => {
setSize({
width: element.offsetWidth,
height: element.offsetHeight,
});
};
useEffect(() => {
const element = containerRef.current!;
setSize({
width: element.offsetWidth,
height: element.offsetHeight,
});
addResizeListener(element, notifyChild);
return () => {
removeResizeListener(element, notifyChild);
};
}, []);
const myChild = size ? children(size.width, size.height) : null;
return (
<div ref={containerRef} {...rest}>
{myChild}
</div>
);
}

View File

@ -1,79 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/**
*
*
*/
export default class ItemMap<T> {
// 不要用 indexOf 进行位置计算,它会遍历数组
private lastRenderItemToIndexMap: Map<T | undefined, number>;
constructor() {
this.lastRenderItemToIndexMap = new Map();
}
public calculateReSortedItems(nextItems: T[]): (T | undefined)[] {
if (this.lastRenderItemToIndexMap.size === 0) {
nextItems.forEach((item, index) => {
this.lastRenderItemToIndexMap.set(item, index);
});
return nextItems;
}
const nextRenderItems: (T | undefined)[] = [];
const length = nextItems.length;
const nextRenderItemToIndexMap = new Map<T | undefined, number>();
const addItems: T[] = [];
// 遍历 nextItems 找到复用 item 和新增 item
nextItems.forEach(item => {
const lastIndex = this.lastRenderItemToIndexMap.get(item);
// 处理旧 item
if (lastIndex !== undefined) {
// 使用上一次的位置
nextRenderItems[lastIndex] = item;
// 记录位置
nextRenderItemToIndexMap.set(item, lastIndex);
} else {
// 记录新的 item
addItems.push(item);
}
});
// 处理新增 item翻转数组后面在调用 pop 时拿到的时最后一个,以确保顺序
addItems.reverse();
for (let i = 0; i < length; i++) {
// 优先将新增 item 放置在空位置上
if (!nextRenderItems[i]) {
const item = addItems.pop();
nextRenderItems[i] = item;
nextRenderItemToIndexMap.set(item, i);
}
}
// 剩余新 item 补在数组后面
for (let i = addItems.length - 1; i >= 0; i--) {
const item = addItems[i];
nextRenderItemToIndexMap.set(item, nextRenderItems.length);
nextRenderItems.push(item);
}
// 如果 nextRenderItems 中存在空 indexnextItems 已经耗尽,不用处理
// 确保新旧数组中 item 的 index 值不会发生变化
this.lastRenderItemToIndexMap = nextRenderItemToIndexMap;
return nextRenderItems;
}
}

View File

@ -1,135 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/**
*
* data scrollToItem
*/
import { useState, useRef, useEffect, useMemo } from 'openinula';
import styles from './VList.less';
import ItemMap from './ItemMap';
import { debounceFunc } from '../../utils/publicUtil';
interface IProps<T extends { id: number | string }> {
data: T[];
maxDeep: number;
width: number; // 暂时未用到,当需要支持横向滚动时使用
height: number; // VList 的高度
children?: any; // inula 组件
itemHeight: number;
scrollToItem?: T; // 滚动到指定项位置,如果该项在可见区域内,不滚动,如果补在,则滚动到中间位置
onRendered: (renderInfo: RenderInfoType<T>) => void;
filter?: (data: T) => boolean; // false 表示该行不显示
}
export type RenderInfoType<T> = {
visibleItems: T[];
};
function parseTranslate<T>(data: T[], itemHeight: number) {
const map = new Map<T, number>();
data.forEach((item, index) => {
map.set(item, index * itemHeight);
});
return map;
}
export function VList<T extends { id: number | string }>(props: IProps<T>) {
const { data, maxDeep, height, width, children, itemHeight, scrollToItem, onRendered } = props;
const [scrollTop, setScrollTop] = useState(Math.max(data.indexOf(scrollToItem!), 0) * itemHeight);
const renderInfoRef: { current: RenderInfoType<T> } = useRef({
visibleItems: [],
});
const [indentationLength, setIndentationLength] = useState(0);
// 每个 item 的 translateY 值固定不变
const itemToTranslateYMap = useMemo(() => parseTranslate(data, itemHeight), [data]);
const itemIndexMap = useMemo(() => new ItemMap<T>(), []);
const containerRef = useRef<HTMLDivElement>();
useEffect(() => {
onRendered(renderInfoRef.current);
});
useEffect(() => {
debounceFunc(() => setIndentationLength(Math.min(12, Math.round(width / (2 * maxDeep)))));
}, [width]);
useEffect(() => {
if (scrollToItem) {
const renderInfo = renderInfoRef.current;
// 在显示区域,不滚动
if (!renderInfo.visibleItems.includes(scrollToItem)) {
const index = data.indexOf(scrollToItem);
// 显示在页面中间
const top = Math.max(index * itemHeight - height / 2, 0);
containerRef.current?.scrollTo({ top: top });
}
}
}, [scrollToItem]);
// 滚动事件会频繁触发,通过框架提供的代理会有大量计算寻找 dom 元素,直接绑定到原生事件上减少计算量
useEffect(() => {
const handleScroll = event => {
const scrollTop = event.target.scrollTop;
setScrollTop(scrollTop);
};
const container = containerRef.current;
container?.addEventListener('scroll', handleScroll);
return () => {
container?.removeEventListener('scroll', handleScroll);
};
}, []);
const totalHeight = itemHeight * data.length;
const maxIndex = data.length; // slice 截取渲染 item 数组时最大位置不能超过自然长度
// 第一个可见 item index
const firstInViewItemIndex = Math.floor(scrollTop / itemHeight);
// 可见区域前最多冗余 4 个 item
const startRenderIndex = Math.max(firstInViewItemIndex - 4, 0); // index 不能小于 0
// 最多可见数量
const maxInViewCount = Math.floor(height / itemHeight);
// 最后可见 item index
const lastInViewIndex = Math.min(firstInViewItemIndex + maxInViewCount, maxIndex);
// 记录可见 items
renderInfoRef.current.visibleItems = data.slice(firstInViewItemIndex, lastInViewIndex);
// 可见区域后冗余 4 个 item
const lastRenderIndex = Math.min(lastInViewIndex + 4, maxIndex);
// 需要渲染的 item
const renderItems = data.slice(startRenderIndex, lastRenderIndex);
// 给 items 重新排序,确保未移出渲染数组的 item 在新的渲染数组中位置不变,这样在 diff 算法比较后,这部分的 dom 不会发生更新
const nextRenderList = itemIndexMap.calculateReSortedItems(renderItems);
const list = nextRenderList.map((item, index) => {
if (!item) {
return null;
}
return (
<div
key={String(index)} // 固定 key 值,这样就只会更新 translateY 的值
className={styles.item}
style={{ transform: `translateY(${itemToTranslateYMap.get(item)}px)` }}
>
{children(item, indentationLength)}
</div>
);
});
return (
<div ref={containerRef} className={styles.container}>
{list}
<div style={{ marginTop: totalHeight }}></div>
</div>
);
}

View File

@ -1,17 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export { VList } from './VList';
export type { RenderInfoType } from './VList';

View File

@ -1,78 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
@import 'assets.less';
.treeContainer {
height: 100%;
.treeItem {
width: 100%;
position: absolute;
line-height: 1.125rem;
align-items: center;
display: inline-flex;
&:hover {
background-color: @select-color;
}
.treeIcon {
color: @arrow-color;
display: inline-block;
width: 12px;
padding-left: 0.5rem;
}
.componentName {
white-space: nowrap;
color: @component-name-color;
display: inline-flex;
}
.componentKeyName {
color: @component-key-color;
}
.componentKeyValue {
color: @component-key-value-color;
max-width: 100px;
overflow-x: hidden;
text-overflow: ellipsis;
display: inline-flex;
white-space: nowrap;
}
}
.selectedItemChild {
background-color: @select-item-child-color;
}
.select {
background-color: @select-color;
}
}
.Badge {
display: inline-block;
background-color: rgba(0, 0, 0, 0.1);
color: #000000;
padding: 0 0.25rem;
line-height: normal;
border-radius: 0.125rem;
margin-left: 0.25rem;
font-family: @attr-name-font-family;
font-size: 9px;
height: 1rem;
}

View File

@ -1,318 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { useState, useEffect, useCallback, memo } from 'openinula';
import styles from './VTree.less';
import Triangle from '../svgs/Triangle';
import { createRegExp } from '../utils/regExpUtil';
import { SizeObserver } from './SizeObserver';
import { RenderInfoType, VList } from './VList';
import { postMessageToBackground } from '../panelConnection';
import { Highlight, RemoveHighlight } from '../utils/constants';
import { NameObj } from '../parser/parseVNode';
export interface IData {
id: number;
name: NameObj;
indentation: number;
userKey: string;
}
interface IItem {
indentationLength: number;
hasChild: boolean;
onCollapse: (data: IData) => void;
onClick: (id: IData) => void;
onMouseEnter: (id: IData) => void;
onMouseLeave: (id: IData) => void;
isCollapsed: boolean;
isSelect: boolean;
highlightValue: string;
data: IData;
isSelectedItemChild: boolean;
}
function Item(props: IItem) {
const {
hasChild,
onCollapse,
isCollapsed,
data,
onClick,
indentationLength,
onMouseEnter,
onMouseLeave,
isSelect,
highlightValue = '',
isSelectedItemChild,
} = props;
const { name, userKey, indentation } = data;
const isShowKey = userKey !== '';
const showIcon = hasChild ? <Triangle director={isCollapsed ? 'right' : 'down'} /> : '';
const handleClickCollapse = () => {
onCollapse(data);
};
const handleClick = () => {
onClick(data);
};
const handleMouseEnter = () => {
onMouseEnter(data);
};
const handleMouseLeave = () => {
onMouseLeave(data);
};
const itemAttr: Record<string, any> = {
className: isSelectedItemChild ? styles.selectedItemChild : styles.treeItem,
onClick: handleClick,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
};
if (isSelect) {
itemAttr.tabIndex = 0;
itemAttr.className = styles.treeItem + ' ' + styles.select;
}
if (isSelectedItemChild) {
itemAttr.className = styles.treeItem + ' ' + styles.selectedItemChild;
}
const pushBadge = (showName: Array<any>, badgeName: string) => {
showName.push(' ');
showName.push(<div className={`${styles.Badge}`}>{badgeName}</div>);
};
const pushItemName = (showName: Array<any>, cutName: string, char: string) => {
const index = cutName.search(char);
if (index > -1) {
const notHighlightStr = cutName.slice(0, index);
showName.push(`<${notHighlightStr}`);
showName.push(<mark>{char}</mark>);
showName.push(`${cutName.slice(index + char.length)}>`);
} else {
showName.push(`<${cutName}`);
}
};
const pushBadgeName = (showName: Array<any>, cutName: string, char: string) => {
const index = cutName.search(char);
if (index > -1) {
const notHighlightStr = cutName.slice(0, index);
showName.push(
<div className={`${styles.Badge}`}>
{notHighlightStr}
{<mark>{char}</mark>}
{cutName.slice(index + char.length)}
</div>
);
} else {
pushBadge(showName, cutName);
}
};
const reg = createRegExp(highlightValue);
const heightCharacters = name.itemName.match(reg);
const showName = [];
const addShowName = (showName: Array<string>, name: NameObj) => {
showName.push(`<${name.itemName}>`);
name.badge.forEach(key => {
showName.push(<div className={`${styles.Badge}`}>{key}</div>);
});
};
if (heightCharacters) {
// 高亮第一次匹配即可
const char = heightCharacters[0];
pushItemName(showName, name.itemName, char);
if (name.badge.length > 0) {
name.badge.forEach(key => {
pushBadgeName(showName, key, char);
});
}
} else {
addShowName(showName, name);
}
return (
<div {...itemAttr}>
<div
style={{ marginLeft: indentation * indentationLength }}
className={styles.treeIcon}
onclick={handleClickCollapse}
>
{showIcon}
</div>
<span className={styles.componentName}>{showName}</span>
{isShowKey && (
<>
<span className={styles.componentKeyName}>&nbsp;key</span>
{'="'}
<span className={styles.componentKeyValue}>{userKey}</span>
{'"'}
</>
)}
</div>
);
}
function VTree(props: {
data: IData[];
maxDeep: number;
highlightValue: string;
scrollToItem: IData;
onRendered: (renderInfo: RenderInfoType<IData>) => void;
collapsedNodes?: IData[];
onCollapseNode?: (item: IData[]) => void;
selectItem: IData;
onSelectItem: (item: IData) => void;
}) {
const { data, maxDeep, highlightValue, scrollToItem, onRendered, onCollapseNode, onSelectItem } = props;
const [collapseNode, setCollapseNode] = useState(props.collapsedNodes || []);
const [selectItem, setSelectItem] = useState(props.selectItem);
const [childItems, setChildItems] = useState<Array<IData>>([]);
useEffect(() => {
setSelectItem(scrollToItem);
}, [scrollToItem]);
useEffect(() => {
if (props.selectItem !== selectItem) {
setSelectItem(props.selectItem);
}
}, [props.selectItem]);
useEffect(() => {
setCollapseNode(props.collapsedNodes || []);
}, [props.collapsedNodes]);
const changeCollapseNode = (item: IData) => {
const nodes: IData[] = [...collapseNode];
const index = nodes.indexOf(item);
if (index === -1) {
nodes.push(item);
} else {
nodes.splice(index, 1);
}
setCollapseNode(nodes);
if (onCollapseNode) {
onCollapseNode(nodes);
}
};
const getChildItem = (item: IData): Array<IData> => {
const index = data.indexOf(item);
const childList: Array<IData> = [];
for (let i = index + 1; i < data.length; i++) {
if (data[i].indentation > item.indentation) {
childList.push(data[i]);
} else {
break;
}
}
return childList;
};
const handleClickItem = useCallback(
(item: IData) => {
const childItem = getChildItem(item);
setSelectItem(item);
setChildItems(childItem);
if (onSelectItem) {
onSelectItem(item);
}
},
[onSelectItem]
);
const handleMouseEnterItem = useCallback(item => {
postMessageToBackground(Highlight, item);
}, null);
const handleMouseLeaveItem = () => {
postMessageToBackground(RemoveHighlight);
};
let currentCollapseIndentation: null | number = null;
// 过滤掉折叠的 item不展示在 VList 中
const filter = (item: IData) => {
if (currentCollapseIndentation !== null) {
// 缩进更大,不显示
if (item.indentation > currentCollapseIndentation) {
return false;
} else {
// 缩进小,说明完成了收起节点的子节点处理
currentCollapseIndentation = null;
}
}
const isCollapsed = collapseNode.includes(item);
if (isCollapsed) {
// 该节点需要收起子节点
currentCollapseIndentation = item.indentation;
}
return true;
};
const showList = data.filter(filter);
return (
<SizeObserver className={styles.treeContainer}>
{(width: number, height: number) => {
return (
<VList
data={showList}
maxDeep={maxDeep}
width={width}
height={height}
itemHeight={17.5}
scrollToItem={selectItem}
onRendered={onRendered}
>
{(item: IData, indentationLength: number) => {
const isCollapsed = collapseNode.includes(item);
const index = showList.indexOf(item);
// 如果收起,一定有 child
// 不收起场景,如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标
const hasChild = isCollapsed || showList[index + 1]?.indentation > item.indentation;
return (
<Item
indentationLength={indentationLength}
hasChild={hasChild}
onCollapse={changeCollapseNode}
onClick={handleClickItem}
onMouseEnter={handleMouseEnterItem}
onMouseLeave={handleMouseLeaveItem}
isCollapsed={collapseNode.includes(item)}
isSelect={selectItem === item}
highlightValue={highlightValue}
data={item}
isSelectedItemChild={childItems.includes(item)}
/>
);
}}
</VList>
);
}}
</SizeObserver>
);
}
export default memo(VTree);

View File

@ -1,32 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
@arrow-color: rgb(95, 99, 104);
@divider-color: rgb(202, 205, 209);
@attr-name-color: rgb(200, 0, 0);
@component-name-color: rgb(136, 18, 128);
@component-key-color: rgb(153, 69, 0);
@component-key-value-color: rgb(26, 26, 166);
@component-attr-color: rgb(200, 0, 0);
@select-color: rgb(144 199 248 / 60%);
@select-item-child-color: rgb(141 199 248 / 40%);
@hover-color: black;
@top-height: 2.625rem;
@divider-width: 0.2px;
@common-font-size: 12px;
@divider-style: @divider-color solid @divider-width;
@attr-name-font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;

View File

@ -1,95 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/**
*
* ResizeObserver IE
* dom
* window resize object
* window object dom dom dom
* dom window window
* dom
*
* <div id='test>
* <object> --> div
* <html></html> --> resize
* </object>
* </div>
*
*/
function timeout(func) {
return setTimeout(func, 20);
}
function requestFrame(func) {
const raf = requestAnimationFrame || timeout;
return raf(func);
}
function cancelFrame(id) {
const cancel = cancelAnimationFrame || clearTimeout;
cancel(id);
}
// 在闲置帧触发回调事件,如果在本次触发前存在未处理回调事件,需要取消未处理回调事件
function resizeListener(event) {
const win = event.target;
if (win.__resizeRAF__) {
cancelFrame(win.__resizeRAF__);
}
win.__resizeRAF__ = requestFrame(function () {
const observeElement = win.__observeElement__;
observeElement.__resizeCallbacks__.forEach(function (func) {
func.call(observeElement, observeElement, event);
});
});
}
function loadObserver(this: any) {
// 将待观测元素传递给 object 标签的 window 对象,这样在触发 resize 事件时可以拿到待观测元素
this.contentDocument.defaultView.__observeElement__ = this.__observeElement__;
// 给 html 的 window 对象添加 resize 事件
this.contentDocument.defaultView.addEventListener('resize', resizeListener);
}
export function addResizeListener(element: any, func: any) {
if (!element.__resizeCallbacks__) {
element.__resizeCallbacks__ = [func];
element.style.position = 'relative';
const observer = document.createElement('object');
observer.setAttribute(
'style',
'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;'
);
observer.data = 'about:blank';
observer.onload = loadObserver;
observer.type = 'text/html';
observer['__observeElement__'] = element;
element.__observer__ = observer;
element.appendChild(observer);
} else {
element.__resizeCallbacks__.push(func);
}
}
export function removeResizeListener(element, func) {
element.__resizeCallbacks__.splice(element.__resizeCallbacks__.indexOf(func), 1);
if (!element.__resizeCallbacks__.length) {
element.__observer__.contentDocument.defaultView.removeEventListener('resize', resizeListener);
element.removeChild(element.__observer__);
element.__observer__ = null;
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { injectSrc, injectCode } from '../utils/injectUtils';
import { checkMessage } from '../utils/transferUtils';
import { DevToolContentScript, DevToolHook, DevToolBackground } from '../utils/constants';
import { changeSource } from '../utils/transferUtils';
// 页面的 window 对象不能直接通过 contentScript 代码修改,只能通过添加 js 代码往页面 window 注入 hook
const rendererURL = chrome.runtime.getURL('/injector.js');
if (window.performance.getEntriesByType('navigation')) {
const entryType = (window.performance.getEntriesByType('navigation')[0] as any).type;
if (entryType === 'navigate') {
injectSrc(rendererURL);
} else if (entryType === 'reload' && !(window as any).__INULA_DEV_HOOK__) {
let rendererCode;
const request = new XMLHttpRequest();
request.addEventListener('load', function () {
rendererCode = this.responseText;
});
request.open('GET', rendererURL, false);
request.send();
injectCode(rendererCode);
}
}
// 监听来自页面的信息
window.addEventListener(
'message',
event => {
// 只监听来自本页面的消息
if (event.source !== window) {
return;
}
const data = event.data;
if (checkMessage(data, DevToolHook)) {
changeSource(data, DevToolContentScript);
// 传递给 background
chrome.runtime.sendMessage(data);
}
},
false
);
// 监听来自 background 的消息
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
// 该方法可以监听页面 contentScript 和插件的消息
// 没有 tab 信息说明消息来自插件
if (!sender.tab && checkMessage(message, DevToolBackground)) {
changeSource(message, DevToolContentScript);
// 传递消息给页面
window.postMessage(message, '*');
}
sendResponse({ status: 'ok' });
});

View File

@ -1,283 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import assign from 'object-assign';
import { VNode } from '../../../inula/src/renderer/vnode/VNode';
const overlayStyles = {
background: 'rgba(120, 170, 210, 0.7)',
padding: 'rgba(77, 200, 0, 0.3)',
margin: 'rgba(255, 155, 0, 0.3)',
border: 'rgba(255, 200, 50, 0.3)',
};
type Rect = {
bottom: number;
height: number;
left: number;
right: number;
top: number;
width: number;
};
function setBoxStyle(eleStyle, boxArea, node) {
assign(node.style, {
borderTopWidth: eleStyle[boxArea + 'Top'] + 'px',
borderLeftWidth: eleStyle[boxArea + 'Left'] + 'px',
borderRightWidth: eleStyle[boxArea + 'Right'] + 'px',
borderBottomWidth: eleStyle[boxArea + 'Bottom'] + 'px',
});
}
function getOwnerWindow(node: Element): typeof window | null {
if (!node.ownerDocument) {
return null;
}
return node.ownerDocument.defaultView;
}
function getOwnerIframe(node: Element): Element | null {
const nodeWindow = getOwnerWindow(node);
if (nodeWindow) {
return nodeWindow.frameElement;
}
return null;
}
function getElementStyle(domElement: Element) {
const style = window.getComputedStyle(domElement);
return {
marginLeft: parseInt(style.marginLeft, 10),
marginRight: parseInt(style.marginRight, 10),
marginTop: parseInt(style.marginTop, 10),
marginBottom: parseInt(style.marginBottom, 10),
borderLeft: parseInt(style.borderLeftWidth, 10),
borderRight: parseInt(style.borderRightWidth, 10),
borderTop: parseInt(style.borderTopWidth, 10),
borderBottom: parseInt(style.borderBottomWidth, 10),
paddingLeft: parseInt(style.paddingLeft, 10),
paddingRight: parseInt(style.paddingRight, 10),
paddingTop: parseInt(style.paddingTop, 10),
paddingBottom: parseInt(style.paddingBottom, 10),
};
}
function mergeRectOffsets(rects: Array<Rect>): Rect {
return rects.reduce((previousRect, rect) => {
if (previousRect == null) {
return rect;
}
return {
top: previousRect.top + rect.top,
left: previousRect.left + rect.left,
width: previousRect.width + rect.width,
height: previousRect.height + rect.height,
bottom: previousRect.bottom + rect.bottom,
right: previousRect.right + rect.right,
};
});
}
function getBoundingClientRectWithBorderOffset(node: Element) {
const dimensions = getElementStyle(node);
return mergeRectOffsets([
node.getBoundingClientRect(),
{
top: dimensions.borderTop,
left: dimensions.borderLeft,
bottom: dimensions.borderBottom,
right: dimensions.borderRight,
// 高度和宽度不会被使用
width: 0,
height: 0,
},
]);
}
function getNestedBoundingClientRect(node: HTMLElement, boundaryWindow): Rect {
const ownerIframe = getOwnerIframe(node);
if (ownerIframe && ownerIframe !== boundaryWindow) {
const rects = [node.getBoundingClientRect()] as Rect[];
let currentIframe = ownerIframe;
let onlyOneMore = false;
while (currentIframe) {
const rect = getBoundingClientRectWithBorderOffset(currentIframe);
rects.push(rect);
currentIframe = getOwnerIframe(currentIframe);
if (onlyOneMore) {
break;
}
if (currentIframe && getOwnerWindow(currentIframe) === boundaryWindow) {
onlyOneMore = true;
}
}
return mergeRectOffsets(rects);
} else {
return node.getBoundingClientRect();
}
}
// 用来遮罩
class OverlayRect {
node: HTMLElement;
border: HTMLElement;
padding: HTMLElement;
content: HTMLElement;
constructor(doc: Document, container: HTMLElement) {
this.node = doc.createElement('div');
this.border = doc.createElement('div');
this.padding = doc.createElement('div');
this.content = doc.createElement('div');
this.border.style.borderColor = overlayStyles.border;
this.padding.style.borderColor = overlayStyles.padding;
this.content.style.backgroundColor = overlayStyles.background;
assign(this.node.style, {
borderColor: overlayStyles.margin,
pointerEvents: 'none',
position: 'fixed',
});
this.node.style.zIndex = '10000000';
this.node.appendChild(this.border);
this.border.appendChild(this.padding);
this.padding.appendChild(this.content);
container.appendChild(this.node);
}
remove() {
if (this.node.parentNode) {
this.node.parentNode.removeChild(this.node);
}
}
update(boxRect: Rect, eleStyle: any) {
setBoxStyle(eleStyle, 'margin', this.node);
setBoxStyle(eleStyle, 'border', this.border);
setBoxStyle(eleStyle, 'padding', this.padding);
assign(this.content.style, {
height:
boxRect.height -
eleStyle.borderTop -
eleStyle.borderBottom -
eleStyle.paddingTop -
eleStyle.paddingBottom +
'px',
width:
boxRect.width -
eleStyle.borderLeft -
eleStyle.borderRight -
eleStyle.paddingLeft -
eleStyle.paddingRight +
'px',
});
assign(this.node.style, {
top: boxRect.top - eleStyle.marginTop + 'px',
left: boxRect.left - eleStyle.marginLeft + 'px',
});
}
}
class ElementOverlay {
window: typeof window;
container: HTMLElement;
rects: Array<OverlayRect>;
constructor() {
this.window = window;
const doc = window.document;
this.container = doc.createElement('div');
this.container.style.zIndex = '10000000';
this.rects = [];
doc.body.appendChild(this.container);
}
remove() {
this.rects.forEach(rect => {
rect.remove();
});
this.rects.length = 0;
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
}
execute(nodes: Array<VNode>) {
const elements = nodes.filter(node => node.tag === 'DomComponent');
// 有几个 element 就添加几个 OverlayRect
while (this.rects.length > elements.length) {
const rect = this.rects.pop();
rect.remove();
}
if (elements.length === 0) {
return;
}
while (this.rects.length < elements.length) {
this.rects.push(new OverlayRect(this.window.document, this.container));
}
const outerBox = {
top: Number.POSITIVE_INFINITY,
right: Number.NEGATIVE_INFINITY,
bottom: Number.NEGATIVE_INFINITY,
left: Number.POSITIVE_INFINITY,
};
elements.forEach((element, index) => {
const eleStyle = getElementStyle(element.realNode);
const boxRect = getNestedBoundingClientRect(element.realNode, this.window);
outerBox.top = Math.min(outerBox.top, boxRect.top - eleStyle.marginTop);
outerBox.right = Math.max(outerBox.right, boxRect.left + boxRect.width + eleStyle.marginRight);
outerBox.bottom = Math.max(outerBox.bottom, boxRect.top + boxRect.height + eleStyle.marginBottom);
outerBox.left = Math.min(outerBox.left, boxRect.left - eleStyle.marginLeft);
const rect = this.rects[index];
rect.update(boxRect, eleStyle);
});
}
}
let elementOverlay: ElementOverlay | null = null;
export function hideHighlight() {
if (elementOverlay !== null) {
elementOverlay.remove();
elementOverlay = null;
}
}
export function showHighlight(elements: Array<VNode> | null) {
if (window.document == null || elements == null) {
return;
}
if (elementOverlay === null) {
elementOverlay = new ElementOverlay();
}
elementOverlay.execute(elements);
}

View File

@ -1,171 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/**
*
* /
* Tree
*
*
*
*
*
*
*
*
*
* index data
*/
import { useState, useRef } from 'openinula';
import { createRegExp } from '../utils/regExpUtil';
import { NameObj } from '../parser/parseVNode';
type BaseType = {
id: number;
name: NameObj;
indentation: number;
};
/**
*
*
* @param item
* @param data
* @param collapsedNodes
* @returns
*/
function expandItemParent(item: BaseType, data: BaseType[], collapsedNodes: BaseType[]): BaseType[] {
const index = data.indexOf(item);
let currentIndentation = item.indentation;
// 不对原始数据进行修改
const newCollapsedNodes = [...collapsedNodes];
for (let i = index - 1; i >= 0; i--) {
const lastData = data[i];
const lastIndentation = lastData.indentation;
// 缩进更小,找到了父节点
if (lastIndentation < currentIndentation) {
// 更新缩进值,只招父节点的父节点,避免修改父节点的兄弟节点的展开状态
currentIndentation = lastIndentation;
const cIndex = newCollapsedNodes.indexOf(lastData);
if (cIndex !== -1) {
newCollapsedNodes.splice(cIndex, 1);
}
}
}
return newCollapsedNodes;
}
export function FilterTree<T extends BaseType>(props: { data: T[] }) {
const { data } = props;
const [filterValue, setFilterValue] = useState('');
const [currentItem, setCurrentItem] = useState(null);
const showItemsRef = useRef([]); // 页面展示的 items
const matchItemsRef = useRef([]); //匹配过滤条件的 items
const collapsedNodesRef = useRef([]); // 折叠节点,如果匹配 item 被折叠了,需要展开
const matchItems = matchItemsRef.current;
const collapsedNodes = collapsedNodesRef.current;
const updateCollapsedNodes = (item: BaseType) => {
const newCollapsedNodes = expandItemParent(item, data, collapsedNodes);
// 如果新旧收起节点数组长度不一致,说明存在收起节点
if (newCollapsedNodes.length !== collapsedNodes.length) {
// 更新引用,确保 VTree 拿到新的 collapsedNodes
collapsedNodesRef.current = newCollapsedNodes;
}
};
const onChangeSearchValue = (search: string) => {
const reg = createRegExp(search);
let newCurrentItem = null;
let newMatchItems = [];
if (search !== '') {
const showItems: T[] = showItemsRef.current;
newMatchItems = data.reduce((pre, current) => {
const { name } = current;
if (name && reg && name.itemName.match(reg)) {
pre.push(current);
// 如果当前页面显示的 item 存在匹配项,则把他设置为 currentItem
if (newCurrentItem === null && showItems.includes(current)) {
newCurrentItem = current;
}
}
return pre;
}, []);
if (newMatchItems.length === 0) {
setCurrentItem(null);
} else {
if (newCurrentItem === null) {
const item = newMatchItems[0];
// 不处于当前展示页面,需要展开父节点
updateCollapsedNodes(item);
setCurrentItem(item);
} else {
setCurrentItem(newCurrentItem);
}
}
} else {
setCurrentItem(null);
}
matchItemsRef.current = newMatchItems;
setFilterValue(search);
};
const onSelectNext = () => {
const index = matchItems.indexOf(currentItem);
const nextIndex = index + 1;
const item = nextIndex < matchItemsRef.current.length ? matchItems[nextIndex] : matchItems[0];
// 可能不处于当前展示页面,需要展开父节点
updateCollapsedNodes(item);
setCurrentItem(item);
};
const onSelectLast = () => {
const index = matchItems.indexOf(currentItem);
const last = index - 1;
const item = last >= 0 ? matchItems[last] : matchItems[matchItems.length - 1];
// 可能不处于当前展示页面,需要展开父节点
updateCollapsedNodes(item);
setCurrentItem(item);
};
const setShowItems = items => {
showItemsRef.current = [...items];
};
const onClear = () => {
onChangeSearchValue('');
};
const setCollapsedNodes = items => {
// 不更新引用,避免子组件的重复渲染
collapsedNodesRef.current.length = 0;
collapsedNodesRef.current.push(...items);
};
return {
filterValue,
onChangeSearchValue,
onClear,
currentItem,
matchItems,
onSelectNext,
onSelectLast,
setShowItems,
collapsedNodes,
setCollapsedNodes,
};
}

View File

@ -1,460 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import parseTreeRoot, { clearVNode, queryVNode, VNodeToIdMap } from '../parser/parseVNode';
import { packagePayload, checkMessage } from '../utils/transferUtils';
import {
RequestAllVNodeTreeInfos,
AllVNodeTreeInfos,
RequestComponentAttrs,
ComponentAttrs,
DevToolHook,
DevToolContentScript,
ModifyAttrs,
ModifyHooks,
ModifyState,
ModifyProps,
InspectDom,
LogComponentData,
Highlight,
RemoveHighlight,
ViewSource,
PickElement,
StopPickElement,
CopyToConsole,
StorageValue,
} from '../utils/constants';
import { VNode } from '../../../inula/src/renderer/vnode/VNode';
import { parseVNodeAttrs } from '../parser/parseAttr';
import { showHighlight, hideHighlight } from '../highlight';
import {
FunctionComponent,
ClassComponent,
IncompleteClassComponent,
ForwardRef,
MemoComponent,
} from '../../../inula/src/renderer/vnode/VNodeTags';
import { pickElement } from './pickElement';
const roots = [];
let storeDataCount = 0;
function addIfNotInclude(treeRoot: VNode) {
if (!roots.includes(treeRoot)) {
roots.push(treeRoot);
}
}
function send() {
const result = roots.reduce((pre, current) => {
const info = parseTreeRoot(helper.travelVNodeTree, current);
pre.push(info);
return pre;
}, []);
postMessage(AllVNodeTreeInfos, result);
}
function deleteVNode(vNode: VNode) {
// 开发工具中保存了 vNode 的引用,在清理 vNode 的时候需要一并删除
clearVNode(vNode);
const index = roots.indexOf(vNode);
if (index !== -1) {
roots.splice(index, 1);
}
}
export function postMessage(type: string, data) {
window.postMessage(
packagePayload(
{
type: type,
data: data,
},
DevToolHook
),
'*'
);
}
function parseCompAttrs(id: number) {
const vNode = queryVNode(id);
if (!vNode) {
console.error('Do not find match vNode, this is a bug, please report us.');
return;
}
const parsedAttrs = parseVNodeAttrs(vNode, helper.getHookInfo);
postMessage(ComponentAttrs, parsedAttrs);
}
function calculateNextValue(editValue, value, attrPath) {
let nextState;
const editValueType = typeof editValue;
if (editValueType === 'string' || editValueType === 'undefined' || editValueType === 'boolean') {
nextState = value;
} else if (editValueType === 'number') {
const numValue = Number(value);
nextState = isNaN(numValue) ? value : numValue; // 如果能转为数字,转数字,不能转数字旧用原值
} else if (editValueType === 'object') {
if (editValue === null) {
nextState = value;
} else {
const newValue = Array.isArray(editValue) ? [...editValue] : { ...editValue };
// 遍历读取到直接指向需要修改值的对象
let attr = newValue;
for (let i = 0; i < attrPath.length - 1; i++) {
attr = attr[attrPath[i]];
}
// 修改对象上的值
attr[attrPath[attrPath.length - 1]] = value;
nextState = newValue;
}
} else {
console.error('The dev tools tried to edit a non-editable value, this is a bug, please report.', editValue);
}
return nextState;
}
function modifyVNodeAttrs(data) {
const { type, id, value, path } = data;
const vNode = queryVNode(id);
if (!vNode) {
console.error('Do not find match vNode, this is a bug, please report us.');
return;
}
if (type === ModifyProps) {
const nextProps = calculateNextValue(vNode.props, value, path);
helper.updateProps(vNode, nextProps);
} else if (type === ModifyHooks) {
const hooks = vNode.hooks;
const editHook = hooks[path[0]];
const hookInfo = helper.getHookInfo(editHook);
if (hookInfo) {
const editValue = hookInfo.value;
// path 的第一个指向 hIndex从第二个值才开始指向具体属性访问路径
const nextState = calculateNextValue(editValue, value, path.slice(1));
helper.updateHooks(vNode, path[0], nextState);
} else {
console.error('The dev tools tried to edit a non-editable hook, this is a bug, please report.', hooks);
}
} else if (type === ModifyState) {
const oldState = vNode.state || {};
const nextState = { ...oldState };
let accessRef = nextState;
for (let i = 0; i < path.length - 1; i++) {
accessRef = accessRef[path[i]];
}
accessRef[path[path.length - 1]] = value;
helper.updateState(vNode, nextState);
}
}
function logComponentData(id: number) {
const vNode = queryVNode(id);
if (vNode == null) {
console.warn(`Could not find vNode with id "${id}"`);
return null;
}
if (vNode) {
const info = helper.getComponentInfo(vNode);
console.log('vNode: ', vNode);
console.log('Component Info: ', info);
}
}
/**
* path vNode
*
* @param {VNode} vNode dom
* @param {Array<string | number>} path
* @param {string} attrsName props hooks
*/
const getValueByPath = (vNode: VNode, path: Array<string | number>, attrsName: string) => {
if (attrsName === 'Props') {
return path.reduce((previousValue, currentValue) => {
return previousValue[currentValue];
}, vNode.props);
} else {
// attrsName 为 Hooks
if (path.length > 1) {
return path.reduce((previousValue, currentValue) => {
return previousValue[currentValue];
}, vNode.hooks);
}
return vNode.hooks[path[0]];
}
};
/**
* path vNode
*
* @param {number} id idToVNodeMap key id VNode
* @param {string} itemName
* @param {Array<string | number>} path
* @param {string} attrsName
*/
function logDataWithPath(id: number, itemName: string, path: Array<string | number>, attrsName: string) {
const vNode = queryVNode(id);
if (vNode === null) {
console.warn(`Could not find vNode with id "${id}"`);
return null;
}
if (vNode) {
const value = getValueByPath(vNode, path, attrsName);
if (attrsName === 'Hooks') {
console.log(itemName, value);
} else {
console.log(`${path[path.length - 1]}`, value);
}
}
}
/**
* path vNode
*
* @param {number} id idToVNodeMap key id VNode
* @param {Array<string |number>} path
* @param {string} attrsName
*/
function storeDataWithPath(id: number, path: Array<string | number>, attrsName: string) {
const vNode = queryVNode(id);
if (vNode === null) {
console.warn(`Could not find vNode with id "${id}"`);
return null;
}
if (vNode) {
const value = getValueByPath(vNode, path, attrsName);
const key = `$InulaTemp${storeDataCount++}`;
window[key] = value;
console.log(key);
console.log(value);
}
}
export let helper;
function init(inulaHelper) {
helper = inulaHelper;
(window as any).__INULA_DEV_HOOK__.isInit = true;
}
export function getElement(travelVNodeTree, treeRoot: VNode) {
const result: any[] = [];
travelVNodeTree(
treeRoot,
(node: VNode) => {
if (node.realNode) {
if (Object.keys(node.realNode).length > 0 || node.realNode.size > 0) {
result.push(node);
}
}
},
(node: VNode) => node.realNode != null && (Object.keys(node.realNode).length > 0 || node.realNode.size > 0)
);
return result;
}
// dev tools 点击眼睛图标功能
const inspectDom = data => {
const { id } = data;
const vNode = queryVNode(id);
if (vNode == null) {
console.warn(`Could not find vNode with id "${id}"`);
return null;
}
const info = getElement(helper.travelVNodeTree, vNode);
if (info) {
showHighlight(info);
(window as any).__INULA_DEV_HOOK__.$0 = info[0];
}
};
const picker = pickElement(window);
const actions = new Map([
// 请求左树所有数据
[
RequestAllVNodeTreeInfos,
() => {
send();
},
],
// 请求某个节点的 propshooks
[
RequestComponentAttrs,
data => {
parseCompAttrs(data);
},
],
// 修改 propshooks
[
ModifyAttrs,
data => {
modifyVNodeAttrs(data);
},
],
// 找到节点对应 element
[
InspectDom,
data => {
inspectDom(data);
},
],
// 打印节点数据
[
LogComponentData,
data => {
logComponentData(data);
},
],
// 高亮
[
Highlight,
data => {
const node = queryVNode(data.id);
if (node == null) {
console.warn(`Could not find vNode with id "${data.id}"`);
return null;
}
const info = getElement(helper.travelVNodeTree, node);
showHighlight(info);
},
],
// 移出高亮
[
RemoveHighlight,
() => {
hideHighlight();
},
],
// 查看节点源代码位置
[
ViewSource,
data => {
const node = queryVNode(data.id);
if (node == null) {
console.warn(`Could not find vNode with id "${data.id}"`);
return null;
}
showSource(node);
},
],
// 选中页面元素对应 dev tools 节点
[
PickElement,
() => {
picker.startPick();
},
],
[
StopPickElement,
() => {
picker.stopPick();
},
],
// 在控制台打印 Props Hooks State 值
[
CopyToConsole,
data => {
const node = queryVNode(data.id);
if (node == null) {
console.warn(`Could not find vNode with id "${data.id}"`);
return null;
}
logDataWithPath(data.id, data.itemName, data.path, data.attrsName);
},
],
// 把 Props Hooks State 值存为全局变量
[
StorageValue,
data => {
const node = queryVNode(data.id);
if (node == null) {
console.warn(`Could not find vNode with id "${data.id}"`);
return null;
}
storeDataWithPath(data.id, data.path, data.attrsName);
},
],
]);
const showSource = (node: VNode) => {
switch (node.tag) {
case ClassComponent:
case IncompleteClassComponent:
case FunctionComponent:
global.$type = node.type;
break;
case ForwardRef:
global.$type = node.type.render;
break;
case MemoComponent:
global.$type = node.type.type;
break;
default:
global.$type = null;
break;
}
};
const handleRequest = (type: string, data) => {
const action = actions.get(type);
if (action) {
action.call(this, data);
return null;
}
console.warn('unknown command', type);
};
function injectHook() {
if ((window as any).__INULA_DEV_HOOK__) {
return;
}
Object.defineProperty(window, '__INULA_DEV_HOOK__', {
enumerable: false,
value: {
$0: null,
init,
isInit: false,
addIfNotInclude,
send,
deleteVNode,
// inulaX 使用
getVNodeId: vNode => {
return VNodeToIdMap.get(vNode);
},
},
});
window.addEventListener('message', function (event) {
// 只接收我们自己的消息
if (event.source !== window) {
return;
}
const request = event.data;
if (checkMessage(request, DevToolContentScript)) {
const { payload } = request;
const { type, data } = payload;
// 忽略 inulaX 的 actions
if (type.startsWith('inulax')) {
return;
}
handleRequest(type, data);
}
});
}
injectHook();

View File

@ -1,96 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { PickElement, StopPickElement } from '../utils/constants';
import { getElement, helper, postMessage } from './index';
import { queryVNode, VNodeToIdMap } from '../parser/parseVNode';
import { isUserComponent } from '../parser/parseVNode';
import { throttle } from 'lodash';
import { hideHighlight, showHighlight } from '../highlight';
// 判断鼠标移入节点是否为 dev tools 上的节点,如果不是则找父节点
function getUserComponent(target) {
if (target.tag && isUserComponent(target.tag)) {
return target;
}
while (target.tag && !isUserComponent(target.tag)) {
if (target.parent) {
target = target.parent;
}
}
return target;
}
function onMouseEvent(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
}
function onMouseMove(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
const target = (event.target as any)._inula_VNode;
if (target) {
const id = VNodeToIdMap.get(getUserComponent(target));
const vNode = queryVNode(id);
if (vNode == null) {
console.warn(`Could not find vNode with id "${id}"`);
return null;
}
const info = getElement(helper.travelVNodeTree, vNode);
if (info) {
showHighlight(info);
}
// 0.5 秒内在节流结束后只触发一次
throttle(
() => {
postMessage(PickElement, id);
},
500,
{ leading: false, trailing: true }
)();
}
}
export function pickElement(window: Window) {
function onClick(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
stopPick();
postMessage(StopPickElement, null);
}
const startPick = () => {
if (window && typeof window.addEventListener === 'function') {
window.addEventListener('click', onClick, true);
window.addEventListener('mousedown', onMouseEvent, true);
window.addEventListener('mousemove', onMouseMove, true);
window.addEventListener('mouseup', onMouseEvent, true);
}
};
const stopPick = () => {
hideHighlight();
window.removeEventListener('click', onClick, true);
window.removeEventListener('mousedown', onMouseEvent, true);
window.removeEventListener('mousemove', onMouseMove, true);
window.removeEventListener('mouseup', onMouseEvent, true);
};
return { startPick, stopPick };
}

View File

@ -1,88 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { render, createElement } from 'openinula';
import Panel from '../panel/Panel';
import PanelX from '../panelX/PanelX';
let panelCreated = false;
const viewSource = () => {
setTimeout(() => {
chrome.devtools.inspectedWindow.eval(`
if (window.$type != null) {
if (
window.$type &&
window.$type.prototype &&
window.$type.prototype.render
) {
// 类组件
inspect(window.$type.prototype.render);
} else {
// 函数组件
inspect(window.$type);
}
}
`);
}, 100);
};
const inspectVNode = () => {
chrome.devtools.inspectedWindow.eval(
`
window.__INULA_DEV_HOOK__ && window.__INULA_DEV_HOOK__.$0 !== $0
? (inspect(window.__INULA_DEV_HOOK__.$0.realNode), true)
: false
`,
(_, error) => {
if (error) {
console.error(error);
}
}
);
};
let currentPanel = null;
chrome.devtools.inspectedWindow.eval('window.__INULA_DEV_HOOK__', function (isInula, error) {
if (!isInula || panelCreated) {
return;
}
panelCreated = true;
chrome.devtools.panels.create('Inula', '', 'panel.html', extensionPanel => {
extensionPanel.onShown.addListener(panel => {
if (currentPanel === panel) {
return;
}
currentPanel = panel;
const container = panel.document.getElementById('root');
const element = createElement(Panel, { viewSource, inspectVNode });
render(element, container);
});
});
chrome.devtools.panels.create('InulaX', '', 'panelX.html', extensionPanel => {
extensionPanel.onShown.addListener(panel => {
if (currentPanel === panel) {
return;
}
currentPanel = panel;
const container = panel.document.getElementById('root');
const element = createElement(PanelX, {});
render(element, container);
});
});
});

View File

@ -1,30 +0,0 @@
<!--
~ Copyright (c) 2023 Huawei Technologies Co.,Ltd.
~
~ openInula is licensed under Mulan PSL v2.
~ You can use this software according to the terms and conditions of the Mulan PSL v2.
~ You may obtain a copy of Mulan PSL v2 at:
~
~ http://license.coscl.org.cn/MulanPSL2
~
~ THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
~ EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
~ MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
~ See the Mulan PSL v2 for more details.
-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src *; style-src 'self' 'unsafe-inline'; srcipt-src 'self' 'unsafe-inline' 'unsafe-eval' ">
<script src="inula.development.js"></script>
</head>
<body>
<div>
<p>Inula dev tools!</p>
</div>
</body>
<script> src="main.js"</script>
</html>

View File

@ -1,35 +0,0 @@
{
"name": "Inula dev tools",
"description": "Inula chrome dev extension",
"version": "1.0",
"minimum_chrome_version": "10.0",
"manifest_version": 3,
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"background": {
"script": [
"background.js"
],
"persistent": true
},
"permissions": [
"file:///*",
"http://*/*",
"https://*/*"
],
"devtools_page": "main.html",
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"contentScript.js"
],
"run_at": "document_start"
}
],
"web_accessible_resources": [
"injector.js",
"background.js"
]
}

View File

@ -1,117 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
@import '../components/assets.less';
.app {
display: flex;
flex-direction: row;
height: 100%;
font-size: @common-font-size;
}
button {
border: none;
background: none;
padding: 0;
}
.left {
flex: 0 0 var(--horizontal-percentage);
display: flex;
flex-direction: column;
.left-top {
border-bottom: @divider-style;
flex: 0 0 @top-height;
display: flex;
align-items: center;
padding-right: 0.4rem;
.select {
padding: 0 0.5rem 0 0.8rem;
flex: 0 0;
.Picking {
color: #0088fa;
}
.StopPicking {
color: #5f6673;
}
.StopPicking :hover {
color: #23272f;
}
}
.divider {
flex: 0 0 1px;
margin: 0 0.25rem 0 0.25rem;
border-left: @divider-style;
height: calc(100% - 1rem);
}
.search {
display: flex;
flex: 1 1 0;
}
.searchResult {
flex: 0 0;
padding: 0 0.4rem;
}
.searchAction {
flex: 0 0 1rem;
height: 1rem;
color: @arrow-color;
&:hover {
color: @hover-color;
}
}
}
.left-bottom {
flex: 1;
height: 0;
}
}
.resizeBar {
flex: 0 0 0;
position: relative;
resize: horizontal;
.resizeLine {
position: absolute;
left: -2px;
width: 5px;
height: 100%;
cursor: ew-resize;
}
}
.right {
flex: 3;
overflow-x: hidden;
overflow-y: auto;
border-left: @divider-style;
}
input {
outline: none;
border-width: 0;
padding: 0;
}

View File

@ -1,417 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { useState, useEffect, useRef, memo, useMemo, useCallback, useReducer } from 'openinula';
import VTree, { IData } from '../components/VTree';
import Search from '../components/Search';
import ComponentInfo from '../components/ComponentInfo';
import styles from './Panel.less';
import Select from '../svgs/Select';
import { FilterTree } from '../hooks/FilterTree';
import Close from '../svgs/Close';
import Arrow from '../svgs/Arrow';
import {
AllVNodeTreeInfos,
RequestComponentAttrs,
ComponentAttrs,
PickElement,
StopPickElement,
} from '../utils/constants';
import {
addBackgroundMessageListener,
initBackgroundConnection,
postMessageToBackground,
removeBackgroundMessageListener,
} from '../panelConnection';
import { IAttr } from '../parser/parseAttr';
import { NameObj } from '../parser/parseVNode';
import { createLogger } from '../utils/logUtil';
import type { Source } from '../../../inula/src/renderer/Types';
import ViewSourceContext from '../utils/ViewSource';
import PickElementContext from '../utils/PickElement';
import Discover from '../svgs/Discover';
type ResizeActionType = 'START_RESIZE' | 'SET_HORIZONTAL_PERCENTAGE';
type ResizeAction = {
type: ResizeActionType;
payload: any;
};
type ResizeState = {
horizontalPercentage: number;
isResizing: boolean;
};
const logger = createLogger('panelApp');
let maxDeep = 0;
const parseVNodeData = (rawData, idToTreeNodeMap, nextIdToTreeNodeMap) => {
const indentationMap: {
[id: string]: number;
} = {};
const data: IData[] = [];
let i = 0;
while (i < rawData.length) {
const id = rawData[i] as number;
i++;
const name = rawData[i] as NameObj;
i++;
const parentId = rawData[i] as string;
i++;
const userKey = rawData[i] as string;
i++;
const indentation = parentId === '' ? 0 : indentationMap[parentId] + 1;
maxDeep = maxDeep >= indentation ? maxDeep : indentation;
indentationMap[id] = indentation;
const lastItem = idToTreeNodeMap[id];
if (lastItem) {
// 由于 diff 算法限制,一个 vNode 的 nameuserKeyindentation 属性不会发生变化
// 但是在跳转到新页面时, id 值重置,此时原有 id 对应的节点都发生了变化,需要更新
// 为了让架构尽可能简单,不区分是否是页面挑战,所以每次都需要重新赋值
nextIdToTreeNodeMap[id] = lastItem;
lastItem.name = name;
lastItem.indentation = indentation;
lastItem.userKey = userKey;
data.push(lastItem);
} else {
const item = {
id,
name,
indentation,
userKey,
};
nextIdToTreeNodeMap[id] = item;
data.push(item);
}
}
return data;
};
const getParents = (item: IData | null, parsedVNodeData: IData[]) => {
const parents: IData[] = [];
if (item) {
const index = parsedVNodeData.indexOf(item);
let indentation = item.indentation;
for (let i = index; i >= 0; i--) {
const last = parsedVNodeData[i];
const lastIndentation = last.indentation;
if (lastIndentation < indentation) {
parents.push(last);
indentation = lastIndentation;
}
}
}
return parents;
};
interface IIdToNodeMap {
[id: number]: IData;
}
/**
* dev tools
*
* @param {null | HTMLElement} resizeElement
* @param {number} percentage
*/
const setResizePCTForElement = (resizeElement: null | HTMLElement, percentage: number): void => {
if (resizeElement !== null) {
resizeElement.style.setProperty('--horizontal-percentage', `${percentage}`);
}
};
function resizeReducer(state: ResizeState, action: ResizeAction): ResizeState {
switch (action.type) {
case 'START_RESIZE':
return {
...state,
isResizing: action.payload,
};
case 'SET_HORIZONTAL_PERCENTAGE':
return {
...state,
horizontalPercentage: action.payload,
};
default:
return state;
}
}
function initResizeState(): ResizeState {
const horizontalPercentage = 0.62;
return {
horizontalPercentage,
isResizing: false,
};
}
function Panel({ viewSource, inspectVNode }) {
const [parsedVNodeData, setParsedVNodeData] = useState([]);
const [componentAttrs, setComponentAttrs] = useState<{
parsedProps?: IAttr[];
parsedState?: IAttr[];
parsedHooks?: IAttr[];
}>({});
const [selectComp, setSelectComp] = useState<IData>(null);
const [isPicking, setPicking] = useState(false);
const [source, setSource] = useState<Source>(null);
const idToTreeNodeMapref = useRef<IIdToNodeMap>({});
const [state, dispatch] = useReducer(resizeReducer, null, initResizeState);
const pageRef = useRef<null | HTMLElement>(null);
const treeRef = useRef<null | HTMLElement>(null);
const { horizontalPercentage } = state;
const {
filterValue,
onChangeSearchValue: setFilterValue,
onClear,
currentItem,
matchItems,
onSelectNext,
onSelectLast,
setShowItems,
collapsedNodes,
setCollapsedNodes,
} = FilterTree({ data: parsedVNodeData });
useEffect(() => {
if (isDev) {
// const nextIdToTreeNodeMap: IIdToNodeMap = {};
} else {
const handleBackgroundMessage = message => {
const { payload } = message;
// 对象数据只是记录了引用,内容可能在后续被修改,打印字符串可以获取当前真正内容,不被后续修改影响
logger.info(JSON.stringify(payload));
if (payload) {
const { type, data } = payload;
if (type === AllVNodeTreeInfos) {
const idToTreeNodeMap = idToTreeNodeMapref.current;
const nextIdToTreeNodeMap: IIdToNodeMap = {};
const allTreeData = data.reduce((pre, current) => {
const parsedTreeData = parseVNodeData(current, idToTreeNodeMap, nextIdToTreeNodeMap);
return pre.concat(parsedTreeData);
}, []);
idToTreeNodeMapref.current = nextIdToTreeNodeMap;
setParsedVNodeData(allTreeData);
if (selectComp) {
postMessageToBackground(RequestComponentAttrs, selectComp.id);
}
} else if (type === ComponentAttrs) {
const { parsedProps, parsedState, parsedHooks, src } = data;
setComponentAttrs({
parsedProps,
parsedState,
parsedHooks,
});
setSource(src);
} else if (type === StopPickElement) {
setPicking(false);
} else if (type === PickElement) {
const target = Object.values(idToTreeNodeMapref.current).find(({ id }) => id == data);
setSelectComp(target);
}
}
};
// 在页面渲染后初始化连接
initBackgroundConnection('panel');
// 监听 background 消息
addBackgroundMessageListener(handleBackgroundMessage);
return () => {
removeBackgroundMessageListener(handleBackgroundMessage);
};
}
}, [selectComp]);
useEffect(() => {
const treeElement = treeRef.current;
setResizePCTForElement(treeElement, horizontalPercentage * 100);
}, []);
const handleSearchChange = (str: string) => {
setFilterValue(str);
};
const handleSelectComp = (item: IData) => {
setSelectComp(item);
if (isDev) {
// setComponentAttrs({});
} else {
postMessageToBackground(RequestComponentAttrs, item.id);
}
};
const handleClickParent = useCallback((item: IData) => {
setSelectComp(item);
}, []);
const onRendered = info => {
setShowItems(info.visibleItems);
};
const parents = useMemo(() => getParents(selectComp, parsedVNodeData), [selectComp, parsedVNodeData]);
const viewSourceFunction = useMemo(
() => ({
viewSource: viewSource || null,
}),
[viewSource]
);
// 选择页面元素对应到 dev tools
const pickElementFunction = useMemo(
() => ({
inspectVNode: inspectVNode || null,
}),
[inspectVNode]
);
// 选择页面元素图标样式
let pickClassName;
if (isPicking) {
pickClassName = styles.Picking;
} else {
pickClassName = styles.StopPicking;
}
const MINIMUM_SIZE = 50;
const { isResizing } = state;
const doResize = () => dispatch({ type: 'START_RESIZE', payload: true });
let onResize;
let stopResize;
if (isResizing) {
stopResize = () => dispatch({ type: 'START_RESIZE', payload: false });
onResize = event => {
// 设置横向 resize 百分比区域(左树部分)
const treeElement = treeRef.current;
// 整个页面(左树部分加节点详情部分),要拿到页面宽度,防止 resize 时移出页面
const pageElement = pageRef.current;
if (isResizing || pageElement === null || treeElement === null) {
return;
}
// 左移时防止左树移出页面
event.preventDefault();
const { width, left } = pageElement.getBoundingClientRect();
const mouseAbscissa = event.clientX - left;
const pageSizeMin = MINIMUM_SIZE;
const pageSizeMax = width - MINIMUM_SIZE;
const isMouseInPage = mouseAbscissa > pageSizeMin && mouseAbscissa < pageSizeMax;
if (isMouseInPage) {
const resizedElementWidth = width;
const actionType = 'SET_HORIZONTAL_PERCENTAGE';
const percentage = (mouseAbscissa / resizedElementWidth) * 100;
setResizePCTForElement(treeElement, percentage);
dispatch({
type: actionType,
payload: mouseAbscissa / resizedElementWidth,
});
}
};
}
return (
<ViewSourceContext.Provider value={{ viewSourceFunction }}>
<PickElementContext.Provider value={{ pickElementFunction }}>
<div
ref={pageRef}
onMouseMove={onResize}
onMouseLeave={stopResize}
onMouseUp={stopResize}
className={styles.app}
>
<div ref={treeRef} className={styles.left}>
<div className={styles.leftTop}>
<div className={styles.select}>
<button className={`${pickClassName}`}>
<span
className={styles.eye}
title={'Pick an element from the page'}
onClick={() => {
postMessageToBackground(!isPicking ? PickElement : StopPickElement);
setPicking(!isPicking);
}}
>
<Select />
</span>
</button>
</div>
<div className={styles.divider} />
<div className={styles.search}>
<Discover />
<Search onKeyUp={onSelectNext} onChange={handleSearchChange} value={filterValue} />
</div>
{filterValue !== '' && (
<>
<span className={styles.searchResult}>
{`${matchItems.indexOf(currentItem) + 1}/${matchItems.length}`}
</span>
<div className={styles.divider} />
<button className={styles.searchAction} onClick={onSelectLast}>
<Arrow direction={'up'} />
</button>
<button className={styles.searchAction} onClick={onSelectNext}>
<Arrow direction={'down'} />
</button>
<button className={styles.searchAction} onClick={onClear}>
<Close />
</button>
</>
)}
</div>
<div>
<VTree
data={parsedVNodeData}
maxDeep={maxDeep}
highlightValue={filterValue}
onRendered={onRendered}
collapsedNodes={collapsedNodes}
onCollapseNode={setCollapsedNodes}
scrollToItem={currentItem}
selectItem={selectComp}
onSelectItem={handleSelectComp}
/>
</div>
</div>
<div>
<div onMouseDown={doResize} className={styles.resizeLine} />
</div>
<div>
<ComponentInfo
name={selectComp ? selectComp.name.itemName : null}
attrs={selectComp ? componentAttrs : {}}
parents={parents}
id={selectComp ? selectComp.id : null}
source={selectComp ? source : null}
onClickParent={handleClickParent}
/>
</div>
</div>
</PickElementContext.Provider>
</ViewSourceContext.Provider>
);
}
export default memo(Panel);

View File

@ -1,19 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Panel from './Panel';
// 这里导出 Panel 为了加载 Panel.less
export default Panel;

View File

@ -1,35 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf8">
<meta http-equiv="Content-Security-Policy"
content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' ">
<style>
html {
width: 100%;
height: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow-y: hidden;
}
#root {
width: 100%;
height: 100%;
}
</style>
<script src="inula.development.js"></script>
</head>
<body>
<div id="root"></div>
<script src="panel.js"></script>
</body>
</html>

View File

@ -1,16 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
export * from './panelConnection';

View File

@ -1,74 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { packagePayload } from '../utils/transferUtils';
import { DevToolPanel, InitDevToolPageConnection } from '../utils/constants';
let connection;
const callbacks = [];
export function addBackgroundMessageListener(func: (message) => void) {
callbacks.push(func);
}
export function removeBackgroundMessageListener(func: (message) => void) {
const index = callbacks.indexOf(func);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
export function initBackgroundConnection(type) {
if (!isDev) {
try {
connection = chrome.runtime.connect({ name: type });
const notice = message => {
callbacks.forEach(func => {
func(message);
});
};
// 监听 background 消息
connection.onMessage.addListener(notice);
// 页面打开后发送初始化请求
postMessageToBackground(InitDevToolPageConnection);
} catch (e) {
console.error('create connection failer');
console.error(e);
}
}
}
let reconnectionTimes = 0;
export function postMessageToBackground(type: string, data?: any, inulaX?: boolean) {
try {
const payload = data
? { type, tabId: chrome.devtools.inspectedWindow.tabId, data }
: { type, tabId: chrome.devtools.inspectedWindow.tabId };
connection.postMessage(packagePayload(payload, DevToolPanel));
} catch (e) {
// 可能出现 port 关闭的场景,需要重新建立连接,增加可靠性
if (reconnectionTimes === 20) {
reconnectionTimes = 0;
console.error('reconnect failed');
return;
}
console.error(e);
reconnectionTimes++;
// 重新连接
initBackgroundConnection(inulaX ? 'panelX' : 'panel');
// 初始化成功后才会重新发送消息
postMessageToBackground(type, data);
}
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import Inula, { useState } from 'openinula';
import { Modal } from './Modal';
import { highlight, sendMessage } from './utils';
function executeAction(storeId: string, name: string, args: any[]) {
sendMessage({
type: 'inulax run action',
tabId: chrome.devtools.inspectedWindow.tabId,
storeId,
action: name,
args,
});
}
function queryAction(storeId: string, name: string, args: any[]) {
sendMessage({
type: 'inulax queue action',
tabId: chrome.devtools.inspectedWindow.tabId,
storeId,
action: name,
args,
});
}
export function ActionRunner({ foo, storeId, actionName }) {
const [data, setState] = useState({
modal: false,
gatheredAttrs: [],
query: false,
});
const modalIsOpen = data.modal;
const gatheredAttrs = data.gatheredAttrs;
function setData(val) {
const newData = {
modal: data.modal,
gatheredAttrs: data.gatheredAttrs,
};
Object.entries(val).forEach(([key, value]) => (newData[key] = value));
setState(newData as any);
}
const plainFunction = foo.replace(/\{.*}/gms, '');
const attributes = plainFunction
.replace(/^.*\(/g, '')
.replace(/\).*$/, '')
.split(/, ?/)
.filter((item, index) => index > 0);
return (
<>
<span
title={'Run action'}
onClick={() => {
if (attributes.length > 0) {
setData({ modal: false, gatheredAttrs: [], query: false });
} else {
executeAction(storeId, actionName, gatheredAttrs);
}
}}
>
<b
style={{
cursor: 'pointer',
}}
>
<span
title={'Add to action queue'}
onClick={e => {
e.preventDefault();
if (attributes.len > 0) {
setData({ modal: true, gatheredAttrs: [], query: true });
} else {
queryAction(storeId, actionName, gatheredAttrs);
}
}}
>
{' '}
</span>
</b>
<span>
<i>{plainFunction}</i>
{' {...}'}
</span>
</span>
{modalIsOpen ? (
<Modal
closeModal={() => {
setData({ modal: false });
}}
then={data => {
if (gatheredAttrs.length === attributes.length - 1) {
setData({ modal: false });
executeAction(storeId, actionName, gatheredAttrs.concat(data));
} else {
setData({
gatheredAttrs: gatheredAttrs.concat([data]),
});
}
}}
>
<h3>{data.query ? 'Query action:' : 'Run action:'}</h3>
<p>{highlight(plainFunction, attributes[gatheredAttrs.length])}</p>
</Modal>
) : null}
</>
);
}

View File

@ -1,309 +0,0 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { useState } from 'openinula';
import styles from './PanelX.less';
import { Tree } from './Tree';
import { displayValue, omit } from './utils';
type Mutation = {
mutation: boolean;
items?: Mutation[];
attributes?: { [key: string]: Mutation };
values?: Mutation[];
entries?: Mutation[];
from?: any;
to?: any;
};
export function DiffTree({
mutation,
indent = 0,
index = '',
expand = false,
search = '',
forcedExpand = false,
omitAttrs = [],
doNotDisplayIcon = false,
forcedLabel = null,
className,
}: {
mutation: Mutation;
indent: number;
index?: string | number;
expand?: boolean;
search: string;
forcedExpand?: boolean;
omitAttrs: string[];
doNotDisplayIcon?: boolean;
forcedLabel?: string | number | null;
className?: string;
}) {
if (omitAttrs.length && mutation.attributes) {
mutation.attributes = omit(mutation.attributes, ...omitAttrs);
mutation.from = mutation.from && omit(mutation.from, ...omitAttrs);
mutation.to = mutation.to && omit(mutation.to, ...omitAttrs);
}
const [expanded, setExpanded] = useState(expand);
const deleted = mutation.mutation && !('to' in mutation);
const newValue = mutation.mutation && !('from' in mutation);
const mutated = mutation.mutation;
const isArray = mutated && mutation.items;
const isObject = mutated && mutation.attributes;
const isMap = mutated && mutation.entries;
const isSet = mutated && mutation.values;
const isPrimitive = !isArray && !isObject && !isMap && !isSet;
if (!mutated) {
return (
<Tree
data={mutation.to}
indent={indent}
search={search}
expand={expand}
forcedExpand={forcedExpand}
omitAttrs={omitAttrs}
forcedLabel={forcedLabel}
/>
);
}
if (newValue) {
return (
<Tree
data={mutation.to}
indent={indent}
search={search}
expand={expand}
forcedExpand={forcedExpand}
className={styles.added}
omitAttrs={omitAttrs}
forcedLabel={forcedLabel}
/>
);
}
if (deleted) {
return (
<Tree
data={mutation.from}
indent={indent}
search={search}
expand={expand}
forcedExpand={forcedExpand}
className={styles.deleted}
omitAttrs={omitAttrs}
forcedLabel={forcedLabel}
/>
);
}
return (
<div
style={{
fontFamily: 'monospace',
}}
className={`${
expanded ? 'expanded' : `not-expanded ${mutated && !isPrimitive && !expanded ? styles.changed : ''}`
}`}
onClick={e => {
e.stopPropagation();
}}
>
<span
style={{
cursor: 'pointer',
}}
onClick={() => {
setExpanded(!expanded);
}}
>
{new Array(Math.max(indent, 0)).fill(<span>&nbsp;</span>)}
{isPrimitive ? (
// 如果两个 value 是基本变量并且不同,则简单显示不同点
<div
onClick={e => {
e.stopPropagation();
}}
>
<Tree
data={mutation.from}
indent={indent}
search={search}
index={index}
className={styles.deleted}
omitAttrs={omitAttrs}
/>
<Tree
data={mutation.to}
indent={indent}
search={search}
index={index}
className={styles.added}
omitAttrs={omitAttrs}
/>
</div>
) : (
// 如果至少有一个是复杂变量,则需要展开按钮
<>
{forcedExpand ? '' : expanded ? <span></span> : <span></span>}
{index === 0 || index ? <b className={styles.purple}>{displayValue(index, search)}: </b> : ''}
{isArray ? (
// 如果都是数组进行比较
expanded ? (
[
Array(Math.max(mutation.from.length, mutation.to.length))
.fill(true)
.map((i, index) => {
return (
<div>
{mutation.items[index].mutation ? (
<DiffTree
mutation={{
...mutation.items[index],
to: mutation.to[index],
}}
indent={indent}
search={search}
omitAttrs={omitAttrs}
forcedLabel={index}
/>
) : (
<Tree
data={mutation.to[index]}
indent={indent}
search={search}
index={index}
className={styles.default}
omitAttrs={omitAttrs}
/>
)}
</div>
);
}),
]
) : (
forcedLabel || `Array(${mutation.to?.length})`
)
) : isSet ? (
expanded ? (
<div>
<div>{forcedLabel || `Set(${mutation.to?.values.length})`}</div>
{Array(Math.max(mutation.from?.values.length, mutation.to?.values.length))
.fill(true)
.map((i, index) => (
<div>
{mutation.values[index].mutation ? (
<DiffTree
mutation={{
...mutation.values[index],
}}
indent={indent + 2}
search={search}
omitAttrs={omitAttrs}
/>
) : (
<Tree
data={mutation.to?.values[index]}
indent={indent + 2}
search={search}
className={styles.default}
omitAttrs={omitAttrs}
/>
)}
</div>
))}
</div>
) : (
<span>{forcedLabel || `Set(${mutation.to?.values.length})`}</span>
)
) : isMap ? (
expanded ? (
<>
<span>{forcedLabel || `Map(${mutation.to?.entries.length})`}</span>
{Array(Math.max(mutation.from?.entries.length, mutation.to?.entries.length))
.fill(true)
.map((i, index) =>
mutation.entries[index].mutation ? (
<div>
<DiffTree
mutation={{
...mutation.entries[index],
}}
indent={indent + 2}
search={search}
omitAttrs={omitAttrs}
forcedLabel={'[map item]'}
/>
</div>
) : (
<div>
<Tree
data={mutation.to?.entries[index]}
indent={indent + 2}
search={search}
className={styles.default}
omitAttrs={omitAttrs}
forcedLabel={'[map item]'}
/>
</div>
)
)}
</>
) : (
<span>{forcedLabel || `Map(${mutation.to?.entries.length})`}</span>
)
) : expanded ? (
// 如果都是 object 进行比较
Object.entries(mutation.attributes).map(([key, item]) => {
return item.mutation ? (
<span onClick={e => e.stopPropagation()}>
{
<DiffTree
mutation={item}
index={key}
indent={indent}
search={search}
className={!expanded && mutated ? '' : styles.changed}
omitAttrs={omitAttrs}
/>
}
</span>
) : (
<span onClick={e => e.stopPropagation()}>
{
<Tree
data={mutation.to[key]}
index={key}
indent={indent}
search={search}
className={styles.default}
omitAttrs={omitAttrs}
/>
}
</span>
);
})
) : (
forcedLabel || '{ ... }'
)}
</>
)}
</span>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More