Compare commits
No commits in common. "master" and "dev" have entirely different histories.
|
@ -1,3 +1,3 @@
|
|||
**/node_modules
|
||||
**/build/
|
||||
build/
|
||||
*.d.ts
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
name: 模板名称,在新建issue时候能看到
|
||||
about: 模板描述,对应的issue模板卡片展示时候能看到,介绍模板
|
||||
---
|
||||
|
||||
**标题:** [请在此处填写 issue 标题]
|
||||
|
||||
**问题描述:** [请描述问题背景、可能的原因、如何重现问题以及相关解决方案]
|
||||
|
||||
**关联的 Issues:** [请列出与此问题相关的 issue 编号]
|
||||
|
||||
**检查项:**
|
||||
|
||||
- [ ] 该问题的存在已经确认
|
||||
- [ ] 这个问题的重要性和紧迫性已经确认
|
||||
- [ ] 该问题的责任人已经确认
|
||||
- [ ] 该问题的解决方案已经确认
|
||||
- [ ] 该问题的测试方法已经确认
|
||||
|
||||
**标签:** [请为该问题添加合适的标签]
|
||||
|
||||
**责任人:** [请为该问题分配责任人]
|
||||
|
||||
**优先级:** [请给出该问题的优先级]
|
|
@ -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]
|
|
@ -1,12 +0,0 @@
|
|||
**PR 描述:** [请描述提交此 PR 的背景、目的、所做的更改以及如何测试此 PR]
|
||||
|
||||
**关联的 Issues:** [请列出与此 PR 相关的 issue 编号]
|
||||
|
||||
**检查项(无需修改,提交后界面上可勾选):**
|
||||
- [ ] 代码已经被审查
|
||||
- [ ] 代码符合项目的代码标准和最佳实践
|
||||
- [ ] 代码已经通过所有测试用例
|
||||
- [ ] 代码不影响现有功能的正常使用
|
||||
- [ ] 文档已经同步更新
|
||||
|
||||
**截图(可选):** [提供相关的截图或 gif 动画]
|
|
@ -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]
|
|
@ -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
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run commitlint
|
|
@ -1,4 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run lint-commit
|
|
@ -1,3 +0,0 @@
|
|||
**/build
|
||||
*.md
|
||||
*.html
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
- .*
|
|
@ -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
|
|
@ -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
|
|
@ -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
127
LICENSE
|
@ -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 License,Version 2
|
||||
|
||||
Mulan Permissive Software License,Version 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 IT’S 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 License,Version 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.
|
|
@ -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.
|
87
README.md
87
README.md
|
@ -1,87 +0,0 @@
|
|||
# openInula 开源项目
|
||||
|
||||
## 项目介绍
|
||||
|
||||
单词 Inula(发音为:[ˈɪnjʊlə]),意为一类旋覆花属菊科的植物。openInula 是一款用于构建用户界面的 JavaScript 库,提供响应式 API 帮助开发者简单高效构建 Web 页面,比传统虚拟 DOM 方式渲染效率提升30%以上!同时 openInula 提供与 React 保持一致的 API,并且提供5大常用功能组件:状态管理器、路由、国际化、请求组件、应用脚手架,以便开发者高效、高质量的构筑基于 openInula 的前端产品。
|
||||
|
||||
## 技术架构
|
||||
|
||||

|
||||
|
||||
## 核心能力
|
||||
|
||||
### 响应式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)
|
||||
|
||||
* 微信公众号:
|
||||
|
||||

|
|
@ -1,28 +1,3 @@
|
|||
# 0.0.2 版本
|
||||
|
||||
## 新特性
|
||||
|
||||
- **inula-request** 新增响应体中获取完整 URL 能力。
|
||||
|
||||
## API变更
|
||||
|
||||
无
|
||||
|
||||
## Bug修复
|
||||
|
||||
- **inula** 解决事件卸载失败问题。
|
||||
- **inula** 解决 mouseover 重复触发 mouseEnter 事件问题。
|
||||
- **inula** 大数组合并使用 concat。
|
||||
- **inula** 事件支持 defaultPrevented 属性
|
||||
|
||||
## CVE漏洞修复
|
||||
|
||||
无
|
||||
|
||||
## 已知问题
|
||||
|
||||
无
|
||||
|
||||
# 0.0.1 版本
|
||||
|
||||
## 新特性
|
||||
|
|
117
package.json
117
package.json
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
# openinula + vite
|
||||
|
||||
该模板提供了 `openinula` 工作在 `vite`的基础配置。
|
||||
> 请注意由于Vite插件有node版本限制,请使用`node -v`命令确认node版本大于等于node v18。
|
|
@ -11,7 +11,7 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"openinula": "^0.1.1"
|
||||
"inulajs": "^0.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"openinula": "^0.1.1"
|
||||
"inulajs": "^0.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
|
|
|
@ -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>
|
|
@ -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'));
|
|
@ -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'],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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;
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"description": "simple reactive app template."
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
# openinula + vite
|
||||
|
||||
该模板提供了 `openinula` 工作在 `vite`的基础配置。
|
||||
> 请注意由于Vite插件有node版本限制,请使用`node -v`命令确认node版本大于等于node v18。
|
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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'));
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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'],
|
||||
},
|
||||
};
|
|
@ -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([
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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声明规范
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
webpack/
|
||||
public/
|
|
@ -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)
|
||||
};
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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({
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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引用
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}));
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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;
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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' });
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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 中存在空 index,nextItems 已经耗尽,不用处理
|
||||
// 确保新旧数组中 item 的 index 值不会发生变化
|
||||
this.lastRenderItemToIndexMap = nextRenderItemToIndexMap;
|
||||
return nextRenderItems;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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';
|
|
@ -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;
|
||||
}
|
|
@ -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}> 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);
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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' });
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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();
|
||||
},
|
||||
],
|
||||
// 请求某个节点的 props,hooks
|
||||
[
|
||||
RequestComponentAttrs,
|
||||
data => {
|
||||
parseCompAttrs(data);
|
||||
},
|
||||
],
|
||||
// 修改 props,hooks
|
||||
[
|
||||
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();
|
|
@ -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 };
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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>
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 的 name,userKey,indentation 属性不会发生变化
|
||||
// 但是在跳转到新页面时, 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);
|
|
@ -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;
|
|
@ -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>
|
|
@ -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';
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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> </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
Loading…
Reference in New Issue