Commit e1efc4fe authored by liujiangnan's avatar liujiangnan

feat: 添加组件

parent a3f5ba9f
No preview for this file type
var fs = require('fs-extra');
var file = require('./util/file');
module.exports = {
load() {
},
unload() {
},
replaceDirUuid: function (path, dbpath) {
Editor.log('开始检查:' + path);
file.findDirUuid(path);
Editor.log('资源检查完成');
},
messages: {
'checkFileName'() {
var uuids = Editor.Selection.curSelection('asset');
uuids.forEach((uuid) => {
var dir_path = Editor.assetdb._uuid2path[uuid];
if (fs.existsSync(dir_path)) {
this.replaceDirUuid(dir_path, Editor.assetdb.uuidToUrl(uuid));
}
});
},
},
}
\ No newline at end of file
{
"name": "check-file-name",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.0.1",
"dependencies": {
"node-uuid": "1.4.8"
}
},
"node_modules/node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
"deprecated": "Use uuid module instead",
"bin": {
"uuid": "bin/uuid"
}
}
},
"dependencies": {
"node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
}
}
}
{
"name": "check-file-name",
"version": "0.0.1",
"description": "check-file-name",
"author": "Cocos Creator",
"main": "main.js",
"main-menu": {
"i18n:MAIN_MENU.package.title/check-file-name": {
"message": "check-file-name:checkFileName"
}
},
"dependencies": {
"node-uuid": "1.4.8"
}
}
var fs = require("fs-extra");
var path = require("path");
var AppName = ""
module.exports = {
/**
* 递归目录 检查文件名
* 参考 https://docs.cocos.com/creator/manual/zh/advanced-topics/meta.html
*/
findDirUuid: function (dir) {
if(AppName == '') {
AppName = this.getRootDirName(dir);
if(AppName != "") {
Editor.log("AppName: " + AppName);
}
}
var stat = fs.statSync(dir);
if (!stat.isDirectory()) {
return;
}
var subpaths = fs.readdirSync(dir),
subpath;
for (var i = 0; i < subpaths.length; ++i) {
if (subpaths[i][0] === ".") {
continue;
}
subpath = path.join(dir, subpaths[i]);
stat = fs.statSync(subpath);
if (stat.isDirectory()) {
this.findDirUuid(subpath);
} else if (stat.isFile()) {
var metastr = subpath.substr(subpath.length - 5, 5);
if (metastr != ".meta") {
this.check(AppName, subpaths[i]);
}
}
}
},
getRootDirName: function (path) {
let pArr = path.split("/");
let assteIndex = -1;
pArr.find((item, index) => {
if(item == 'assets') {
assteIndex = index;
return true
} else {
return false
}
})
if(assteIndex > 0 && assteIndex<(pArr.length-1)) {
return pArr[assteIndex + 1];
} else {
return ""
}
},
check: (appName, filePath) => {
if(escape(filePath).indexOf("%u")>=0) {
Editor.log(`检测到[中文或中文符号]命名的文件: ${filePath}`);
}
if (!/^\S*$/.test(filePath)) {
Editor.log(`检测到[包含空格]命名的文件: ${filePath}`);
}
if (/[-]/.test(filePath)) {
Editor.log(`检测到存在[包含减号(-)]命名的文件: ${filePath}`);
}
if(filePath.indexOf(appName) == -1 && (filePath.endsWith(".js") || filePath.endsWith(".ts"))) {
Editor.log(`检测到[不包含包名(${appName})]的文件: ${filePath}`);
}
}
};
# 配置文件
config.json
\ No newline at end of file
### 1.3.0.20210929
1. 代码重构
2. 引入 eazax 框架
3. 查找性能大幅提升
4. 文件解析优化,增强稳定性
5. 去除「通过 uuid 查找」面板
6. 加入检查更新机制
### 1.2.0.20210405
1. 修复“双开编辑器卡死”的问题
2. 代码优化,提高查找速度
3. 增加多语言(英语)支持
4. 支持自定义快捷键(设置面板)
### 1.1.0-20201112
1. 增加“通过 uuid 查找”功能(新增面板)
2. 优化代码
### 1.0.0-20201103
1. 首次发布
MIT License
Copyright (c) 2021 陈皮皮
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.
# References Finder
## Introduction
[Cocos Creator Editor Extension]
**Find asset references in seconds by one press.**
## Open Source
This extension is an open source project, here is the git repository: [https://gitee.com/ifaswind/ccc-references-finder](https://gitee.com/ifaswind/ccc-references-finder)
If you like this project, don't forget to star [![star](https://gitee.com/ifaswind/ccc-references-finder/badge/star.svg?theme=dark)](https://gitee.com/ifaswind/ccc-references-finder/stargazers)!
*If you have any usage problems, just create an issue on Gitee or add my WeChat `im_chenpipi` and leave a message.*
## Screenshots
![screenshot-1](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/screenshot-1.png)
![screenshot-2](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/screenshot-2.png)
![settings-panel](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/settings-panel.png)
## Environment
Platform: Windows、macOS
Engine: Cocos Creator 2.x
## Download & Installation
### Install from Cocos Store
You can find this extension in Cocos Store now, click on *Extension -> Cocos Store* option to open the Cocos Store.
Enter "**References Finder**" in the search bar, find it and then install it.
![cocos-store](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/cocos-store.png)
*References Finder: [https://store.cocos.com/app/detail/2531](https://store.cocos.com/app/detail/2531)*
### Download from git repository
Click [here](https://gitee.com/ifaswind/ccc-references-finder/releases) or go to the release panel, download the latest version package of this extension.
And then unzip the package:
- Windows: Unzip to `C:\Users\${your username}\.CocosCreator\packages\`
- macOS: Unzip to `~/.CocosCreator/packages/`
For example, on my Windows computer, the full path of `package.json` file should be `C:\Users\Shaun\.CocosCreator\packages\ccc-references-finder\package.json`.
## Usage
### Find Asset References
1. Select any asset(s) in Asset Panel.
2. Press the hotkey (The default is `F6`) or click on *Extension -> References Finder -> Find Selected* option to find references.
3. Then all references of asset would be printed on Console Panel.
### Settings
Click on *Extension -> References Finder -> Setting* option to open the setting panel.
- **Show Details**: Show more details(node, component, property)
- **Fold Result**: Fold result in one log
In the **Hotkey** option, you can choose a hotkey(shortcut, for finding references of current selected asset) in preset list, or you can customize one in **Custom** option by yourself.
One thing you should know, not every keys/keys-combinations can be used, because some keys/keys-combinations have been used by the system or Cocos Creator.
*Accelerator reference: [https://www.electronjs.org/docs/api/accelerator](https://www.electronjs.org/docs/api/accelerator)*
🥳 Enjoy!
---
# 公众号
## 菜鸟小栈
😺 我是陈皮皮,一个还在不断学习的游戏开发者,一个热爱分享的 Cocos Star Writer。
🎨 这是我的个人公众号,专注但不仅限于游戏开发和前端技术分享。
💖 每一篇原创都非常用心,你的关注就是我原创的动力!
> Input and output.
![](https://gitee.com/ifaswind/image-storage/raw/master/weixin/official-account.png)
\ No newline at end of file
# 引用查找器
## 介绍
[Cocos Creator 编辑器扩展]
**一键查找资源的所有引用,可精确到预制体或场景中的节点、组件和属性(不包括代码中的动态引用)。**
## 开源
本扩展项目完全开源,仓库地址:[https://gitee.com/ifaswind/ccc-references-finder](https://gitee.com/ifaswind/ccc-references-finder)
如果你觉得这个项目还不错,请不要忘记点 [![star](https://gitee.com/ifaswind/ccc-references-finder/badge/star.svg?theme=dark)](https://gitee.com/ifaswind/ccc-references-finder/stargazers)!
*如有使用上的问题,可以在 Gitee 仓库中提 Issue 或者添加我的微信 `im_chenpipi` 并留言。*
## 截图
![screenshot-1](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/screenshot-1.png)
![screenshot-2](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/screenshot-2.png)
![settings-panel](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/settings-panel.png)
## 运行环境
平台:Windows、macOS
引擎:Cocos Creator 2.x
## 下载 & 安装
### 扩展商店安装
本扩展已上架 Cocos 商店,点击 Cocos Creator 编辑器顶部菜单栏中的 *扩展 -> 扩展商店* 即可打开扩展商店。
在页面上方的搜索栏中搜索“**引用查找器**”就可以找到本插件,进入详情页即可直接安装(建议安装到全局)。
![cocos-store](https://gitee.com/ifaswind/image-storage/raw/master/repositories/ccc-references-finder/cocos-store.png)
*引用查找器:[https://store.cocos.com/app/detail/2531](https://store.cocos.com/app/detail/2531)*
### 自行下载安装
[此处](https://gitee.com/ifaswind/ccc-references-finder/releases)或仓库发行版处下载最新的扩展压缩包。
下载完成后将压缩包解压:
- Windows:解压到 `C:\Users\${你的用户名}\.CocosCreator\packages\` 目录下
- macOS:解压到 `~/.CocosCreator/packages/` 目录下
以 Windows 为例,扩展的 `package.json` 文件在我的电脑上的完整目录为 `C:\Users\Shaun\.CocosCreator\packages\ccc-references-finder\package.json`。
## 使用说明
### 一键查找资源引用
安装本扩展后,在资源管理器中选中任意资源,按下快捷键(默认为 `F6`)即可获取该资源的所有引用(不包括代码中的动态引用),结果将在控制台中以文本的方式打印出来。
> 查找快捷键可进入扩展的设置面板进行修改
### 设置
点击编辑器顶部菜单栏中的 *扩展 -> 引用查找器 -> 设置* 即可打开扩展的设置面板。
- **自动展开结果**:切换不同的结果展示方式(自动展开或手动展开)
- **精确到组件属性**:引用信息精确到预制体或场景中的节点上的组件和属性(有的话)
在设置面板中你可以更换快速查找引用的快捷键,也可以自定义一个自己喜欢的快捷键。
不过需要注意的是,并非所有的按键都可以使用,因为有些快捷键已被系统或 Cocos Creator 占用。
*键盘快捷键参考:[https://www.electronjs.org/docs/api/accelerator](https://www.electronjs.org/docs/api/accelerator)*
---
# 公众号
## 菜鸟小栈
😺 我是陈皮皮,一个还在不断学习的游戏开发者,一个热爱分享的 Cocos Star Writer。
🎨 这是我的个人公众号,专注但不仅限于游戏开发和前端技术分享。
💖 每一篇原创都非常用心,你的关注就是我原创的动力!
> Input and output.
![](https://gitee.com/ifaswind/image-storage/raw/master/weixin/official-account.png)
## 游戏开发交流群
皮皮创建了一个**游戏开发交流群**,供小伙伴们交流开发经验、问题求助和摸鱼(划掉)。
感兴趣的小伙伴可以添加我微信 `im_chenpipi` 并留言 `加群`
\ No newline at end of file
module.exports = {
'name': 'References Finder',
'find': 'Find Current Selected',
'find-panel': 'Find Panel',
'settings': 'Settings',
'check-update': 'Check Update',
// update
'current-latest': 'Currently the latest version!',
'has-new-version': 'New version found!',
'local-version': 'Local version: ',
'latest-version': 'Latest version: ',
'git-releases': 'Releases: https://gitee.com/ifaswind/ccc-references-finder/releases',
'cocos-store': 'Cocos Store: https://store.cocos.com/app/detail/2531',
// main
'please-select-assets': 'Please select asset(s) in Asset Panel first',
'invalid-uuid': 'Invalid uuid',
'not-support-folder': 'Does not support folder',
'find-asset-refs': 'Find references',
'no-refs': 'No references found',
'scene': 'Scene',
'prefab': 'Prefab',
'animation': 'Animation',
'material': 'Material',
'font': 'Font',
'node': 'Node',
'component': 'Component',
'property': 'Property',
'result': 'Reference result',
'node-refs': 'Node References',
'asset-refs': 'Asset References',
'asset-info': 'Asset Info',
'asset-type': 'Type: ',
'asset-uuid': 'Uuid: ',
'asset-url': 'Url: ',
'asset-path': 'Path: ',
// settings
'none': 'None',
'select-key': 'Hotkey',
'select-key-tooltip': 'Chose a hotkey to open the search bar quickly',
'custom-key': 'Custom',
'custom-key-placeholder': 'Choose a hotkey above or customize one by yourself',
'custom-key-tooltip': 'You can also customize your own hotkey',
'auto-check-update': 'Auto Check Update',
'auto-check-update-tooltip': 'Check if there is a new version when the extension is loaded',
'reference': '· Hotkey customization reference: ',
'accelerator': 'Keyboard Shortcuts',
'repository': '· Git repository of this extension: ',
'apply': 'Apply',
'quote-error': 'Do not use double quotes!',
'custom-key-error': 'Please specify a hotkey!',
'print-details': 'Show Details',
'print-details-tooltip': 'Show more details(node, component, property)',
'print-folding': 'Fold Result',
'print-folding-tooltip': 'Fold result in one log',
};
module.exports = {
'name': '引用查找器',
'find': '查找当前选中资源',
'find-panel': '查找面板',
'settings': '设置',
'check-update': '检查更新',
// update
'current-latest': '当前已是最新版本!',
'has-new-version': '发现新版本!',
'local-version': '本地版本:',
'latest-version': '最新版本:',
'git-releases': '发行版:https://gitee.com/ifaswind/ccc-references-finder/releases',
'cocos-store': 'Cocos 商店:https://store.cocos.com/app/detail/2531',
// main
'please-select-assets': '请先在资源管理器中选择需要查找引用的资源',
'invalid-uuid': '无效的 uuid',
'not-support-folder': '暂不支持查找文件夹',
'find-asset-refs': '查找资源引用',
'no-refs': '没有找到该资源的引用',
'scene': '场景',
'prefab': '预制体',
'animation': '动画',
'material': '材质',
'font': '字体',
'node': '节点',
'component': '组件',
'property': '属性',
'result': '引用查找结果',
'node-refs': '节点引用',
'asset-refs': '资源引用',
'asset-info': '资源信息',
'asset-type': 'Type:',
'asset-uuid': 'Uuid:',
'asset-url': 'Url:',
'asset-path': 'Path:',
// settings
'none': '',
'select-key': '快捷键',
'select-key-tooltip': '选择一个快速打开搜索栏的快捷键',
'custom-key': '自定义',
'custom-key-placeholder': '在上方选择一个快捷键或自定义一个快捷键',
'custom-key-tooltip': '自定义快速打开搜索栏的快捷键',
'auto-check-update': '自动检查更新',
'auto-check-update-tooltip': '扩展启动时自动检查是否有新版本',
'reference': '· 快捷键自定义请参考:',
'accelerator': '键盘快捷键',
'repository': '· 本扩展的 Git 仓库:',
'apply': '应用',
'quote-error': '请勿使用双引号!',
'custom-key-error': '请指定一个快捷键!',
'print-details': '展示详情',
'print-details-tooltip': '引用查找结果精确到节点、组件和属性',
'print-folding': '折叠结果',
'print-folding-tooltip': '引用查找结果将需要手动展开,拯救你的控制台',
};
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"checkJs": false
},
"exclude": [
"node_modules"
],
"include": [
"lib/**/*",
"src/**/*",
"typings/**/*"
]
}
\ No newline at end of file
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var Stream = _interopDefault(require('stream'));
var http = _interopDefault(require('http'));
var Url = _interopDefault(require('url'));
var https = _interopDefault(require('https'));
var zlib = _interopDefault(require('zlib'));
// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
// fix for "Readable" isn't a named export issue
const Readable = Stream.Readable;
const BUFFER = Symbol('buffer');
const TYPE = Symbol('type');
class Blob {
constructor() {
this[TYPE] = '';
const blobParts = arguments[0];
const options = arguments[1];
const buffers = [];
let size = 0;
if (blobParts) {
const a = blobParts;
const length = Number(a.length);
for (let i = 0; i < length; i++) {
const element = a[i];
let buffer;
if (element instanceof Buffer) {
buffer = element;
} else if (ArrayBuffer.isView(element)) {
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
} else if (element instanceof ArrayBuffer) {
buffer = Buffer.from(element);
} else if (element instanceof Blob) {
buffer = element[BUFFER];
} else {
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
}
size += buffer.length;
buffers.push(buffer);
}
}
this[BUFFER] = Buffer.concat(buffers);
let type = options && options.type !== undefined && String(options.type).toLowerCase();
if (type && !/[^\u0020-\u007E]/.test(type)) {
this[TYPE] = type;
}
}
get size() {
return this[BUFFER].length;
}
get type() {
return this[TYPE];
}
text() {
return Promise.resolve(this[BUFFER].toString());
}
arrayBuffer() {
const buf = this[BUFFER];
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
return Promise.resolve(ab);
}
stream() {
const readable = new Readable();
readable._read = function () {};
readable.push(this[BUFFER]);
readable.push(null);
return readable;
}
toString() {
return '[object Blob]';
}
slice() {
const size = this.size;
const start = arguments[0];
const end = arguments[1];
let relativeStart, relativeEnd;
if (start === undefined) {
relativeStart = 0;
} else if (start < 0) {
relativeStart = Math.max(size + start, 0);
} else {
relativeStart = Math.min(start, size);
}
if (end === undefined) {
relativeEnd = size;
} else if (end < 0) {
relativeEnd = Math.max(size + end, 0);
} else {
relativeEnd = Math.min(end, size);
}
const span = Math.max(relativeEnd - relativeStart, 0);
const buffer = this[BUFFER];
const slicedBuffer = buffer.slice(relativeStart, relativeStart + span);
const blob = new Blob([], { type: arguments[2] });
blob[BUFFER] = slicedBuffer;
return blob;
}
}
Object.defineProperties(Blob.prototype, {
size: { enumerable: true },
type: { enumerable: true },
slice: { enumerable: true }
});
Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
value: 'Blob',
writable: false,
enumerable: false,
configurable: true
});
/**
* fetch-error.js
*
* FetchError interface for operational errors
*/
/**
* Create FetchError instance
*
* @param String message Error message for human
* @param String type Error type for machine
* @param String systemError For Node.js system error
* @return FetchError
*/
function FetchError(message, type, systemError) {
Error.call(this, message);
this.message = message;
this.type = type;
// when err.type is `system`, err.code contains system error code
if (systemError) {
this.code = this.errno = systemError.code;
}
// hide custom error implementation details from end-users
Error.captureStackTrace(this, this.constructor);
}
FetchError.prototype = Object.create(Error.prototype);
FetchError.prototype.constructor = FetchError;
FetchError.prototype.name = 'FetchError';
let convert;
try {
convert = require('encoding').convert;
} catch (e) {}
const INTERNALS = Symbol('Body internals');
// fix an issue where "PassThrough" isn't a named export for node <10
const PassThrough = Stream.PassThrough;
/**
* Body mixin
*
* Ref: https://fetch.spec.whatwg.org/#body
*
* @param Stream body Readable stream
* @param Object opts Response options
* @return Void
*/
function Body(body) {
var _this = this;
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$size = _ref.size;
let size = _ref$size === undefined ? 0 : _ref$size;
var _ref$timeout = _ref.timeout;
let timeout = _ref$timeout === undefined ? 0 : _ref$timeout;
if (body == null) {
// body is undefined or null
body = null;
} else if (isURLSearchParams(body)) {
// body is a URLSearchParams
body = Buffer.from(body.toString());
} else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
// body is ArrayBuffer
body = Buffer.from(body);
} else if (ArrayBuffer.isView(body)) {
// body is ArrayBufferView
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
} else if (body instanceof Stream) ; else {
// none of the above
// coerce to string then buffer
body = Buffer.from(String(body));
}
this[INTERNALS] = {
body,
disturbed: false,
error: null
};
this.size = size;
this.timeout = timeout;
if (body instanceof Stream) {
body.on('error', function (err) {
const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err);
_this[INTERNALS].error = error;
});
}
}
Body.prototype = {
get body() {
return this[INTERNALS].body;
},
get bodyUsed() {
return this[INTERNALS].disturbed;
},
/**
* Decode response as ArrayBuffer
*
* @return Promise
*/
arrayBuffer() {
return consumeBody.call(this).then(function (buf) {
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
});
},
/**
* Return raw response as Blob
*
* @return Promise
*/
blob() {
let ct = this.headers && this.headers.get('content-type') || '';
return consumeBody.call(this).then(function (buf) {
return Object.assign(
// Prevent copying
new Blob([], {
type: ct.toLowerCase()
}), {
[BUFFER]: buf
});
});
},
/**
* Decode response as json
*
* @return Promise
*/
json() {
var _this2 = this;
return consumeBody.call(this).then(function (buffer) {
try {
return JSON.parse(buffer.toString());
} catch (err) {
return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json'));
}
});
},
/**
* Decode response as text
*
* @return Promise
*/
text() {
return consumeBody.call(this).then(function (buffer) {
return buffer.toString();
});
},
/**
* Decode response as buffer (non-spec api)
*
* @return Promise
*/
buffer() {
return consumeBody.call(this);
},
/**
* Decode response as text, while automatically detecting the encoding and
* trying to decode to UTF-8 (non-spec api)
*
* @return Promise
*/
textConverted() {
var _this3 = this;
return consumeBody.call(this).then(function (buffer) {
return convertBody(buffer, _this3.headers);
});
}
};
// In browsers, all properties are enumerable.
Object.defineProperties(Body.prototype, {
body: { enumerable: true },
bodyUsed: { enumerable: true },
arrayBuffer: { enumerable: true },
blob: { enumerable: true },
json: { enumerable: true },
text: { enumerable: true }
});
Body.mixIn = function (proto) {
for (const name of Object.getOwnPropertyNames(Body.prototype)) {
// istanbul ignore else: future proof
if (!(name in proto)) {
const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
Object.defineProperty(proto, name, desc);
}
}
};
/**
* Consume and convert an entire Body to a Buffer.
*
* Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
*
* @return Promise
*/
function consumeBody() {
var _this4 = this;
if (this[INTERNALS].disturbed) {
return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
}
this[INTERNALS].disturbed = true;
if (this[INTERNALS].error) {
return Body.Promise.reject(this[INTERNALS].error);
}
let body = this.body;
// body is null
if (body === null) {
return Body.Promise.resolve(Buffer.alloc(0));
}
// body is blob
if (isBlob(body)) {
body = body.stream();
}
// body is buffer
if (Buffer.isBuffer(body)) {
return Body.Promise.resolve(body);
}
// istanbul ignore if: should never happen
if (!(body instanceof Stream)) {
return Body.Promise.resolve(Buffer.alloc(0));
}
// body is stream
// get ready to actually consume the body
let accum = [];
let accumBytes = 0;
let abort = false;
return new Body.Promise(function (resolve, reject) {
let resTimeout;
// allow timeout on slow response body
if (_this4.timeout) {
resTimeout = setTimeout(function () {
abort = true;
reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout'));
}, _this4.timeout);
}
// handle stream errors
body.on('error', function (err) {
if (err.name === 'AbortError') {
// if the request was aborted, reject with this Error
abort = true;
reject(err);
} else {
// other errors, such as incorrect content-encoding
reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err));
}
});
body.on('data', function (chunk) {
if (abort || chunk === null) {
return;
}
if (_this4.size && accumBytes + chunk.length > _this4.size) {
abort = true;
reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size'));
return;
}
accumBytes += chunk.length;
accum.push(chunk);
});
body.on('end', function () {
if (abort) {
return;
}
clearTimeout(resTimeout);
try {
resolve(Buffer.concat(accum, accumBytes));
} catch (err) {
// handle streams that have accumulated too much data (issue #414)
reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err));
}
});
});
}
/**
* Detect buffer encoding and convert to target encoding
* ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
*
* @param Buffer buffer Incoming buffer
* @param String encoding Target encoding
* @return String
*/
function convertBody(buffer, headers) {
if (typeof convert !== 'function') {
throw new Error('The package `encoding` must be installed to use the textConverted() function');
}
const ct = headers.get('content-type');
let charset = 'utf-8';
let res, str;
// header
if (ct) {
res = /charset=([^;]*)/i.exec(ct);
}
// no charset in content type, peek at response body for at most 1024 bytes
str = buffer.slice(0, 1024).toString();
// html5
if (!res && str) {
res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
}
// html4
if (!res && str) {
res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
if (!res) {
res = /<meta[\s]+?content=(['"])(.+?)\1[\s]+?http-equiv=(['"])content-type\3/i.exec(str);
if (res) {
res.pop(); // drop last quote
}
}
if (res) {
res = /charset=(.*)/i.exec(res.pop());
}
}
// xml
if (!res && str) {
res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
}
// found charset
if (res) {
charset = res.pop();
// prevent decode issues when sites use incorrect encoding
// ref: https://hsivonen.fi/encoding-menu/
if (charset === 'gb2312' || charset === 'gbk') {
charset = 'gb18030';
}
}
// turn raw buffers into a single utf-8 buffer
return convert(buffer, 'UTF-8', charset).toString();
}
/**
* Detect a URLSearchParams object
* ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143
*
* @param Object obj Object to detect by type or brand
* @return String
*/
function isURLSearchParams(obj) {
// Duck-typing as a necessary condition.
if (typeof obj !== 'object' || typeof obj.append !== 'function' || typeof obj.delete !== 'function' || typeof obj.get !== 'function' || typeof obj.getAll !== 'function' || typeof obj.has !== 'function' || typeof obj.set !== 'function') {
return false;
}
// Brand-checking and more duck-typing as optional condition.
return obj.constructor.name === 'URLSearchParams' || Object.prototype.toString.call(obj) === '[object URLSearchParams]' || typeof obj.sort === 'function';
}
/**
* Check if `obj` is a W3C `Blob` object (which `File` inherits from)
* @param {*} obj
* @return {boolean}
*/
function isBlob(obj) {
return typeof obj === 'object' && typeof obj.arrayBuffer === 'function' && typeof obj.type === 'string' && typeof obj.stream === 'function' && typeof obj.constructor === 'function' && typeof obj.constructor.name === 'string' && /^(Blob|File)$/.test(obj.constructor.name) && /^(Blob|File)$/.test(obj[Symbol.toStringTag]);
}
/**
* Clone body given Res/Req instance
*
* @param Mixed instance Response or Request instance
* @return Mixed
*/
function clone(instance) {
let p1, p2;
let body = instance.body;
// don't allow cloning a used body
if (instance.bodyUsed) {
throw new Error('cannot clone body after it is used');
}
// check that body is a stream and not form-data object
// note: we can't clone the form-data object without having it as a dependency
if (body instanceof Stream && typeof body.getBoundary !== 'function') {
// tee instance body
p1 = new PassThrough();
p2 = new PassThrough();
body.pipe(p1);
body.pipe(p2);
// set instance body to teed body and return the other teed body
instance[INTERNALS].body = p1;
body = p2;
}
return body;
}
/**
* Performs the operation "extract a `Content-Type` value from |object|" as
* specified in the specification:
* https://fetch.spec.whatwg.org/#concept-bodyinit-extract
*
* This function assumes that instance.body is present.
*
* @param Mixed instance Any options.body input
*/
function extractContentType(body) {
if (body === null) {
// body is null
return null;
} else if (typeof body === 'string') {
// body is string
return 'text/plain;charset=UTF-8';
} else if (isURLSearchParams(body)) {
// body is a URLSearchParams
return 'application/x-www-form-urlencoded;charset=UTF-8';
} else if (isBlob(body)) {
// body is blob
return body.type || null;
} else if (Buffer.isBuffer(body)) {
// body is buffer
return null;
} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
// body is ArrayBuffer
return null;
} else if (ArrayBuffer.isView(body)) {
// body is ArrayBufferView
return null;
} else if (typeof body.getBoundary === 'function') {
// detect form data input from form-data module
return `multipart/form-data;boundary=${body.getBoundary()}`;
} else if (body instanceof Stream) {
// body is stream
// can't really do much about this
return null;
} else {
// Body constructor defaults other things to string
return 'text/plain;charset=UTF-8';
}
}
/**
* The Fetch Standard treats this as if "total bytes" is a property on the body.
* For us, we have to explicitly get it with a function.
*
* ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
*
* @param Body instance Instance of Body
* @return Number? Number of bytes, or null if not possible
*/
function getTotalBytes(instance) {
const body = instance.body;
if (body === null) {
// body is null
return 0;
} else if (isBlob(body)) {
return body.size;
} else if (Buffer.isBuffer(body)) {
// body is buffer
return body.length;
} else if (body && typeof body.getLengthSync === 'function') {
// detect form data input from form-data module
if (body._lengthRetrievers && body._lengthRetrievers.length == 0 || // 1.x
body.hasKnownLength && body.hasKnownLength()) {
// 2.x
return body.getLengthSync();
}
return null;
} else {
// body is stream
return null;
}
}
/**
* Write a Body to a Node.js WritableStream (e.g. http.Request) object.
*
* @param Body instance Instance of Body
* @return Void
*/
function writeToStream(dest, instance) {
const body = instance.body;
if (body === null) {
// body is null
dest.end();
} else if (isBlob(body)) {
body.stream().pipe(dest);
} else if (Buffer.isBuffer(body)) {
// body is buffer
dest.write(body);
dest.end();
} else {
// body is stream
body.pipe(dest);
}
}
// expose Promise
Body.Promise = global.Promise;
/**
* headers.js
*
* Headers class offers convenient helpers
*/
const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/;
const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
function validateName(name) {
name = `${name}`;
if (invalidTokenRegex.test(name) || name === '') {
throw new TypeError(`${name} is not a legal HTTP header name`);
}
}
function validateValue(value) {
value = `${value}`;
if (invalidHeaderCharRegex.test(value)) {
throw new TypeError(`${value} is not a legal HTTP header value`);
}
}
/**
* Find the key in the map object given a header name.
*
* Returns undefined if not found.
*
* @param String name Header name
* @return String|Undefined
*/
function find(map, name) {
name = name.toLowerCase();
for (const key in map) {
if (key.toLowerCase() === name) {
return key;
}
}
return undefined;
}
const MAP = Symbol('map');
class Headers {
/**
* Headers class
*
* @param Object headers Response headers
* @return Void
*/
constructor() {
let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined;
this[MAP] = Object.create(null);
if (init instanceof Headers) {
const rawHeaders = init.raw();
const headerNames = Object.keys(rawHeaders);
for (const headerName of headerNames) {
for (const value of rawHeaders[headerName]) {
this.append(headerName, value);
}
}
return;
}
// We don't worry about converting prop to ByteString here as append()
// will handle it.
if (init == null) ; else if (typeof init === 'object') {
const method = init[Symbol.iterator];
if (method != null) {
if (typeof method !== 'function') {
throw new TypeError('Header pairs must be iterable');
}
// sequence<sequence<ByteString>>
// Note: per spec we have to first exhaust the lists then process them
const pairs = [];
for (const pair of init) {
if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') {
throw new TypeError('Each header pair must be iterable');
}
pairs.push(Array.from(pair));
}
for (const pair of pairs) {
if (pair.length !== 2) {
throw new TypeError('Each header pair must be a name/value tuple');
}
this.append(pair[0], pair[1]);
}
} else {
// record<ByteString, ByteString>
for (const key of Object.keys(init)) {
const value = init[key];
this.append(key, value);
}
}
} else {
throw new TypeError('Provided initializer must be an object');
}
}
/**
* Return combined header value given name
*
* @param String name Header name
* @return Mixed
*/
get(name) {
name = `${name}`;
validateName(name);
const key = find(this[MAP], name);
if (key === undefined) {
return null;
}
return this[MAP][key].join(', ');
}
/**
* Iterate over all headers
*
* @param Function callback Executed for each item with parameters (value, name, thisArg)
* @param Boolean thisArg `this` context for callback function
* @return Void
*/
forEach(callback) {
let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
let pairs = getHeaders(this);
let i = 0;
while (i < pairs.length) {
var _pairs$i = pairs[i];
const name = _pairs$i[0],
value = _pairs$i[1];
callback.call(thisArg, value, name, this);
pairs = getHeaders(this);
i++;
}
}
/**
* Overwrite header values given name
*
* @param String name Header name
* @param String value Header value
* @return Void
*/
set(name, value) {
name = `${name}`;
value = `${value}`;
validateName(name);
validateValue(value);
const key = find(this[MAP], name);
this[MAP][key !== undefined ? key : name] = [value];
}
/**
* Append a value onto existing header
*
* @param String name Header name
* @param String value Header value
* @return Void
*/
append(name, value) {
name = `${name}`;
value = `${value}`;
validateName(name);
validateValue(value);
const key = find(this[MAP], name);
if (key !== undefined) {
this[MAP][key].push(value);
} else {
this[MAP][name] = [value];
}
}
/**
* Check for header name existence
*
* @param String name Header name
* @return Boolean
*/
has(name) {
name = `${name}`;
validateName(name);
return find(this[MAP], name) !== undefined;
}
/**
* Delete all header values given name
*
* @param String name Header name
* @return Void
*/
delete(name) {
name = `${name}`;
validateName(name);
const key = find(this[MAP], name);
if (key !== undefined) {
delete this[MAP][key];
}
}
/**
* Return raw headers (non-spec api)
*
* @return Object
*/
raw() {
return this[MAP];
}
/**
* Get an iterator on keys.
*
* @return Iterator
*/
keys() {
return createHeadersIterator(this, 'key');
}
/**
* Get an iterator on values.
*
* @return Iterator
*/
values() {
return createHeadersIterator(this, 'value');
}
/**
* Get an iterator on entries.
*
* This is the default iterator of the Headers object.
*
* @return Iterator
*/
[Symbol.iterator]() {
return createHeadersIterator(this, 'key+value');
}
}
Headers.prototype.entries = Headers.prototype[Symbol.iterator];
Object.defineProperty(Headers.prototype, Symbol.toStringTag, {
value: 'Headers',
writable: false,
enumerable: false,
configurable: true
});
Object.defineProperties(Headers.prototype, {
get: { enumerable: true },
forEach: { enumerable: true },
set: { enumerable: true },
append: { enumerable: true },
has: { enumerable: true },
delete: { enumerable: true },
keys: { enumerable: true },
values: { enumerable: true },
entries: { enumerable: true }
});
function getHeaders(headers) {
let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value';
const keys = Object.keys(headers[MAP]).sort();
return keys.map(kind === 'key' ? function (k) {
return k.toLowerCase();
} : kind === 'value' ? function (k) {
return headers[MAP][k].join(', ');
} : function (k) {
return [k.toLowerCase(), headers[MAP][k].join(', ')];
});
}
const INTERNAL = Symbol('internal');
function createHeadersIterator(target, kind) {
const iterator = Object.create(HeadersIteratorPrototype);
iterator[INTERNAL] = {
target,
kind,
index: 0
};
return iterator;
}
const HeadersIteratorPrototype = Object.setPrototypeOf({
next() {
// istanbul ignore if
if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) {
throw new TypeError('Value of `this` is not a HeadersIterator');
}
var _INTERNAL = this[INTERNAL];
const target = _INTERNAL.target,
kind = _INTERNAL.kind,
index = _INTERNAL.index;
const values = getHeaders(target, kind);
const len = values.length;
if (index >= len) {
return {
value: undefined,
done: true
};
}
this[INTERNAL].index = index + 1;
return {
value: values[index],
done: false
};
}
}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())));
Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, {
value: 'HeadersIterator',
writable: false,
enumerable: false,
configurable: true
});
/**
* Export the Headers object in a form that Node.js can consume.
*
* @param Headers headers
* @return Object
*/
function exportNodeCompatibleHeaders(headers) {
const obj = Object.assign({ __proto__: null }, headers[MAP]);
// http.request() only supports string as Host header. This hack makes
// specifying custom Host header possible.
const hostHeaderKey = find(headers[MAP], 'Host');
if (hostHeaderKey !== undefined) {
obj[hostHeaderKey] = obj[hostHeaderKey][0];
}
return obj;
}
/**
* Create a Headers object from an object of headers, ignoring those that do
* not conform to HTTP grammar productions.
*
* @param Object obj Object of headers
* @return Headers
*/
function createHeadersLenient(obj) {
const headers = new Headers();
for (const name of Object.keys(obj)) {
if (invalidTokenRegex.test(name)) {
continue;
}
if (Array.isArray(obj[name])) {
for (const val of obj[name]) {
if (invalidHeaderCharRegex.test(val)) {
continue;
}
if (headers[MAP][name] === undefined) {
headers[MAP][name] = [val];
} else {
headers[MAP][name].push(val);
}
}
} else if (!invalidHeaderCharRegex.test(obj[name])) {
headers[MAP][name] = [obj[name]];
}
}
return headers;
}
const INTERNALS$1 = Symbol('Response internals');
// fix an issue where "STATUS_CODES" aren't a named export for node <10
const STATUS_CODES = http.STATUS_CODES;
/**
* Response class
*
* @param Stream body Readable stream
* @param Object opts Response options
* @return Void
*/
class Response {
constructor() {
let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
Body.call(this, body, opts);
const status = opts.status || 200;
const headers = new Headers(opts.headers);
if (body != null && !headers.has('Content-Type')) {
const contentType = extractContentType(body);
if (contentType) {
headers.append('Content-Type', contentType);
}
}
this[INTERNALS$1] = {
url: opts.url,
status,
statusText: opts.statusText || STATUS_CODES[status],
headers,
counter: opts.counter
};
}
get url() {
return this[INTERNALS$1].url || '';
}
get status() {
return this[INTERNALS$1].status;
}
/**
* Convenience property representing if the request ended normally
*/
get ok() {
return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300;
}
get redirected() {
return this[INTERNALS$1].counter > 0;
}
get statusText() {
return this[INTERNALS$1].statusText;
}
get headers() {
return this[INTERNALS$1].headers;
}
/**
* Clone this response
*
* @return Response
*/
clone() {
return new Response(clone(this), {
url: this.url,
status: this.status,
statusText: this.statusText,
headers: this.headers,
ok: this.ok,
redirected: this.redirected
});
}
}
Body.mixIn(Response.prototype);
Object.defineProperties(Response.prototype, {
url: { enumerable: true },
status: { enumerable: true },
ok: { enumerable: true },
redirected: { enumerable: true },
statusText: { enumerable: true },
headers: { enumerable: true },
clone: { enumerable: true }
});
Object.defineProperty(Response.prototype, Symbol.toStringTag, {
value: 'Response',
writable: false,
enumerable: false,
configurable: true
});
const INTERNALS$2 = Symbol('Request internals');
// fix an issue where "format", "parse" aren't a named export for node <10
const parse_url = Url.parse;
const format_url = Url.format;
const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;
/**
* Check if a value is an instance of Request.
*
* @param Mixed input
* @return Boolean
*/
function isRequest(input) {
return typeof input === 'object' && typeof input[INTERNALS$2] === 'object';
}
function isAbortSignal(signal) {
const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal);
return !!(proto && proto.constructor.name === 'AbortSignal');
}
/**
* Request class
*
* @param Mixed input Url or Request instance
* @param Object init Custom options
* @return Void
*/
class Request {
constructor(input) {
let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let parsedURL;
// normalize input
if (!isRequest(input)) {
if (input && input.href) {
// in order to support Node.js' Url objects; though WHATWG's URL objects
// will fall into this branch also (since their `toString()` will return
// `href` property anyway)
parsedURL = parse_url(input.href);
} else {
// coerce input to a string before attempting to parse
parsedURL = parse_url(`${input}`);
}
input = {};
} else {
parsedURL = parse_url(input.url);
}
let method = init.method || input.method || 'GET';
method = method.toUpperCase();
if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) {
throw new TypeError('Request with GET/HEAD method cannot have body');
}
let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null;
Body.call(this, inputBody, {
timeout: init.timeout || input.timeout || 0,
size: init.size || input.size || 0
});
const headers = new Headers(init.headers || input.headers || {});
if (inputBody != null && !headers.has('Content-Type')) {
const contentType = extractContentType(inputBody);
if (contentType) {
headers.append('Content-Type', contentType);
}
}
let signal = isRequest(input) ? input.signal : null;
if ('signal' in init) signal = init.signal;
if (signal != null && !isAbortSignal(signal)) {
throw new TypeError('Expected signal to be an instanceof AbortSignal');
}
this[INTERNALS$2] = {
method,
redirect: init.redirect || input.redirect || 'follow',
headers,
parsedURL,
signal
};
// node-fetch-only options
this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20;
this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true;
this.counter = init.counter || input.counter || 0;
this.agent = init.agent || input.agent;
}
get method() {
return this[INTERNALS$2].method;
}
get url() {
return format_url(this[INTERNALS$2].parsedURL);
}
get headers() {
return this[INTERNALS$2].headers;
}
get redirect() {
return this[INTERNALS$2].redirect;
}
get signal() {
return this[INTERNALS$2].signal;
}
/**
* Clone this request
*
* @return Request
*/
clone() {
return new Request(this);
}
}
Body.mixIn(Request.prototype);
Object.defineProperty(Request.prototype, Symbol.toStringTag, {
value: 'Request',
writable: false,
enumerable: false,
configurable: true
});
Object.defineProperties(Request.prototype, {
method: { enumerable: true },
url: { enumerable: true },
headers: { enumerable: true },
redirect: { enumerable: true },
clone: { enumerable: true },
signal: { enumerable: true }
});
/**
* Convert a Request to Node.js http request options.
*
* @param Request A Request instance
* @return Object The options object to be passed to http.request
*/
function getNodeRequestOptions(request) {
const parsedURL = request[INTERNALS$2].parsedURL;
const headers = new Headers(request[INTERNALS$2].headers);
// fetch step 1.3
if (!headers.has('Accept')) {
headers.set('Accept', '*/*');
}
// Basic fetch
if (!parsedURL.protocol || !parsedURL.hostname) {
throw new TypeError('Only absolute URLs are supported');
}
if (!/^https?:$/.test(parsedURL.protocol)) {
throw new TypeError('Only HTTP(S) protocols are supported');
}
if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) {
throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8');
}
// HTTP-network-or-cache fetch steps 2.4-2.7
let contentLengthValue = null;
if (request.body == null && /^(POST|PUT)$/i.test(request.method)) {
contentLengthValue = '0';
}
if (request.body != null) {
const totalBytes = getTotalBytes(request);
if (typeof totalBytes === 'number') {
contentLengthValue = String(totalBytes);
}
}
if (contentLengthValue) {
headers.set('Content-Length', contentLengthValue);
}
// HTTP-network-or-cache fetch step 2.11
if (!headers.has('User-Agent')) {
headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
}
// HTTP-network-or-cache fetch step 2.15
if (request.compress && !headers.has('Accept-Encoding')) {
headers.set('Accept-Encoding', 'gzip,deflate');
}
let agent = request.agent;
if (typeof agent === 'function') {
agent = agent(parsedURL);
}
if (!headers.has('Connection') && !agent) {
headers.set('Connection', 'close');
}
// HTTP-network fetch step 4.2
// chunked encoding is handled by Node.js
return Object.assign({}, parsedURL, {
method: request.method,
headers: exportNodeCompatibleHeaders(headers),
agent
});
}
/**
* abort-error.js
*
* AbortError interface for cancelled requests
*/
/**
* Create AbortError instance
*
* @param String message Error message for human
* @return AbortError
*/
function AbortError(message) {
Error.call(this, message);
this.type = 'aborted';
this.message = message;
// hide custom error implementation details from end-users
Error.captureStackTrace(this, this.constructor);
}
AbortError.prototype = Object.create(Error.prototype);
AbortError.prototype.constructor = AbortError;
AbortError.prototype.name = 'AbortError';
// fix an issue where "PassThrough", "resolve" aren't a named export for node <10
const PassThrough$1 = Stream.PassThrough;
const resolve_url = Url.resolve;
/**
* Fetch function
*
* @param Mixed url Absolute url or Request instance
* @param Object opts Fetch options
* @return Promise
*/
function fetch(url, opts) {
// allow custom promise
if (!fetch.Promise) {
throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
}
Body.Promise = fetch.Promise;
// wrap http.request into fetch
return new fetch.Promise(function (resolve, reject) {
// build request object
const request = new Request(url, opts);
const options = getNodeRequestOptions(request);
const send = (options.protocol === 'https:' ? https : http).request;
const signal = request.signal;
let response = null;
const abort = function abort() {
let error = new AbortError('The user aborted a request.');
reject(error);
if (request.body && request.body instanceof Stream.Readable) {
request.body.destroy(error);
}
if (!response || !response.body) return;
response.body.emit('error', error);
};
if (signal && signal.aborted) {
abort();
return;
}
const abortAndFinalize = function abortAndFinalize() {
abort();
finalize();
};
// send request
const req = send(options);
let reqTimeout;
if (signal) {
signal.addEventListener('abort', abortAndFinalize);
}
function finalize() {
req.abort();
if (signal) signal.removeEventListener('abort', abortAndFinalize);
clearTimeout(reqTimeout);
}
if (request.timeout) {
req.once('socket', function (socket) {
reqTimeout = setTimeout(function () {
reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout'));
finalize();
}, request.timeout);
});
}
req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
finalize();
});
req.on('response', function (res) {
clearTimeout(reqTimeout);
const headers = createHeadersLenient(res.headers);
// HTTP fetch step 5
if (fetch.isRedirect(res.statusCode)) {
// HTTP fetch step 5.2
const location = headers.get('Location');
// HTTP fetch step 5.3
const locationURL = location === null ? null : resolve_url(request.url, location);
// HTTP fetch step 5.5
switch (request.redirect) {
case 'error':
reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect'));
finalize();
return;
case 'manual':
// node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL.
if (locationURL !== null) {
// handle corrupted header
try {
headers.set('Location', locationURL);
} catch (err) {
// istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request
reject(err);
}
}
break;
case 'follow':
// HTTP-redirect fetch step 2
if (locationURL === null) {
break;
}
// HTTP-redirect fetch step 5
if (request.counter >= request.follow) {
reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
finalize();
return;
}
// HTTP-redirect fetch step 6 (counter increment)
// Create a new Request object.
const requestOpts = {
headers: new Headers(request.headers),
follow: request.follow,
counter: request.counter + 1,
agent: request.agent,
compress: request.compress,
method: request.method,
body: request.body,
signal: request.signal,
timeout: request.timeout,
size: request.size
};
// HTTP-redirect fetch step 9
if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) {
reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
finalize();
return;
}
// HTTP-redirect fetch step 11
if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') {
requestOpts.method = 'GET';
requestOpts.body = undefined;
requestOpts.headers.delete('content-length');
}
// HTTP-redirect fetch step 15
resolve(fetch(new Request(locationURL, requestOpts)));
finalize();
return;
}
}
// prepare response
res.once('end', function () {
if (signal) signal.removeEventListener('abort', abortAndFinalize);
});
let body = res.pipe(new PassThrough$1());
const response_options = {
url: request.url,
status: res.statusCode,
statusText: res.statusMessage,
headers: headers,
size: request.size,
timeout: request.timeout,
counter: request.counter
};
// HTTP-network fetch step 12.1.1.3
const codings = headers.get('Content-Encoding');
// HTTP-network fetch step 12.1.1.4: handle content codings
// in following scenarios we ignore compression support
// 1. compression support is disabled
// 2. HEAD request
// 3. no Content-Encoding header
// 4. no content response (204)
// 5. content not modified response (304)
if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) {
response = new Response(body, response_options);
resolve(response);
return;
}
// For Node v6+
// Be less strict when decoding compressed responses, since sometimes
// servers send slightly invalid responses that are still accepted
// by common browsers.
// Always using Z_SYNC_FLUSH is what cURL does.
const zlibOptions = {
flush: zlib.Z_SYNC_FLUSH,
finishFlush: zlib.Z_SYNC_FLUSH
};
// for gzip
if (codings == 'gzip' || codings == 'x-gzip') {
body = body.pipe(zlib.createGunzip(zlibOptions));
response = new Response(body, response_options);
resolve(response);
return;
}
// for deflate
if (codings == 'deflate' || codings == 'x-deflate') {
// handle the infamous raw deflate response from old servers
// a hack for old IIS and Apache servers
const raw = res.pipe(new PassThrough$1());
raw.once('data', function (chunk) {
// see http://stackoverflow.com/questions/37519828
if ((chunk[0] & 0x0F) === 0x08) {
body = body.pipe(zlib.createInflate());
} else {
body = body.pipe(zlib.createInflateRaw());
}
response = new Response(body, response_options);
resolve(response);
});
return;
}
// for br
if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') {
body = body.pipe(zlib.createBrotliDecompress());
response = new Response(body, response_options);
resolve(response);
return;
}
// otherwise, use response as-is
response = new Response(body, response_options);
resolve(response);
});
writeToStream(req, request);
});
}
/**
* Redirect code matching
*
* @param Number code Status code
* @return Boolean
*/
fetch.isRedirect = function (code) {
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
};
// expose Promise
fetch.Promise = global.Promise;
module.exports = exports = fetch;
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = exports;
exports.Headers = Headers;
exports.Request = Request;
exports.Response = Response;
exports.FetchError = FetchError;
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "ccc-references-finder",
"version": "1.3.0.20210929",
"description": "一键查找资源的所有引用,可精确到预制体或场景中的节点、组件和属性(不包括代码中的动态引用)。",
"author": {
"name": "陈皮皮 (ifaswind)",
"email": "952157129@qq.com",
"url": "https://chenpipi.cn",
"wechat": "im_chenpipi",
"git-home": "https://gitee.com/ifaswind",
"official-account": "公众号「菜鸟小栈」"
},
"repository": "https://gitee.com/ifaswind/ccc-references-finder",
"license": "MIT",
"main": "src/main/index.js",
"main-menu": {
"i18n:MAIN_MENU.package.title/i18n:ccc-references-finder.name/i18n:ccc-references-finder.find": {
"message": "ccc-references-finder:find-current-selection",
"icon": "/images/search.png",
"accelerator": "F6"
},
"i18n:MAIN_MENU.package.title/i18n:ccc-references-finder.name/i18n:ccc-references-finder.settings": {
"message": "ccc-references-finder:open-settings-panel",
"icon": "/images/settings.png"
},
"i18n:MAIN_MENU.package.title/i18n:ccc-references-finder.name/i18n:ccc-references-finder.check-update": {
"message": "ccc-references-finder:menu-check-update",
"icon": "/images/update.png"
},
"i18n:MAIN_MENU.package.title/i18n:ccc-references-finder.name/v1.3.0.20210929": {
"message": "ccc-references-finder:menu-version",
"icon": "/images/version.png"
}
},
"reload": {
"renderer": [],
"ignore": [
"config.json",
"CHANGELOG.md",
"README.md",
"README.en.md"
]
}
}
\ No newline at end of file
const Path = require('path');
const Fs = require('fs');
const PackageUtil = require('../eazax/package-util');
/** 配置文件路径 */
const CONFIG_PATH = Path.join(__dirname, '../../config.json');
/** package.json 的路径 */
const PACKAGE_PATH = Path.join(__dirname, '../../package.json');
/** 包名 */
const PACKAGE_NAME = PackageUtil.name;
/** 快捷键行为 */
const ACTION_NAME = 'find';
/** package.json 中的菜单项 key */
const MENU_ITEM_KEY = `i18n:MAIN_MENU.package.title/i18n:${PACKAGE_NAME}.name/i18n:${PACKAGE_NAME}.${ACTION_NAME}`;
/**
* 配置管理器
*/
const ConfigManager = {
/**
* 默认配置
*/
get defaultConfig() {
return {
version: '1.1',
printDetails: true,
printFolding: true,
autoCheckUpdate: true,
};
},
/**
* 读取配置
*/
get() {
// 配置
const config = ConfigManager.defaultConfig;
if (Fs.existsSync(CONFIG_PATH)) {
const localConfig = JSON.parse(Fs.readFileSync(CONFIG_PATH));
for (const key in config) {
if (localConfig[key] !== undefined) {
config[key] = localConfig[key];
}
}
}
// 快捷键
config.hotkey = ConfigManager.getAccelerator();
// Done
return config;
},
/**
* 保存配置
* @param {*} value 配置
*/
set(value) {
// 配置
const config = ConfigManager.defaultConfig;
for (const key in config) {
if (value[key] !== undefined) {
config[key] = value[key];
}
}
Fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
// 快捷键
ConfigManager.setAccelerator(value.hotkey);
},
/**
* 获取快捷键
* @returns {string}
*/
getAccelerator() {
const package = JSON.parse(Fs.readFileSync(PACKAGE_PATH)),
item = package['main-menu'][MENU_ITEM_KEY];
return item['accelerator'] || '';
},
/**
* 设置快捷键
* @param {string} value
*/
setAccelerator(value) {
const package = JSON.parse(Fs.readFileSync(PACKAGE_PATH)),
item = package['main-menu'][MENU_ITEM_KEY];
if (value != undefined && value !== '') {
item['accelerator'] = value;
} else {
delete item['accelerator'];
}
Fs.writeFileSync(PACKAGE_PATH, JSON.stringify(package, null, 2));
},
};
module.exports = ConfigManager;
/**
* 浏览器工具
* @author 陈皮皮 (ifaswind)
* @version 20210729
*/
const BrowserUtil = {
/**
* 获取当前 Url 中的参数
* @param {string} key 键
* @returns {string}
*/
getUrlParam(key) {
if (!window || !window.location) {
return null;
}
const query = window.location.search.replace('?', '');
if (query === '') {
return null;
}
const substrings = query.split('&');
for (let i = 0; i < substrings.length; i++) {
const keyValue = substrings[i].split('=');
if (decodeURIComponent(keyValue[0]) === key) {
return decodeURIComponent(keyValue[1]);
}
}
return null;
},
/**
* 获取 Cookie 值
* @param {string} key 键
* @returns {string}
*/
getCookie(key) {
const regExp = new RegExp(`(^| )${key}=([^;]*)(;|$)`),
values = document.cookie.match(regExp);
if (values !== null) {
return values[2];
}
return null;
},
/**
* 设置 Cookie
* @param {string} key 键
* @param {string | number | boolean} value 值
* @param {string} expires 过期时间(GMT)
*/
setCookie(key, value, expires) {
let keyValues = `${key}=${encodeURIComponent(value)};`;
if (expires) {
keyValues += `expires=${expires};`;
}
document.cookie = keyValues;
},
};
module.exports = BrowserUtil;
/**
* 颜色工具
* @author 陈皮皮 (ifaswind)
* @version 20210725
*/
const ColorUtil = {
/**
* 将十六进制颜色值转为 RGB 格式
* @param {string} hex
* @returns {{ r: number, g: number, b: number }}
*/
hexToRGB(hex) {
// 是否为 HEX 格式
const regExp = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
if (!regExp.test(hex)) {
return null;
}
// 四位
if (hex.length === 4) {
const r = hex.slice(1, 2),
g = hex.slice(2, 3),
b = hex.slice(3, 4);
hex = `#${r}${r}${g}${g}${b}${b}`;
}
// 转换进制
const rgb = {
r: parseInt(`0x${hex.slice(1, 3)}`),
g: parseInt(`0x${hex.slice(3, 5)}`),
b: parseInt(`0x${hex.slice(5, 7)}`),
}
return rgb;
},
};
module.exports = ColorUtil;
/*
Cocos Creator 风格样式
版本: 20210911
作者: 陈皮皮 (ifaswind)
主页: https://gitee.com/ifaswind
公众号: 菜鸟小栈
*/
/* 属性容器 */
.properties {
width: 100%;
border: 1px solid #666;
border-radius: 3px;
padding: 5px;
box-sizing: border-box;
outline: 0;
display: flex;
flex-direction: column;
overflow: auto;
}
.properties > * {
margin: 2px 0;
}
.properties:first-child {
margin-top: 0;
}
.properties:last-child {
margin-bottom: 0;
}
/* 属性 */
.property {
width: 100%;
min-height: 23px;
box-sizing: border-box;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
}
/* 属性标签 */
.property > .label {
width: 38%;
min-width: 70px;
position: relative;
margin-left: 5px;
line-height: 23px;
font-size: 12px;
white-space: nowrap;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex-shrink: 0;
align-items: baseline;
justify-content: flex-start;
}
/* 属性标签文本:虚指 */
.property:hover > .label > .text {
color: #09f;
}
/* 属性标签文本:聚焦内部 */
.property:focus-within > .label > .text {
color: #fd942b;
}
/* 属性标签内容:聚焦内部 */
.property:focus-within > .content > * {
border-color: #fd942b;
}
/* tooltip */
.tooltip {
background-color: #333333;
padding: 5px 8px;
border: 1px solid #646464;
border-radius: 4px;
position: absolute;
top: -38px;
left: -5px;
visibility: hidden;
text-align: center;
z-index: 2;
}
/* tooltip 三角形 */
.tooltip::before,
.tooltip::after {
content: '';
display: block;
width: 0;
height: 0;
border: 6px solid transparent;
position: absolute;
left: 10px;
transform: rotate(-90deg);
}
/* tooltip 三角形 */
.tooltip::before {
border-right: 6px solid #333333;
top: 100%;
}
/* tooltip 三角形边框 */
.tooltip::after {
border-right: 6px solid #646464;
top: calc(100% + 1px);
z-index: -1;
}
/* 前一个元素虚指时的 tooltip */
*:hover + .tooltip {
visibility: visible;
}
/* 属性内容 */
.property > .content {
display: flex;
flex: 1;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
}
.property > .content > * {
width: auto;
min-width: 20px;
height: 21px;
flex: 1;
}
.property > .content > *:focus {
border-color: #fd942b;
}
/* 提示 */
.tip {
width: 100%;
min-height: 45px;
background: #333;
border: 1px solid #666;
border-radius: 3px;
padding: 12px 8px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: left;
color: #bdbdbd;
line-height: 17px;
font-size: 13px;
white-space: pre-line;
}
.tip > * {
display: inline-block;
}
/* *::滚动条 */
*::-webkit-scrollbar {
width: 11px;
}
/* *::滚动条-按钮 */
*::-webkit-scrollbar-button {
display: none;
}
/* *::滚动条-横竖交汇处 */
*::-webkit-scrollbar-corner {
display: none;
}
/* *::滚动条-轨道 */
*::-webkit-scrollbar-track {
/* background: rgba(0, 0, 0, 0.5); */
background: none !important;
background-clip: content-box;
border: 5px solid transparent;
}
/* *::滚动条-滑块 */
*::-webkit-scrollbar-thumb {
background: #7d7d7d;
background-clip: content-box;
border: 4px solid transparent;
border-radius: 6px;
}
/* *::滚动条-滑块:虚指 */
*::-webkit-scrollbar-thumb:hover {
background-color: #fd942b;
border: 3px solid transparent;
}
/*
Cocos Creator 风格标签 (橙黑)
版本: 20210725
作者: 陈皮皮 (ifaswind)
主页: https://gitee.com/ifaswind
公众号: 菜鸟小栈
*/
/* 下拉选择器 */
select {
background-color: #262626;
outline: none;
box-sizing: border-box;
border: 1px solid #171717;
border-radius: 100px;
padding: 0 8px;
font-size: 12px;
color: #bdbdbd;
cursor: pointer;
/* 替换默认的箭头 */
appearance: none;
-webkit-appearance: none;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA+ElEQVRYR+2VXw6CMAzGV3YUjSZ6CsI4l3gutnALH/xzEB8spokSQphbiwkv5RHafb9+awuYlR9YWd8ogDqgDqgDPx3w3h8A4FlV1UOysLz3O2stlmV5j+VHAbqu2yLijRIR8VzXdcOBaNu2KYriRDl93x+dc5e5/ChACGFjjBnIORBj8Q/A3jl3ZQFQcAiBKhgqz4GYiqdyklPAgeCKU5FJgFwnJOLZACkIqTgLgILnhOj9t9slE5N1BePunUKMv6Uajj0Fsbmfg5CIs68g5oRUfBEAJdOqBYCXdFUvBuCsZva/4B+H55zBnoKcQzkxCqAOqAOrO/AGwnWWIa30xvoAAAAASUVORK5CYII=);
background-size: 16px;
background-repeat: no-repeat;
background-position: right 3px center;
}
/* 下拉选择器:虚指 */
select:hover {
border-color: #888888;
}
/* 输入框,文本区域 */
input,
textarea {
background-color: #262626;
box-sizing: border-box;
padding: 0 5px;
border: 1px solid #171717;
border-radius: 3px;
color: #fd942b;
font-size: 12px;
outline: none;
}
/* 文本区域 */
textarea {
min-height: 40px;
resize: vertical;
}
/* 输入框,文本区域::占位符 */
input::placeholder,
textarea::placeholder {
font-size: 12px;
font-style: normal;
}
/* 数字输入框 */
input[type='number'] {
width: 50px !important;
}
/* 数字输入框::增减按钮 */
input[type='number']::-webkit-outer-spin-button,
input[type='number']::-webkit-inner-spin-button {
/* appearance: none; */
/* -webkit-appearance: none; */
/* margin: 0; */
margin-right: -2px;
}
/* 复选框 */
input[type='checkbox'] {
appearance: none;
-webkit-appearance: none;
width: 16px !important;
height: 16px !important;
min-width: 16px !important;
background-image: none;
background-color: #262626;
border: 1px solid #171717;
border-radius: 3px;
padding-left: 0;
position: relative !important;
flex: 0 !important;
margin: 0;
color: #fd942b;
outline: none;
cursor: pointer;
}
/* 复选框:勾选 */
input[type='checkbox']:checked::after {
width: 12px;
height: 12px;
display: inline-block;
content: '';
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABKUlEQVRYR+2W2w2DMAxF41ZdBnWzqjMQZkBdjWUqkiqUgCHOO4Ef+EECwTlO7AvATj7gZD67BA5bgW/fcLXdj/cwnfVxiMD4aVomYQILKTssUV0Aw3XVWKKqgFr2G0BLTZqWqCbggq8NIHkVgSD43A/FBag9J8MOJL+/hq6oQGjlbIYrMUNAVSAEg/28+iI7BW4IuObVJZAK3wj45tUmkANfBFyNs08uLBIKd70DgroWNY0WKAGfVmBUHwlLWuFqcRWl4OsWREioh2zxuukTYtWoPlrGcOyfnIEkc9s3gsb9QLg5hiUkIuB0EOVIRMJJAXUxaTsS4FaBaIlEuFMgWCID7hXwSmTCgwSsEgXgwQJ/CZSYheBRAlpCEP/20UGFHij6R5Qicgn8AIQ23JRIyuB1AAAAAElFTkSuQmCC);
background-size: 12px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* 输入框:虚指 */
input:hover {
border-color: #888888;
}
/* 滑动条 */
input[type='range'] {
appearance: none;
-webkit-appearance: none;
height: 4px !important;
background-color: #262626;
border: 1px solid #171717;
padding-left: 0;
padding-right: 0;
}
/* 滑动条:虚指|聚焦 */
input[type='range']:hover,
input[type='range']:focus,
*:focus-within input[type='range'] {
border-color: #171717 !important;
}
/* 滑动条::把手 */
input[type='range']::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
width: 12px;
height: 12px;
top: 2px;
background: #333;
box-sizing: border-box;
border: 2px solid #949494;
box-shadow: 0 1px 3px 1px #000 inset, 0 1px 1px 0 rgba(0, 0, 0, 0.9);
border-radius: 100%;
}
/* 滑动条::把手:虚指 */
input[type='range']::-webkit-slider-thumb:hover {
border-color: #bcbcbc;
cursor: pointer;
}
/* 滑动条::把手:激活 */
input[type='range']::-webkit-slider-thumb:active,
*:focus-within > input[type='range']::-webkit-slider-thumb {
color: #bdbdbd;
border-color: #fd942b !important;
cursor: ew-resize;
}
/* 取色器 */
input[type='color'] {
width: 16px;
height: 16px;
box-sizing: border-box;
border-radius: 1px;
padding: 0;
cursor: pointer;
}
/* 取色器::色板容器 */
input[type='color']::-webkit-color-swatch-wrapper {
padding: 0;
}
/* 取色器::色板 */
input[type='color']::-webkit-color-swatch {
border: none;
}
/* 超链接 */
a {
color: #fd942b;
text-decoration: none;
}
/* 超链接:虚指 */
a:hover {
text-decoration: underline;
}
/* 分割线 */
hr {
width: 100%;
height: 1px;
background-color: #666;
border: none;
margin: 10px 0 !important;
}
:root {
/* 背景颜色 */
--eazax-bg-color: #454545;
/* 主颜色 */
--eazax-main-color: #262626;
/* 强调色 */
--eazax-accent-color: #2e88fb;
/* 聚焦色 */
--eazax-focus-color: #fd942b;
/* 边框调色 */
--eazax-border-color: #171717;
/* 边框虚指调色 */
--eazax-border-hover-color: #888888;
/* 文本颜色 */
--eazax-font-color: #bdbdbd;
/* 内容颜色 */
--eazax-content-color: #fd942b;
}
const MainEvent = require('./main-event');
const { print, checkUpdate } = require('./editor-main-util');
/**
* (渲染进程)检查更新回调
* @param {Electron.IpcMainEvent} event
* @param {boolean} logWhatever 无论有无更新都打印提示
*/
function onCheckUpdateEvent(event, logWhatever) {
checkUpdate(logWhatever);
}
/**
* (渲染进程)打印事件回调
* @param {Electron.IpcMainEvent} event
* @param {'log' | 'info' | 'warn' | 'error' | any} type
* @param {any[]?} args
*/
function onPrintEvent(event, type) {
// print(type, ...args);
const args = [type];
for (let i = 2, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
print.apply(null, args);
}
/**
* 编辑器主进程套件 (依赖 Cocos Creator 编辑器)
* @author 陈皮皮 (ifaswind)
* @version 20210818
*/
const EditorMainKit = {
/**
* 注册
*/
register() {
MainEvent.on('check-update', onCheckUpdateEvent);
MainEvent.on('print', onPrintEvent);
},
/**
* 取消注册
*/
unregister() {
MainEvent.removeListener('check-update', onCheckUpdateEvent);
MainEvent.removeListener('print', onPrintEvent);
},
};
module.exports = EditorMainKit;
const I18n = require('./i18n');
const PackageUtil = require('./package-util');
const Updater = require('./updater');
/** 编辑器语言 */
const LANG = Editor.lang || Editor.I18n.getLanguage();
/** 包名 */
const PACKAGE_NAME = PackageUtil.name;
/** 扩展名称 */
const EXTENSION_NAME = I18n.get(LANG, 'name');
/**
* 编辑器主进程工具 (依赖 Cocos Creator 编辑器)
* @author 陈皮皮 (ifaswind)
* @version 20210929
*/
const EditorMainUtil = {
/**
* 语言
*/
get language() {
return LANG;
},
/**
* i18n
* @param {string} key 关键词
* @returns {string}
*/
translate(key) {
return I18n.get(LANG, key);
},
/**
* 打印信息到控制台(带标题)
* @param {'log' | 'info' | 'warn' | 'error' | any} type
* @param {any[]?} args
*/
print(type) {
const args = [`[${EXTENSION_NAME}]`];
for (let i = 1, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
const object = Editor.log ? Editor : console;
switch (type) {
case 'log': {
object.log.apply(object, args);
break;
}
case 'info': {
object.info.apply(object, args);
break;
}
case 'warn': {
object.warn.apply(object, args);
break;
}
case 'error': {
object.error.apply(object, args);
break;
}
default: {
args.splice(1, 0, type);
object.log.apply(object, args);
}
}
},
/**
* 打印信息到控制台(不带标题)
* @param {'log' | 'info' | 'warn' | 'error' | any} type
* @param {any[]?} args
*/
pureWithoutTitle(type) {
const args = [];
for (let i = 1, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
const object = Editor.log ? Editor : console;
switch (type) {
case 'log': {
object.log.apply(object, args);
break;
}
case 'info': {
object.info.apply(object, args);
break;
}
case 'warn': {
object.warn.apply(object, args);
break;
}
case 'error': {
object.error.apply(object, args);
break;
}
default: {
args.splice(1, 0, type);
object.log.apply(object, args);
}
}
},
/**
* 检查更新
* @param {boolean} logWhatever 无论有无更新都打印提示
*/
async checkUpdate(logWhatever) {
// 编辑器本次启动是否已经检查过了
if (!logWhatever && (Editor[PACKAGE_NAME] && Editor[PACKAGE_NAME].hasCheckUpdate)) {
return;
}
Editor[PACKAGE_NAME] = { hasCheckUpdate: true };
// 是否有新版本
const hasNewVersion = await Updater.check();
// 打印到控制台
const { print, translate } = EditorMainUtil;
const localVersion = Updater.getLocalVersion();
if (hasNewVersion) {
const remoteVersion = await Updater.getRemoteVersion();
print('info', translate('has-new-version'));
print('info', `${translate('local-version')}${localVersion}`);
print('info', `${translate('latest-version')}${remoteVersion}`);
print('info', translate('git-releases'));
print('info', translate('cocos-store'));
} else if (logWhatever) {
print('info', translate('current-latest'));
print('info', `${translate('local-version')}${localVersion}`);
}
},
/**
* (3.x)重新加载扩展
*/
async reload() {
const path = await Editor.Package.getPath(PACKAGE_NAME);
await Editor.Package.unregister(path);
await Editor.Package.register(path);
await Editor.Package.enable(path);
},
};
module.exports = EditorMainUtil;
const RendererEvent = require("./renderer-event");
/**
* 编辑器渲染进程套件 (依赖 Cocos Creator 编辑器)
* @author 陈皮皮 (ifaswind)
* @version 20210818
*/
const EditorRendererKit = {
/**
* 打印信息到 Creator 编辑器控制台
* @param {'log' | 'info' | 'warn' | 'error' | any} type
* @param {any[]?} args
*/
print(type) {
// return RendererEvent.send('print', type, ...args);
const args = ['print', type];
for (let i = 1, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
return RendererEvent.send.apply(RendererEvent, args);
},
};
module.exports = EditorRendererKit;
const Fs = require('fs');
const Path = require('path');
const { promisify } = require('util');
/**
* 文件工具 (Promise 化)
* @author 陈皮皮 (ifaswind)
* @version 20210818
*/
const FileUtil = {
/**
* 获取文件状态
* @param {Fs.PathLike} path 路径
* @returns {Promise<Fs.stats>}
*/
stat: promisify(Fs.stat),
/**
* 创建文件夹
* @param {Fs.PathLike} path 路径
* @param {Fs.MakeDirectoryOptions?} options 选项
* @returns {Promise<void>}
*/
mkdir: promisify(Fs.mkdir),
/**
* 读取文件夹
* @param {Fs.PathLike} path 路径
* @param {any} options 选项
* @returns {Promise<string[]>}
*/
readdir: promisify(Fs.readdir),
/**
* 移除文件夹
* @param {Fs.PathLike} path 路径
* @param {Fs.RmDirOptions?} options 选项
* @returns {Promise<void>}
*/
rmdir: promisify(Fs.rmdir),
/**
* 读取文件
* @param {Fs.PathLike} path 路径
* @param {any} options 选项
* @returns {Promise<Buffer>}
*/
readFile: promisify(Fs.readFile),
/**
* 创建文件
* @param {Fs.PathLike} path 路径
* @param {string | NodeJS.ArrayBufferView} data 数据
* @param {Fs.WriteFileOptions?} options 选项
* @returns {Promise<void>}
*/
writeFile: promisify(Fs.writeFile),
/**
* 移除文件
* @param {Fs.PathLike} path 路径
* @returns {Promise<void>}
*/
unlink: promisify(Fs.unlink),
/**
* 测试路径是否存在 (同步)
* @param {Fs.PathLike} path 路径
*/
existsSync: Fs.existsSync,
/**
* 复制文件/文件夹
* @param {Fs.PathLike} srcPath 源路径
* @param {Fs.PathLike} destPath 目标路径
* @returns {Promise<boolean>}
*/
async copy(srcPath, destPath) {
if (!FileUtil.existsSync(srcPath)) {
return false;
}
const stats = await FileUtil.stat(srcPath);
if (stats.isDirectory()) {
if (!FileUtil.existsSync(destPath)) {
await FileUtil.createDir(destPath);
}
const names = await FileUtil.readdir(srcPath);
for (const name of names) {
await FileUtil.copy(Path.join(srcPath, name), Path.join(destPath, name));
}
} else {
await FileUtil.writeFile(destPath, await FileUtil.readFile(srcPath));
}
return true;
},
/**
* 创建文件夹 (递归)
* @param {Fs.PathLike} path 路径
* @returns {Promise<boolean>}
*/
async createDir(path) {
if (FileUtil.existsSync(path)) {
return true;
} else {
const dir = Path.dirname(path);
if (await FileUtil.createDir(dir)) {
await FileUtil.mkdir(path);
return true;
}
}
return false;
},
/**
* 移除文件/文件夹 (递归)
* @param {Fs.PathLike} path 路径
*/
async remove(path) {
if (!FileUtil.existsSync(path)) {
return;
}
const stats = await FileUtil.stat(path);
if (stats.isDirectory()) {
const names = await FileUtil.readdir(path);
for (const name of names) {
await FileUtil.remove(Path.join(path, name));
}
await FileUtil.rmdir(path);
} else {
await FileUtil.unlink(path);
}
},
/**
* 遍历文件/文件夹并执行函数
* @param {Fs.PathLike} path 路径
* @param {(filePath: Fs.PathLike, stat: Fs.Stats) => void | Promise<void>} handler 处理函数
*/
async map(path, handler) {
if (!FileUtil.existsSync(path)) {
return;
}
const stats = await FileUtil.stat(path);
if (stats.isDirectory()) {
const names = await FileUtil.readdir(path);
for (const name of names) {
await FileUtil.map(Path.join(path, name), handler);
}
} else {
await handler(path, stats);
}
},
};
module.exports = FileUtil;
const zh = require('../../i18n/zh');
const en = require('../../i18n/en');
/**
* 多语言
* @author 陈皮皮 (ifaswind)
* @version 20210929
*/
const I18n = {
/**
* 中文
*/
zh,
/**
* 英文
*/
en,
/**
* 获取多语言文本
* @param {string} lang 语言
* @param {string} key 关键字
* @returns {string}
*/
get(lang, key) {
if (I18n[lang] && I18n[lang][key]) {
return I18n[lang][key];
}
return key;
},
};
module.exports = I18n;
const { ipcMain } = require('electron');
const PackageUtil = require('./package-util');
/** 包名 */
const PACKAGE_NAME = PackageUtil.name;
/**
* 主进程 IPC 事件
* @author 陈皮皮 (ifaswind)
* @version 20210818
*/
const MainEvent = {
/**
* 监听事件(一次性)
* @param {string} channel 频道
* @param {Function} callback 回调
*/
once(channel, callback) {
return ipcMain.once(`${PACKAGE_NAME}:${channel}`, callback);
},
/**
* 监听事件
* @param {string} channel 频道
* @param {Function} callback 回调
*/
on(channel, callback) {
return ipcMain.on(`${PACKAGE_NAME}:${channel}`, callback);
},
/**
* 取消事件监听
* @param {string} channel 频道
* @param {Function} callback 回调
*/
removeListener(channel, callback) {
return ipcMain.removeListener(`${PACKAGE_NAME}:${channel}`, callback);
},
/**
* 取消事件的所有监听
* @param {string} channel 频道
*/
removeAllListeners(channel) {
return ipcMain.removeAllListeners(`${PACKAGE_NAME}:${channel}`);
},
/**
* 发送事件到指定渲染进程
* @param {Electron.WebContents} webContents 渲染进程事件对象
* @param {string} channel 频道
* @param {any[]?} args 参数
*/
send(webContents, channel) {
// return webContents.send(`${PACKAGE_NAME}:${channel}`, ...args);
const args = [`${PACKAGE_NAME}:${channel}`];
for (let i = 2, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
return webContents.send.apply(webContents, args);
},
/**
* 回复事件给渲染进程
* @param {Electron.IpcMainEvent} ipcMainEvent 事件对象
* @param {string} channel 频道
* @param {any[]?} args 参数
*/
reply(ipcMainEvent, channel) {
// return ipcMainEvent.reply(`${PACKAGE_NAME}:${channel}`, ...args);
const args = [`${PACKAGE_NAME}:${channel}`];
for (let i = 2, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
return ipcMainEvent.reply.apply(ipcMainEvent, args);
},
};
module.exports = MainEvent;
const { shell } = require('electron');
/** 包信息 */
const PACKAGE_JSON = require('../../package.json');
/**
* 包工具
* @author 陈皮皮 (ifaswind)
* @version 20210908
*/
const PackageUtil = {
/**
* 包名
* @type {string}
*/
get name() {
return PACKAGE_JSON.name;
},
/**
* 版本
* @type {string}
*/
get version() {
return PACKAGE_JSON.version;
},
/**
* 仓库地址
* @type {string}
*/
get repository() {
return PACKAGE_JSON.repository;
},
/**
* 打开仓库页面
*/
openRepository() {
const url = PackageUtil.repository;
shell.openExternal(url);
},
};
module.exports = PackageUtil;
const { ipcRenderer } = require('electron');
const PackageUtil = require('./package-util');
/** 包名 */
const PACKAGE_NAME = PackageUtil.name;
/**
* 渲染进程 IPC 事件
* @author 陈皮皮 (ifaswind)
* @version 20210818
*/
const RendererEvent = {
/**
* 监听事件(一次性)
* @param {string} channel 频道
* @param {Function} callback 回调
*/
once(channel, callback) {
return ipcRenderer.once(`${PACKAGE_NAME}:${channel}`, callback);
},
/**
* 监听事件
* @param {string} channel 频道
* @param {Function} callback 回调
*/
on(channel, callback) {
return ipcRenderer.on(`${PACKAGE_NAME}:${channel}`, callback);
},
/**
* 取消事件监听
* @param {string} channel 频道
* @param {Function} callback 回调
*/
removeListener(channel, callback) {
return ipcRenderer.removeListener(`${PACKAGE_NAME}:${channel}`, callback);
},
/**
* 取消事件的所有监听
* @param {string} channel 频道
*/
removeAllListeners(channel) {
return ipcRenderer.removeAllListeners(`${PACKAGE_NAME}:${channel}`);
},
/**
* 发送事件到主进程
* @param {string} channel 频道
* @param {...any} args 参数
*/
send(channel) {
// return ipcRenderer.send(`${PACKAGE_NAME}:${channel}`, ...args);
const args = [`${PACKAGE_NAME}:${channel}`];
for (let i = 1, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
return ipcRenderer.send.apply(ipcRenderer, args);
},
/**
* 发送事件到主进程(同步)
* @param {string} channel 频道
* @param {...any} args 参数
* @returns {Promise<any>}
*/
sendSync(channel) {
// return ipcRenderer.sendSync(`${PACKAGE_NAME}:${channel}`, ...args);
const args = [`${PACKAGE_NAME}:${channel}`];
for (let i = 1, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
return ipcRenderer.sendSync.apply(ipcRenderer, args);
},
};
module.exports = RendererEvent;
const fetch = require('../../lib/node-fetch');
const PackageUtil = require('./package-util');
const { compareVersion } = require('./version-util');
/** 本地版本 */
const LOCAL_VERSION = PackageUtil.version;
/** 远程仓库地址 */
const REMOTE_URL = PackageUtil.repository;
/**
* 更新器
* @author 陈皮皮 (ifaswind)
* @version 20210804
*/
const Updater = {
/**
* 远程仓库地址
* @type {string}
*/
get remote() {
return REMOTE_URL;
},
/**
* 分支
* @type {string}
*/
branch: 'master',
/**
* 获取远端的 package.json
* @returns {Promise<object>}
*/
async getRemotePackageJson() {
const packageJsonUrl = `${Updater.remote}/raw/${Updater.branch}/package.json`;
// 发起网络请求
const response = await fetch(packageJsonUrl, {
method: 'GET',
cache: 'no-cache',
mode: 'no-cors',
});
// 请求结果
if (response.status !== 200) {
return null;
}
// 读取 json
const json = response.json();
return json;
},
/**
* 获取远端版本号
* @returns {Promise<string>}
*/
async getRemoteVersion() {
const package = await Updater.getRemotePackageJson();
if (package && package.version) {
return package.version;
}
return null;
},
/**
* 获取本地版本号
* @returns {string}
*/
getLocalVersion() {
return LOCAL_VERSION;
},
/**
* 检查远端是否有新版本
* @returns {Promise<boolean>}
*/
async check() {
// 远端版本号
const remoteVersion = await Updater.getRemoteVersion();
if (!remoteVersion) {
return false;
}
// 本地版本号
const localVersion = Updater.getLocalVersion();
// 对比版本号
const result = compareVersion(localVersion, remoteVersion);
return (result < 0);
},
};
module.exports = Updater;
/**
* 版本工具
* @author 陈皮皮 (ifaswind)
* @version 20210814
*/
const VersionUtil = {
/**
* 拆分版本号
* @param {string | number} version 版本号文本
* @returns {number[]}
* @example
* splitVersionString('1.2.0'); // [1, 2, 0]
*/
splitVersionString(version) {
if (typeof version === 'number') {
return [version];
}
if (typeof version === 'string') {
return (
version.replace(/-/g, '.')
.split('.')
.map(v => (parseInt(v) || 0))
);
}
return [0];
},
/**
* 对比版本号
* @param {string | number} a 版本 a
* @param {string | number} b 版本 b
* @returns {-1 | 0 | 1}
* @example
* compareVersion('1.0.0', '1.0.1'); // -1
* compareVersion('1.1.0', '1.1.0'); // 0
* compareVersion('1.2.1', '1.2.0'); // 1
* compareVersion('1.2.0.1', '1.2.0'); // 1
*/
compareVersion(a, b) {
const acs = VersionUtil.splitVersionString(a),
bcs = VersionUtil.splitVersionString(b);
const count = Math.max(acs.length, bcs.length);
for (let i = 0; i < count; i++) {
const ac = acs[i],
bc = bcs[i];
// 前者缺少分量或前者小于后者
if (ac == undefined || ac < bc) {
return -1;
}
// 后者缺少分量或前者大于后者
if (bc == undefined || ac > bc) {
return 1;
}
}
return 0;
},
};
module.exports = VersionUtil;
const { BrowserWindow } = require('electron');
/**
* 窗口工具(主进程)
* @author 陈皮皮 (ifaswind)
* @version 20210825
*/
const WindowUtil = {
/**
* 最先打开的窗口
* @returns {BrowserWindow}
*/
getFirstWindow() {
const wins = BrowserWindow.getAllWindows();
return wins[wins.length - 1];
},
/**
* 获取当前聚焦的窗口
* @returns {BrowserWindow}
*/
getFocusedWindow() {
return BrowserWindow.getFocusedWindow();
},
/**
* 计算窗口位置(相对于最先打开的窗口)
* @param {[number, number]} size 窗口尺寸
* @param {'top' | 'center'} anchor 锚点
* @returns {[number, number]}
*/
calcWindowPosition(size, anchor) {
const win = WindowUtil.getFirstWindow();
return WindowUtil.calcWindowPositionByTarget(size, anchor, win);
},
/**
* 计算窗口位置(相对于当前聚焦的窗口)
* @param {[number, number]} size 窗口尺寸
* @param {'top' | 'center'} anchor 锚点
* @returns {[number, number]}
*/
calcWindowPositionByFocused(size, anchor) {
const win = WindowUtil.getFocusedWindow();
return WindowUtil.calcWindowPositionByTarget(size, anchor, win);
},
/**
* 计算窗口位置(相对于当前聚焦的窗口)
* @param {[number, number]} size 窗口尺寸
* @param {'top' | 'center'} anchor 锚点
* @param {BrowserWindow} win 目标窗口
* @returns {[number, number]}
*/
calcWindowPositionByTarget(size, anchor, win) {
// 根据目标窗口的位置和尺寸来计算
const winSize = win.getSize(),
winPos = win.getPosition();
// 注意:原点 (0, 0) 在屏幕左上角
// 另外,窗口的位置值必须是整数,否则修改无效(像素的最小粒度为 1)
const x = Math.floor(winPos[0] + (winSize[0] / 2) - (size[0] / 2));
let y;
switch (anchor) {
case 'top': {
y = Math.floor(winPos[1]);
break;
}
default:
case 'center': {
y = Math.floor(winPos[1] + (winSize[1] / 2) - (size[1] / 2));
break;
}
}
return [x, y];
},
};
module.exports = WindowUtil;
/**
* 编辑器 API(用于抹平不同版本编辑器之间的差异)
* @author 陈皮皮 (ifaswind)
* @version 20210830
*/
const EditorAPI = {
/**
* 当前语言
* @returns {string}
*/
getLanguage() {
return Editor.lang || Editor.I18n.getLanguage();
},
/**
* 绝对路径转为编辑器资源路径
* @param {string} fspath
*/
fspathToUrl(fspath) {
return Editor.assetdb.fspathToUrl(fspath);
},
/**
* 编辑器资源路径转为绝对路径
* @param {string} url
*/
urlToFspath(url) {
return Editor.assetdb.urlToFspath(url);
},
/**
* 通过 uuid 获取资源信息
* @param {string} uuid
*/
assetInfoByUuid(uuid) {
return Editor.assetdb.assetInfoByUuid(uuid);
},
/**
* 通过 uuid 获取子资源信息
* @param {string} uuid
*/
subAssetInfosByUuid(uuid) {
return Editor.assetdb.subAssetInfosByUuid(uuid);
},
/**
* 获取当前选中的资源 uuid
* @returns {string[]}
*/
getCurrentSelectedAssets() {
return Editor.Selection.curSelection('asset');
},
/**
* 获取当前选中的节点 uuid
* @returns {string[]}
*/
getCurrentSelectedNodes() {
return Editor.Selection.curSelection('node');
},
/**
* 是否为 uuid
* @param {string} uuid
*/
isUuid(uuid) {
return Editor.Utils.UuidUtils.isUuid(uuid);
},
/**
* 压缩 uuid
* @param {string} uuid
*/
compressUuid(uuid) {
return Editor.Utils.UuidUtils.compressUuid(uuid);
},
/**
* 反压缩 uuid
* @param {string} uuid
*/
decompressUuid(uuid) {
return Editor.Utils.UuidUtils.decompressUuid(uuid);
},
};
module.exports = EditorAPI;
const { extname, basename } = require("path");
const EditorAPI = require("./editor-api");
const { print, translate } = require("../eazax/editor-main-util");
const FileUtil = require("../eazax/file-util");
const { containsValue } = require("./object-util");
const Parser = require("./parser");
const { DefaultDeserializer } = require("v8");
/** 扩展名对应文件类型 */
const ASSET_TYPE_MAP = {
// 场景
'.fire': 'scene',
'.scene': 'scene',
// 预制体
'.prefab': 'prefab',
// 动画
'.anim': 'animation',
// 材质
'.mtl': 'material',
// 字体
'.fnt.meta': 'font',
};
/**
* 查找器
*/
const Finder = {
/**
* 使用 uuid 进行查找
* @param {string} uuid
*/
async findByUuid(uuid) {
// 是否为有效 uuid
if (!EditorAPI.isUuid(uuid)) {
print('log', translate('invalid-uuid'), uuid);
return [];
}
// 获取资源信息
const assetInfo = EditorAPI.assetInfoByUuid(uuid);
let sIndex = assetInfo.path.lastIndexOf("/");
let eIndex = assetInfo.path.lastIndexOf(".");
if(sIndex == -1) {
sIndex = 0;
}
if(eIndex == -1) {
sIndex = assetInfo.path.length-1;
}
const fileName = assetInfo.path.substring(sIndex+1, eIndex);
if (assetInfo) {
// 记录子资源 uuid
const subAssetUuids = [];
// 资源类型检查
if (assetInfo.type === 'texture') {
// 纹理子资源
const subAssetInfos = EditorAPI.subAssetInfosByUuid(uuid);
if (subAssetInfos && subAssetInfos.length > 0) {
for (let i = 0; i < subAssetInfos.length; i++) {
subAssetUuids.push(subAssetInfos[i].uuid);
}
}
} else if (assetInfo.type === 'typescript' || assetInfo.type === 'javascript') {
// 脚本资源
uuid = EditorAPI.compressUuid(uuid);
}
// 查找资源引用
const results = [],
selfResults = await Finder.findRefs(uuid, fileName);
for (let i = 0, l = selfResults.length; i < l; i++) {
results.push(selfResults[i]);
}
// 查找子资源的引用
if (subAssetUuids.length > 0) {
for (let i = 0, l = subAssetUuids.length; i < l; i++) {
const subResults = await Finder.findRefs(subAssetUuids[i]);
for (let j = 0, l = subResults.length; j < l; j++) {
results.push(subResults[j]);
}
}
}
return results;
} else {
// 不存在的资源,直接查找 uuid
print('log', translate('find-asset-refs'), uuid);
return (await Finder.findRefs(uuid));
}
},
/**
* 查找引用
* @param {string} uuid
* @returns {Promise<{ type: string, url: string, refs?: object[]}[]>}
*/
async findRefs(uuid, fileName="") {
const result = [];
// 文件处理函数
const handler = async (path, stats) => {
const ext = extname(path);
if (ext === '.fire' || ext === '.scene' || ext === '.prefab') {
// 场景和预制体资源(转为节点树)
const tree = await Parser.getNodeTree(path);
if (!tree) {
return;
}
// 遍历第一层节点查找引用
const refs = [];
for (let children = tree.children, i = 0, l = children.length; i < l; i++) {
Finder.findRefsInNode(tree, children[i], uuid, refs);
}
// 保存当前文件引用结果
if (refs.length > 0) {
result.push({
type: ASSET_TYPE_MAP[ext],
url: EditorAPI.fspathToUrl(path),
refs: refs,
});
}
} else if (ext === '.anim') {
// 动画资源
const data = JSON.parse(await FileUtil.readFile(path)),
curveData = data['curveData'],
contains = containsValue(curveData, uuid);
if (contains) {
result.push({
type: ASSET_TYPE_MAP[ext],
url: EditorAPI.fspathToUrl(path),
});
}
} else if (ext === '.mtl' || path.endsWith('.fnt.meta')) {
// 材质和字体资源
const data = JSON.parse(await FileUtil.readFile(path));
// 需排除自己
if ((data['uuid'] === uuid)) {
return;
}
// 是否引用
const contains = containsValue(data, uuid);
if (contains) {
const _ext = (ext === '.mtl') ? '.mtl' : '.fnt.meta';
result.push({
type: ASSET_TYPE_MAP[_ext],
url: EditorAPI.fspathToUrl(path),
});
}
} else if (ext === '.ts' || ext === '.js') {
// 脚本代码
const data = await FileUtil.readFile(path);
const regCheckImport = new RegExp(fileName);
if(regCheckImport.test(data.toString())) {
result.push({
type: "Script",
url: EditorAPI.fspathToUrl(path),
});
}
}
};
// 遍历资源目录下的文件
const assetsPath = EditorAPI.urlToFspath('db://assets');
await FileUtil.map(assetsPath, handler);
return result;
},
/**
* 查找节点中的引用
* @param {object} tree 节点树
* @param {object} node 目标节点
* @param {string} uuid 查找的 uuid
* @param {object[]} result 结果
*/
findRefsInNode(tree, node, uuid, result) {
// 检查节点上的组件是否有引用
const components = node.components;
if (components && components.length > 0) {
for (let i = 0, l = components.length; i < l; i++) {
const properties = Finder.getContainsUuidProperties(components[i], uuid);
if (properties.length === 0) {
continue;
}
// 资源类型
let type = components[i]['__type__'];
// 是否为脚本资源
if (EditorAPI.isUuid(type)) {
const scriptUuid = EditorAPI.decompressUuid(type),
assetInfo = EditorAPI.assetInfoByUuid(scriptUuid);
type = basename(assetInfo.url);
}
// 遍历相关属性名
for (let i = 0; i < properties.length; i++) {
let property = properties[i];
if (property === '__type__') {
property = null;
} else {
// 处理属性名称(Label 组件需要特殊处理)
if (type === 'cc.Label' && property === '_N$file') {
property = 'font';
}
// 去除属性名的前缀
if (property.startsWith('_N$')) {
property = property.replace('_N$', '');
} else if (property[0] === '_') {
property = property.substring(1);
}
}
// 保存结果
result.push({
node: node.path,
component: type,
property: property,
});
}
}
}
// 检查预制体是否有引用
const prefab = node.prefab;
if (prefab) {
// 排除预制体自己
if (uuid !== tree.uuid) {
const contains = containsValue(prefab, uuid);
if (contains) {
result.push({
node: node.path,
});
}
}
}
// 遍历子节点
const children = node.children;
if (children && children.length > 0) {
for (let i = 0, l = children.length; i < l; i++) {
Finder.findRefsInNode(tree, children[i], uuid, result);
}
}
},
/**
* 获取对象包含指定 uuid 的属性
* @param {object} object 对象
* @param {string} uuid 值
* @returns {string[]}
*/
getContainsUuidProperties(object, uuid) {
const properties = [];
const search = (target, path) => {
if (Object.prototype.toString.call(target) === '[object Object]') {
for (const key in target) {
const curPath = (path != null) ? `${path}.${key}` : key;
if (target[key] === uuid) {
properties.push(path || key);
}
search(target[key], curPath);
}
} else if (Array.isArray(target)) {
for (let i = 0, l = target.length; i < l; i++) {
const curPath = (path != null) ? `${path}[${i}]` : `[${i}]`;
if (target[i] === uuid) {
properties.push(path || `[${i}]`);
}
search(target[i], curPath);
}
}
}
search(object, null);
return properties;
},
};
module.exports = Finder;
const PanelManager = require('./panel-manager');
const ConfigManager = require('../common/config-manager');
const EditorMainKit = require('../eazax/editor-main-kit');
const { checkUpdate, print, translate } = require('../eazax/editor-main-util');
const { openRepository } = require('../eazax/package-util');
const EditorAPI = require('./editor-api');
const Parser = require('./parser');
const Finder = require('./finder');
const Printer = require('./printer');
/**
* 生命周期:加载
*/
function load() {
// 监听事件
EditorMainKit.register();
}
/**
* 生命周期:卸载
*/
function unload() {
// 取消事件监听
EditorMainKit.unregister();
}
/**
* 查找当前选中资源
*/
async function findCurrentSelection() {
// 过滤选中的资源 uuid
const uuids = EditorAPI.getCurrentSelectedAssets();
for (let i = 0; i < uuids.length; i++) {
const assetInfo = EditorAPI.assetInfoByUuid(uuids[i]);
if (assetInfo.type === 'folder') {
uuids.splice(i--);
}
}
// 未选择资源
if (uuids.length === 0) {
print('log', translate('please-select-assets'));
return;
}
// 遍历查找
for (let i = 0; i < uuids.length; i++) {
const uuid = uuids[i],
assetInfo = EditorAPI.assetInfoByUuid(uuid),
shortUrl = assetInfo.url.replace('db://', '');
// 查找引用
print('log', '🔍', `${translate('find-asset-refs')} ${shortUrl}`);
const refs = await Finder.findByUuid(uuid);
if (refs.length === 0) {
print('log', '📂', `${translate('no-refs')} ${shortUrl}`);
continue;
}
// 打印结果
Printer.printResult({
type: assetInfo.type,
uuid: uuid,
url: assetInfo.url,
path: assetInfo.path,
refs: refs,
});
}
}
function getSelection() {
}
/**
* 资源变化回调
* @param {{ type: string, uuid: string }} info
*/
function onAssetChanged(info) {
const { type, uuid } = info;
// 场景和预制体
if (type === 'scene' || type === 'prefab') {
const { url, path } = EditorAPI.assetInfoByUuid(uuid);
// 排除内置资源
if (url.indexOf('db://internal') !== -1) {
return;
}
// 更新节点树
Parser.updateCache(path);
}
}
module.exports = {
/**
* 扩展消息
*/
messages: {
/**
* 查找当前选中资源
* @param {*} event
*/
'find-current-selection'(event) {
findCurrentSelection();
},
/**
* 打开设置面板
* @param {*} event
*/
'open-settings-panel'(event) {
PanelManager.openSettingsPanel();
},
/**
* 检查更新
* @param {*} event
*/
'menu-check-update'(event) {
checkUpdate(true);
},
/**
* 版本
* @param {*} event
*/
'menu-version'(event) {
openRepository();
},
/**
* 场景面板加载完成后
* @param {*} event
*/
'scene:ready'(event) {
// 自动检查更新
const config = ConfigManager.get();
if (config.autoCheckUpdate) {
checkUpdate(false);
}
},
/**
* 资源变化
* @param {*} event
* @param {{ type: string, uuid: string }} info
*/
'asset-db:asset-changed'(event, info) {
onAssetChanged(info);
},
},
load,
unload,
};
/**
* 对象工具
* @author 陈皮皮 (ifaswind)
* @version 20210929
*/
const ObjectUtil = {
/**
* 判断指定值是否是一个对象
* @param {any} arg 参数
*/
isObject(arg) {
return Object.prototype.toString.call(arg) === '[object Object]';
},
/**
* 对象中是否包含指定的属性
* @param {object} object 对象
* @param {string} name 属性名
*/
containsProperty(object, name) {
let result = false;
const search = (_object) => {
if (ObjectUtil.isObject(_object)) {
for (const key in _object) {
if (key == name) {
result = true;
return;
}
search(_object[key]);
}
} else if (Array.isArray(_object)) {
for (let i = 0, l = _object.length; i < l; i++) {
search(_object[i]);
}
}
}
search(object);
return result;
},
/**
* 对象中是否包含指定的值
* @param {object} object 对象
* @param {any} value 值
*/
containsValue(object, value) {
let result = false;
const search = (_object) => {
if (ObjectUtil.isObject(_object)) {
for (const key in _object) {
if (_object[key] === value) {
result = true;
return;
}
search(_object[key]);
}
} else if (Array.isArray(_object)) {
for (let i = 0, l = _object.length; i < l; i++) {
search(_object[i]);
}
}
}
search(object);
return result;
},
};
module.exports = ObjectUtil;
const { BrowserWindow } = require('electron');
const { join } = require('path');
const { language, translate } = require('../eazax/editor-main-util');
const { calcWindowPosition } = require('../eazax/window-util');
/** 扩展名称 */
const EXTENSION_NAME = translate('name');
/**
* 面板管理器 (主进程)
*/
const PanelManager = {
/**
* 面板实例
* @type {BrowserWindow}
*/
settings: null,
/**
* 打开设置面板
*/
openSettingsPanel() {
// 已打开则直接展示
if (PanelManager.settings) {
PanelManager.settings.show();
return;
}
// 窗口尺寸和位置
const winSize = [500, 346],
winPos = calcWindowPosition(winSize, 'center');
// 创建窗口
const win = PanelManager.settings = new BrowserWindow({
width: winSize[0],
height: winSize[1],
minWidth: winSize[0],
minHeight: winSize[1],
x: winPos[0],
y: winPos[1] - 100,
useContentSize: true,
frame: true,
title: `${EXTENSION_NAME} | Cocos Creator`,
autoHideMenuBar: true,
resizable: true,
minimizable: false,
maximizable: false,
fullscreenable: false,
skipTaskbar: false,
alwaysOnTop: true,
hasShadow: true,
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
// 就绪后(展示,避免闪烁)
win.on('ready-to-show', () => win.show());
// 关闭后
win.on('closed', () => (PanelManager.settings = null));
// 监听按键
win.webContents.on('before-input-event', (event, input) => {
if (input.key === 'Escape') PanelManager.closeSettingsPanel();
});
// 调试用的 devtools
// win.webContents.openDevTools({ mode: 'detach' });
// 加载页面
const path = join(__dirname, '../renderer/settings/index.html');
win.loadURL(`file://${path}?lang=${language}`);
},
/**
* 关闭面板
*/
closeSettingsPanel() {
if (!PanelManager.settings) {
return;
}
PanelManager.settings.hide();
PanelManager.settings.close();
PanelManager.settings = null;
},
};
module.exports = PanelManager;
const { print } = require("../eazax/editor-main-util");
const FileUtil = require("../eazax/file-util");
const { containsProperty } = require("./object-util");
/**
* 解析器
*/
const Parser = {
/**
* 节点树缓存
* @type {{ [key: string]: object }}
*/
caches: Object.create(null),
/**
* 获取节点树
* @param {string} path 路径
* @returns {Promise<object>}
*/
async getNodeTree(path) {
if (!Parser.caches[path]) {
const file = await FileUtil.readFile(path);
let data = null;
try {
data = JSON.parse(file);
} catch (error) {
print('warn', '文件解析失败', path);
print('warn', error);
}
if (!data) {
return null;
}
Parser.caches[path] = Parser.convert(data);
}
return Parser.caches[path];
},
/**
* 更新缓存
* @param {string} path 路径
*/
async updateCache(path) {
Parser.caches[path] = null;
await Parser.getNodeTree(path);
},
/**
* 将资源解析为节点树
* @param {object} source 源数据
* @returns {object}
*/
convert(source) {
const tree = Object.create(null),
type = source[0]['__type__'];
if (type === 'cc.SceneAsset') {
// 场景资源
const sceneId = source[0]['scene']['__id__'],
children = source[sceneId]['_children'];
tree.type = 'cc.Scene'; // 类型
tree.id = sceneId; // ID
// 场景下可以有多个一级节点
tree.children = [];
for (let i = 0, l = children.length; i < l; i++) {
const nodeId = children[i]['__id__'];
Parser.convertNode(source, nodeId, tree);
}
} else if (type === 'cc.Prefab') {
// 预制体资源
const uuid = source[source.length - 1]['asset']['__uuid__'];
tree.type = 'cc.Prefab'; // 类型
tree.uuid = uuid; // uuid
// 预制体本身就是一个节点
tree.children = [];
const nodeId = source[0]['data']['__id__'];
Parser.convertNode(source, nodeId, tree);
}
return tree;
},
/**
* 解析节点
* @param {object} source 源数据
* @param {number} nodeId 节点 ID
* @param {object} parent 父节点
*/
convertNode(source, nodeId, parent) {
const srcNode = source[nodeId],
node = Object.create(null);
// 基本信息
node.name = srcNode['_name'];
node.id = nodeId;
node.type = srcNode['__type__'];
// 路径
const parentPath = parent.path || null;
node.path = parentPath ? `${parentPath}/${node.name}` : node.name;
// 预制体引用
const srcPrefab = srcNode['_prefab'];
if (srcPrefab) {
const id = srcPrefab['__id__'];
node.prefab = Parser.extractValidInfo(source[id]);
}
// 组件
node.components = [];
const srcComponents = srcNode['_components'];
if (srcComponents && srcComponents.length > 0) {
for (let i = 0, l = srcComponents.length; i < l; i++) {
const compId = srcComponents[i]['__id__'],
component = Parser.extractValidInfo(source[compId]);
node.components.push(component);
}
}
// 子节点
node.children = [];
const srcChildren = srcNode['_children'];
if (srcChildren && srcChildren.length > 0) {
for (let i = 0, l = srcChildren.length; i < l; i++) {
const nodeId = srcChildren[i]['__id__'];
Parser.convertNode(source, nodeId, node);
}
}
// 保存到父节点
parent.children.push(node);
},
/**
* 提取有效信息(含有 uuid)
* @param {object} source 源数据
* @returns {{ __type__: string, _name: string, fileId?: string }}
*/
extractValidInfo(source) {
const result = Object.create(null);
// 记录有用的属性
const keys = ['__type__', '_name', 'fileId'];
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i];
if (source[key] !== undefined) {
result[key] = source[key];
}
}
// 记录包含 uuid 的属性
for (const key in source) {
const contains = containsProperty(source[key], '__uuid__');
if (contains) {
result[key] = source[key];
}
}
return result;
},
};
module.exports = Parser;
const { translate, print, pureWithoutTitle } = require('../eazax/editor-main-util');
const ConfigManager = require('../common/config-manager');
/** 图标表 */
const ICON_MAP = {
'scene': '🔥',
'prefab': '💠',
'node': '🎲',
'component': '🧩',
'property': '📄',
'asset': '📦',
'asset-info': '📋',
'node-refs': '📙',
'asset-refs': '📗',
};
/**
* 打印机
*/
const Printer = {
/**
* 打印结果至控制台
* @param {object} result
*/
printResult(result) {
if (!result) {
return;
}
const { printDetails, printFolding } = ConfigManager.get();
// 标志位
const nodeRefs = [], assetRefs = [];
let nodeRefsCount = 0, assetRefsCount = 0;
// 遍历引用信息
for (let refs = result.refs, i = 0, l = refs.length; i < l; i++) {
const ref = refs[i],
type = ref.type,
url = ref.url.replace('db://', '').replace('.meta', '');
if (type === 'scene' || type === 'prefab') {
// 场景或预制体
nodeRefs.push(`  ${ICON_MAP[type]} [${translate(type)}] ${url}`);
// 节点引用
for (let details = ref.refs, j = 0, l = details.length; j < l; j++) {
nodeRefsCount++;
// 详情
if (printDetails) {
const detail = details[j];
let item = `    ${ICON_MAP['node']} [${translate('node')}] ${detail.node}`;
if (detail.component) {
item += `  →  ${ICON_MAP['component']} [${translate('component')}] ${detail.component}`;
}
if (detail.property) {
item += `  →  ${ICON_MAP['property']} [${translate('property')}] ${detail.property}`;
}
nodeRefs.push(item);
}
}
} else {
// 资源引用
assetRefsCount++;
assetRefs.push(`  ${ICON_MAP['asset']} [${translate(type)}] ${url}`);
}
}
// 组装文本
const texts = [];
// 分割线
texts.push(`${'- - '.repeat(36)}`);
// 基础信息
texts.push(`${ICON_MAP['asset-info']} ${translate('asset-info')}`);
texts.push(`  - ${translate('asset-type')}${result.type}`);
texts.push(`  - ${translate('asset-uuid')}${result.uuid}`);
texts.push(`  - ${translate('asset-url')}${result.url}`);
texts.push(`  - ${translate('asset-path')}${result.path}`);
// 分割线
texts.push(`${'- - '.repeat(36)}`);
// 节点引用
if (nodeRefs.length > 0) {
texts.push(`${ICON_MAP['node-refs']} ${translate('node-refs')} x ${nodeRefsCount}`);
for (let i = 0, l = nodeRefs.length; i < l; i++) {
texts.push(nodeRefs[i]);
}
}
// 资源引用
if (assetRefs.length > 0) {
texts.push(`${ICON_MAP['asset-refs']} ${translate('asset-refs')} x ${assetRefsCount}`);
for (let i = 0, l = assetRefs.length; i < l; i++) {
texts.push(assetRefs[i]);
}
}
// 结尾分割线
texts.push(`${'- - '.repeat(36)}`);
// 打印到控制台
if (printFolding) {
// 单行打印
texts.unshift(`🗂 ${translate('result')} >>>`);
print('log', texts.join('\n'));
} else {
// 逐行打印
print('log', translate('result'));
for (let i = 0, l = texts.length; i < l; i++) {
pureWithoutTitle(`  ${texts[i]}`);
}
}
},
};
module.exports = Printer;
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0 12px;
background-color: #454545;
color: #bdbdbd;
user-select: none;
}
#app {
width: 100%;
height: 100%;
}
/* 标题 */
.title {
font-size: 20px;
font-weight: 800;
padding: 10px 0;
}
/* 属性容器 */
.properties {
overflow: visible;
}
/* 应用按钮 */
.apply-btn {
min-width: 20px;
height: 33px;
background-image: linear-gradient(#4281b6, #4281b6);
border: 1px solid #171717;
border-radius: 3px;
color: #fff;
font-size: 16px;
font-weight: 800;
text-align: center;
outline: none;
overflow: hidden;
cursor: pointer;
}
.apply-btn:hover {
background-image: none;
background-color: #4c87b6;
}
.apply-btn:active {
background-image: none;
background-color: #2e6da2;
border-color: #fd942b;
color: #cdcdcd;
box-shadow: 1px 1px 10px #262626 inset;
}
[v-cloak] {
display: none;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 样式 -->
<link rel="stylesheet" type="text/css" href="../../eazax/css/cocos-tag.css">
<link rel="stylesheet" type="text/css" href="../../eazax/css/cocos-class.css">
<link rel="stylesheet" type="text/css" href="index.css">
<!-- 脚本 -->
<script type="text/javascript" src="../../../lib/vue.global.prod.js" defer></script>
<script type="text/javascript" src="index.js" defer></script>
</head>
<body>
<div id="app" v-cloak>
<!-- 标题 -->
<div class="title">{{ t('settings') }}</div>
<!-- 配置 -->
<div class="properties">
<!-- 选择快捷键 -->
<div class="property">
<div class="label">
<span class="text">{{ t('select-key') }}</span>
<span class="tooltip">{{ t('select-key-tooltip') }}</span>
</div>
<div class="content">
<select v-model="selectKey">
<option v-for="item in presets" :key="item.key" :value="item.key">{{ item.name }}</option>
</select>
</div>
</div>
<!-- 自定义快捷键 -->
<div class="property">
<div class="label">
<span class="text">{{ t('custom-key') }}</span>
<span class="tooltip">{{ t('custom-key-tooltip') }}</span>
</div>
<div class="content">
<input v-model="customKey" :placeholder="t('custom-key-placeholder')" />
</div>
</div>
<!-- 展示详情 -->
<div class="property">
<div class="label">
<span class="text">{{ t('print-details') }}</span>
<span class="tooltip">{{ t('print-details-tooltip') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="printDetails" />
</div>
</div>
<!-- 折叠结果 -->
<div class="property">
<div class="label">
<span class="text">{{ t('print-folding') }}</span>
<span class="tooltip">{{ t('print-folding-tooltip') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="printFolding" />
</div>
</div>
<!-- 自动检查更新 -->
<div class="property">
<div class="label">
<span class="text">{{ t('auto-check-update') }}</span>
<span class="tooltip">{{ t('auto-check-update-tooltip') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="autoCheckUpdate" />
</div>
</div>
<!-- 快捷键参考 -->
<div class="tip">
<span>{{ t('reference') }}</span>
<a href="https://www.electronjs.org/docs/api/accelerator" target="_blank">{{ t('accelerator') }}</a>
</div>
<!-- Git 仓库 -->
<div class="tip">
<span>{{ t('repository') }}</span>
<a :href="repositoryUrl" target="_blank">{{ packageName }}</a>
</div>
<div class="line"></div>
<!-- 应用按钮 -->
<button class="apply-btn" @click="onApplyBtnClick">{{ t('apply') }}</button>
</div>
</div>
</body>
</html>
\ No newline at end of file
const { shell } = require('electron');
const { getUrlParam } = require('../../eazax/browser-util');
const I18n = require('../../eazax/i18n');
const RendererEvent = require('../../eazax/renderer-event');
const PackageUtil = require('../../eazax/package-util');
const EditorRendererKit = require('../../eazax/editor-renderer-kit');
const ConfigManager = require('../../common/config-manager');
// 导入 Vue 工具函数
const { ref, watch, onMounted, onBeforeUnmount, createApp } = Vue;
/** 当前语言 */
const LANG = getUrlParam('lang');
// 构建 Vue 应用
const App = {
/**
* 设置
* @param {*} props
* @param {*} context
*/
setup(props, context) {
// 预设快捷键
const presets = ref([
{ key: '', name: t('none') },
{ key: 'custom', name: t('custom-key') },
{ key: 'F1', name: 'F1' },
{ key: 'F3', name: 'F3' },
{ key: 'F4', name: 'F4' },
{ key: 'F5', name: 'F5' },
{ key: 'F6', name: 'F6' },
{ key: 'CmdOrCtrl+F', name: 'Cmd/Ctrl + F' },
{ key: 'CmdOrCtrl+B', name: 'Cmd/Ctrl + B' },
{ key: 'CmdOrCtrl+Shift+F', name: 'Cmd/Ctrl + Shift + F' },
]);
// 选择
const selectKey = ref('');
// 自定义
const customKey = ref('');
// 打印详情
const printDetails = ref(true);
// 单行打印
const printFolding = ref(true);
// 自动检查更新
const autoCheckUpdate = ref(false);
// 仓库地址
const repositoryUrl = PackageUtil.repository;
// 包名
const packageName = PackageUtil.name;
// 监听选择快捷键
watch(selectKey, (value) => {
if (value !== 'custom') {
customKey.value = '';
}
});
// 监听自定义
watch(customKey, (value) => {
if (value !== '' && selectKey.value !== 'custom') {
selectKey.value = 'custom';
}
});
/**
* 获取配置
*/
function getConfig() {
const config = ConfigManager.get();
if (!config) return;
// 配置
printDetails.value = config.printDetails;
printFolding.value = config.printFolding;
autoCheckUpdate.value = config.autoCheckUpdate;
// 快捷键
const hotkey = config.hotkey;
if (!hotkey || hotkey === '') {
selectKey.value = '';
customKey.value = '';
return;
}
// 预设快捷键
for (let i = 0, l = presets.value.length; i < l; i++) {
if (presets.value[i].key === hotkey) {
selectKey.value = hotkey;
customKey.value = '';
return;
}
}
// 自定义快捷键
selectKey.value = 'custom';
customKey.value = hotkey;
}
/**
* 保存配置
*/
function setConfig() {
const config = {
hotkey: null,
printDetails: printDetails.value,
printFolding: printFolding.value,
autoCheckUpdate: autoCheckUpdate.value,
};
if (selectKey.value === 'custom') {
// 自定义输入是否有效
if (customKey.value === '') {
EditorRendererKit.print('warn', t('custom-key-error'));
return;
}
// 不可以使用双引号(避免 json 值中出现双引号而解析错误,导致插件加载失败)
if (customKey.value.includes('"')) {
customKey.value = customKey.value.replace(/\"/g, '');
EditorRendererKit.print('warn', t('quote-error'));
return;
}
config.hotkey = customKey.value;
} else {
config.hotkey = selectKey.value;
}
// 保存到本地
ConfigManager.set(config);
}
/**
* 应用按钮点击回调
* @param {*} event
*/
function onApplyBtnClick(event) {
// 保存配置
setConfig();
}
/**
* 翻译
* @param {string} key
*/
function t(key) {
return I18n.get(LANG, key);
}
/**
* 生命周期:挂载后
*/
onMounted(() => {
// 获取配置
getConfig();
// 覆盖 a 标签点击回调(使用默认浏览器打开网页)
const links = document.querySelectorAll('a[href]');
links.forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
const url = link.getAttribute('href');
shell.openExternal(url);
});
});
// (主进程)检查更新
RendererEvent.send('check-update', false);
});
/**
* 生命周期:卸载前
*/
onBeforeUnmount(() => {
});
return {
presets,
selectKey,
customKey,
printDetails,
printFolding,
autoCheckUpdate,
repositoryUrl,
packageName,
onApplyBtnClick,
t,
};
},
};
// 创建实例
const app = createApp(App);
// 挂载
app.mount('#app');
/**
* Cocos Creator 编辑器模块
* @author 陈皮皮(ifaswind)
* @version 20210312
* @see https://gitee.com/ifaswind/eazax-ccc/blob/master/declarations/editor.d.ts
*/
declare module Editor {
/**
* Log the normal message and show on the console. The method will send ipc message editor:console-log to all windows.
* @param args Whatever arguments the message needs
*/
function log(...args: any): void;
/**
* Log the normal message and show on the console. The method will send ipc message editor:console-log to all windows.
* @param args Whatever arguments the message needs
*/
function info(...args: any): void;
/**
* Log the warnning message and show on the console, it also shows the call stack start from the function call it. The method will send ipc message editor:console-warn to all windows.
* @param args Whatever arguments the message needs
*/
function warn(...args: any): void;
/**
* Log the error message and show on the console, it also shows the call stack start from the function call it. The method will sends ipc message editor:console-error to all windows.
* @param args Whatever arguments the message needs
*/
function error(...args: any): void;
/**
* Log the success message and show on the console The method will send ipc message editor:console-success to all windows.
* @param args Whatever arguments the message needs
*/
function success(...args: any): void;
/**
* Require the module by Editor.url. This is good for module exists in package, since the absolute path of package may be variant in different machine.
* @param url
*/
function require(url: string): any;
/**
* Returns the file path (if it is registered in custom protocol) or url (if it is a known public protocol).
* @param url
* @param encode
*/
function url(url: string, encode?: string): string;
function T(key: string): string;
}
declare module Editor {
readonly let appPath: string;
readonly let frameworkPath: string;
readonly let importPath: string;
readonly let isWin32: boolean;
readonly let isDarwin: boolean;
readonly let lang: string;
readonly let libraryPath: string;
readonly let sceneScripts: { [packageName: string]: string };
}
declare module Editor {
/**
* 渲染进程
*/
module RendererProcess {
/**
* AssetDB singleton class in renderer process, you can access the instance with `Editor.assetdb`.
*/
class AssetDB {
/**
* The remote AssetDB instance of main process, same as `Editor.remote.assetdb`.
*/
readonly remote: Remote;
/**
* The library path.
*/
readonly library: string;
/**
* Reveal given url in native file system.
* @param url
*/
explore(url: string): string;
/**
* Reveal given url's library file in native file system.
* @param url
*/
exploreLib(url: string): string;
/**
* Get native file path by url.
* @param url
* @param cb The callback function.
*/
queryPathByUrl(url: string, cb?: (err: any, path: any) => void): void;
/**
* Get uuid by url.
* @param url
* @param cb The callback function.
*/
queryUuidByUrl(url: string, cb?: (err: any, uuid: any) => void): void;
/**
* Get native file path by uuid.
* @param uuid
* @param cb The callback function.
*/
queryPathByUuid(uuid: string, cb?: (err: any, path: any) => void): void;
/**
* Get asset url by uuid.
* @param uuid
* @param cb The callback function.
*/
queryUrlByUuid(uuid: string, cb?: (err: any, url: any) => void): void;
/**
* Get asset info by uuid.
* @param uuid
* @param cb The callback function.
*/
queryInfoByUuid(uuid: string, cb?: (err: any, info: any) => void): void;
/**
* Get meta info by uuid.
* @param uuid
* @param cb The callback function.
*/
queryMetaInfoByUuid(uuid: string, cb?: (err: any, info: any) => void): void;
/**
* Query all assets from asset-db.
* @param cb The callback function.
*/
deepQuery(cb?: (err: any, results: any[]) => void): void;
/**
* Query assets by url pattern and asset-type.
* @param pattern The url pattern.
* @param assetTypes The asset type(s).
* @param cb The callback function.
*/
queryAssets(pattern: string, assetTypes: string | string[], cb?: (err: any, results: any[]) => void): void;
/**
* Import files outside asset-db to specific url folder.
* @param rawfiles Rawfile path list.
* @param destUrl The url of dest folder.
* @param showProgress Show progress or not.
* @param cb The callbak function.
*/
import(rawfiles: string[], destUrl: string, showProgress?: boolean, cb?: (err: any, result: any) => void): void;
/**
* Create asset in specific url by sending string data to it.
* @param uuid
* @param metaJson
* @param cb the callback function.
*/
create(url: string, data: string, cb?: (err: any, result: any) => void): void;
/**
* Move asset from src to dest.
* @param srcUrl
* @param destUrl
* @param showMessageBox
*/
move(srcUrl: string, destUrl: string, showMessageBox?: boolean): void;
/**
* Delete assets by url list.
* @param urls
*/
delete(urls: string[]): void;
/**
* Save specific asset by sending string data.
* @param url
* @param data
* @param cb the callback function.
*/
saveExists(url: string, data: string, cb?: (err: any, result: any) => void): void;
/**
* Create or save assets by sending string data. If the url is already existed, it will be changed with new data. The behavior is same with method saveExists. Otherwise, a new asset will be created. The behavior is same with method create.
* @param url
* @param data
* @param cb the callback function.
*/
createOrSave(url: string, data: string, cb?: (err: any, result: any) => void): void;
/**
* Save specific meta by sending meta's json string.
* @param uuid
* @param metaJson
* @param cb the callback function.
*/
saveMeta(uuid: string, metaJson: string, cb?: (err: any, result: any) => void): void;
/**
* Refresh the assets in url, and return the results.
* @param url
* @param cb
*/
refresh(url: string, cb?: (err: any, results: any[]) => void): void;
}
}
/**
* 主进程
*/
module MainProcess {
/**
* AssetDB singleton class in main process, you can access the instance with `Editor.assetdb`.
*/
class AssetDB {
/**
* Return uuid by url. If uuid not found, it will return null.
* @param url
*/
urlToUuid(url: string): string;
/**
* Return uuid by file path. If uuid not found, it will return null.
* @param fspath
*/
fspathToUuid(fspath: string): string;
/**
* Return file path by uuid. If file path not found, it will return null.
* @param url
*/
uuidToFspath(url: string): string;
/**
* Return url by uuid. If url not found, it will return null.
* @param uuid
*/
uuidToUrl(uuid: string): string;
/**
* Return url by file path. If file path not found, it will return null.
* @param fspath
*/
fspathToUrl(fspath: string): string;
/**
* Return file path by url. If url not found, it will return null.
* @param url
*/
urlToFspath(url: string): string;
/**
* Check existance by url.
* @param url
*/
exists(url: string): string;
/**
* Check existance by uuid.
* @param uuid
*/
existsByUuid(uuid: string): string;
/**
* Check existance by path.
* @param fspath
*/
existsByPath(fspath: string): string;
/**
* Check whether asset for a given url is a sub asset.
* @param url
*/
isSubAsset(url: string): boolean;
/**
* Check whether asset for a given uuid is a sub asset.
* @param uuid
*/
isSubAssetByUuid(uuid: string): boolean;
/**
* Check whether asset for a given path is a sub asset.
* @param fspath
*/
isSubAssetByPath(fspath: string): boolean;
/**
* Check whether asset contains sub assets for a given url.
* @param url
*/
containsSubAssets(url: string): boolean;
/**
* Check whether asset contains sub assets for a given uuid.
* @param uuid
*/
containsSubAssetsByUuid(uuid: string): boolean;
/**
* Check whether asset contains sub assets for a given path.
* @param fspath
*/
containsSubAssetsByPath(fspath: string): boolean;
/**
* Return asset info by a given url.
* @param url
*/
assetInfo(url: string): AssetInfo;
/**
* Return asset info by a given uuid.
* @param uuid
*/
assetInfoByUuid(uuid: string): AssetInfo;
/**
* Return asset info by a given file path.
* @param fspath
*/
assetInfoByPath(fspath: string): AssetInfo;
/**
* Return all sub assets info by url if the url contains sub assets.
* @param url
*/
subAssetInfos(url: string): AssetInfo[];
/**
* Return all sub assets info by uuid if the uuid contains sub assets.
* @param uuid
*/
subAssetInfosByUuid(uuid: string): AssetInfo[];
/**
* Return all sub assets info by path if the path contains sub assets.
* @param fspath
*/
subAssetInfosByPath(fspath: string): AssetInfo[];
/**
* Return meta instance by a given url.
* @param url
*/
loadMeta(url: string): MetaBase;
/**
* Return meta instance by a given uuid.
* @param uuid
*/
loadMetaByUuid(uuid: string): MetaBase;
/**
* Return meta instance by a given path.
* @param fspath
*/
loadMetaByPath(fspath: string): MetaBase;
/**
* Return whether a given url is reference to a mount.
* @param url
*/
isMount(url: string): boolean;
/**
* Return whether a given path is reference to a mount.
* @param fspath
*/
isMountByPath(fspath: string): boolean;
/**
* Return whether a given uuid is reference to a mount.
* @param uuid
*/
isMountByUuid(uuid: string): boolean;
/**
* Return mount info by url.
* @param url
*/
mountInfo(url: string): MountInfo;
/**
* Return mount info by uuid.
* @param uuid
*/
mountInfoByUuid(uuid: string): MountInfo;
/**
* Return mount info by path.
* @param fspath
*/
mountInfoByPath(fspath: string): MountInfo;
/**
* Mount a directory to assetdb, and give it a name. If you don't provide a name, it will mount to root.
* @param path file system path.
* @param mountPath the mount path (relative path).
* @param opts options.
* @param opts.hide if the mount hide in assets browser.
* @param opts.virtual if this is a virtual mount point.
* @param opts.icon icon for the mount.
* @param cb a callback function.
* @example Editor.assetdb.mount('path/to/mount', 'assets', function (err) {
// mounted, do something ...
});
*/
mount(path: string, mountPath: string, opts: { hide: object, vitural: object, icon: object }, cb?: (err: any) => void): void;
/**
* Attach the specified mount path.
* @param mountPath the mount path (relative path).
* @param cb a callback function.
* @example Editor.assetdb.attachMountPath('assets', function (err, results) {
// mount path attached, do something ...
// results are the assets created
});
*/
attachMountPath(mountPath: string, cb?: (err: any, results: any[]) => void): void;
/**
* Unattach the specified mount path.
* @param mountPath the mount path (relative path).
* @param cb a callback function.
* @example Editor.assetdb.unattachMountPath('assets', function (err, results) {
// mount path unattached, do something ...
// results are the assets deleted
});
*/
unattachMountPath(mountPath: string, cb?: (err: any, results: any[]) => void): void;
/**
* Unmount by name.
* @param mountPath the mount path.
* @param cb a callback function.
* @example Editor.assetdb.unmount('assets', function (err) {
// unmounted, do something ...
});
*/
unmount(mountPath: string, cb?: (err: any) => void): void;
/**
* Init assetdb, it will scan the mounted directories, and import unimported assets.
* @param cb a callback function.
* @example Editor.assetdb.init(function (err, results) {
// assets that imported during init
results.forEach(function (result) {
// result.uuid
// result.parentUuid
// result.url
// result.path
// result.type
});
});
*/
init(cb?: (err: any, results: any[]) => void): void;
/**
* Refresh the assets in url, and return the results.
* @param url
* @param cb
*/
refresh(url: string, cb?: Function): void;
/**
* deepQuery
* @param cb
* @example Editor.assetdb.deepQuery(function (err, results) {
results.forEach(function (result) {
// result.name
// result.extname
// result.uuid
// result.type
// result.isSubAsset
// result.children - the array of children result
});
});
*/
deepQuery(cb?: Function): void;
/**
* queryAssets
* @param pattern The url pattern.
* @param assetTypes The asset type(s).
* @param cb The callback function.
*/
queryAssets(pattern: string, assetTypes: string | string[], cb?: (err: Error, results: any[]) => void): void;
/**
* queryMetas
* @param pattern The url pattern.
* @param type The asset type.
* @param cb The callback function.
*/
queryMetas(pattern: string, type: string, cb?: (err: Error, results: any[]) => void): void;
/**
* move
* @param srcUrl The url pattern.
* @param destUrl The asset type.
* @param cb The callback function.
*/
move(srcUrl: string, destUrl: string, cb?: (err: Error, results: any[]) => void): void;
/**
* delete
* @param urls
* @param cb
*/
delete(urls: string[], cb?: (err: Error, results: any[]) => void): void;
/**
* Create asset at url with data.
* @param url
* @param data
* @param cb
*/
create(url: string, data: string, cb?: (err: Error, results: any[]) => void): void;
/**
* Save data to the exists asset at url.
* @param url
* @param data
* @param cb
*/
saveExists(url: string, data: string, cb?: (err: Error, meta: any) => void): void;
/**
* Import raw files to url
* @param rawfiles
* @param url
* @param cb
*/
import(rawfiles: string[], url: string, cb?: (err: Error, results: any[]) => void): void;
/**
* Overwrite the meta by loading it through uuid.
* @param uuid
* @param jsonString
* @param cb
*/
saveMeta(uuid: string, jsonString: string, cb?: (err: Error, meta: any) => void): void;
/**
* Exchange uuid for two assets.
* @param urlA
* @param urlB
* @param cb
*/
exchangeUuid(urlA: string, urlB: string, cb?: (err: Error, results: any[]) => void): void;
/**
* Clear imports.
* @param url
* @param cb
*/
clearImports(url: string, cb?: (err: Error, results: any[]) => void): void;
/**
* Register meta type.
* @param extname
* @param folder Whether it's a folder type.
* @param metaCtor
*/
register(extname: string, folder: boolean, metaCtor: object): void;
/**
* Unregister meta type.
* @param metaCtor
*/
unregister(metaCtor: object): void;
/**
* Get the relative path from mount path to the asset by fspath.
* @param fspath
*/
getRelativePath(fspath: string): string;
/**
* Get the backup file path of asset file.
* @param filePath
*/
getAssetBackupPath(filePath: string): string;
}
}
interface MetaBase {
ver: string;
uuid: string;
}
interface MountInfo {
path: string;
name: string;
type: string;
}
interface Metas {
asset: string[];
folder: string[];
mount: string[];
'custom-asset': string[];
'native-asset': string[];
'animation-clip': string[];
'audio-clip': string[];
'bitmap-font': string[];
}
interface App {
readonly home: string;
readonly name: string;
readonly path: string;
readonly version: string;
}
class Remote {
readonly App: App;
readonly isClosing: boolean;
readonly lang: string;
readonly isNode: boolean;
readonly isElectron: boolean;
readonly isNative: boolean;
readonly isPureWeb: boolean;
readonly isRendererProcess: boolean;
readonly isMainProcess: boolean;
readonly isDarwin: boolean;
readonly isWin32: boolean;
readonly isRetina: boolean;
readonly frameworkPath: string;
readonly dev: boolean;
readonly logfile: string;
readonly themePaths: string[];
readonly theme: string;
readonly showInternalMount: boolean;
readonly metas: Metas;
readonly metaBackupPath: string;
readonly assetBackupPath: string;
readonly libraryPath: string;
readonly importPath: string;
readonly externalMounts: any;
readonly mountsWritable: string;
readonly assetdb: MainProcess.AssetDB;
readonly assetdbInited: boolean;
readonly sceneList: string[];
readonly versions: {
'asset-db': string;
CocosCreator: string;
cocos2d: string;
'editor-framework': string;
}
}
/** Remote 实例 */
const remote: Remote;
/** AssetDB 实例 */
const assetdb: MainProcess.AssetDB;
}
interface AssetInfo {
uuid?: string;
path?: string;
url?: string;
type?: string;
isSubAsset?: boolean;
assetType?: string;
id?: string;
name?: string;
subAssetTypes?: string;
}
declare module Editor.Project {
readonly let id: string;
readonly let name: string;
/** Absolute path for current open project. */
readonly let path: string;
}
declare module Editor.Builder {
/**
*
* @param eventName The name of the event
* @param callback The event callback
*/
function on(eventName: string, callback: (options: BuildOptions, cb: Function) => void): void;
/**
*
* @param eventName The name of the event
* @param callback The event callback
*/
function once(eventName: string, callback: (options: BuildOptions, cb: Function) => void): void;
/**
*
* @param eventName The name of the event
* @param callback The event callback
*/
function removeListener(eventName: string, callback: Function): void;
}
declare module Editor.Scene {
/**
*
* @param packageName
* @param method
* @param cb
*/
function callSceneScript(packageName: string, method: string, cb: (err: Error, msg: any) => void): void;
}
declare module Editor.Panel {
/**
* Open a panel via panelID.
* @param panelID The panel ID
* @param argv
*/
function open(panelID: string, argv?: object): void;
/**
* Close a panel via panelID.
* @param panelID The panel ID
*/
function close(panelID: string): void;
/**
* Find panel frame via panelID.
* @param panelID The panel ID
*/
function find(panelID: string): void;
/**
* Extends a panel.
* @param proto
*/
function extend(proto: object): void;
}
declare module Editor.Selection {
/**
* Select item with its id.
* @param type
* @param id
* @param unselectOthers
* @param confirm
*/
function select(type: string, id: string, unselectOthers?: boolean, confirm?: boolean): void;
/**
* Unselect item with its id.
* @param type
* @param id
* @param confirm
*/
function unselect(type: string, id: string, confirm?: boolean): void;
/**
* Hover item with its id. If id is null, it means hover out.
* @param type
* @param id
*/
function hover(type: string, id: string): string;
/**
*
* @param type
*/
function clear(type: string): void;
/**
*
* @param type
*/
function curActivate(type: string): string[];
/**
*
* @param type
*/
function curGlobalActivate(type: string): string[];
/**
*
* @param type
*/
function curSelection(type: string): string[];
/**
*
* @param items
* @param mode 'top-level', 'deep' and 'name'
* @param func
*/
function filter(items: string[], mode: string, func: Function): string[];
}
declare module Editor.Ipc {
/**
* Send message with ...args to main process asynchronously. It is possible to add a callback as the last or the 2nd last argument to receive replies from the IPC receiver.
* @param message Ipc message.
* @param args Whatever arguments the message needs.
* @param callback You can specify a callback function to receive IPC reply at the last or the 2nd last argument.
* @param timeout You can specify a timeout for the callback at the last argument. If no timeout specified, it will be 5000ms.
*/
function sendToMain(message: string, ...args?: any, callback?: Function, timeout?: number): void;
/**
* Send message with ...args to panel defined in renderer process asynchronously. It is possible to add a callback as the last or the 2nd last argument to receive replies from the IPC receiver.
* @param panelID Panel ID.
* @param message Ipc message.
* @param args Whatever arguments the message needs.
* @param callback You can specify a callback function to receive IPC reply at the last or the 2nd last argument.
* @param timeout You can specify a timeout for the callback at the last argument. If no timeout specified, it will be 5000ms.
*/
function sendToPanel(panelID: string, message: string, ...args?: any, callback?: Function, timeout?: number): void;
/**
* Send message with ...args to all opened window and to main process asynchronously.
* @param message Ipc message.
* @param args Whatever arguments the message needs.
* @param option You can indicate the last argument as an IPC option by Editor.Ipc.option({...}).
*/
function sendToAll(message: string, ...args?: any, option?: object): void;
/**
* Send message with ...args to main process synchronized and return a result which is responded from main process.
* @param message Ipc message.
* @param args Whatever arguments the message needs.
*/
function sendToMainSync(message: string, ...args?: any): void;
/**
* Send message with ...args to main process by package name and the short name of the message.
* @param pkgName Package name.
* @param message Ipc message.
* @param args Whatever arguments the message needs.
*/
function sendToPackage(pkgName: string, message: string, ...args?: any): void;
}
declare module Editor.UI {
module Setting {
/**
* Control the default step for float point input element. Default is 0.1.
* @param value
*/
function stepFloat(value: number): void;
/**
* Control the default step for integer input element. Default is 1.
* @param value
*/
function stepInt(value: number): void;
/**
* Control the step when shift key press down. Default is 10.
* @param value
*/
function shiftStep(value: number): void;
}
module DragDrop {
readonly let dragging: boolean;
function start(e: any, t: any): void;
function end(): void;
function updateDropEffect(e: any, t: any);
function type(e: any);
function filterFiles(e: any);
function items(dataTransfer: DataTransfer): AssetInfo[];
function getDragIcon(e: any);
function options(e: any);
function getLength(e: any): number;
}
}
declare module Editor.GizmosUtils {
function addMoveHandles(e, n, t);
function getCenter(e);
function getCenterWorldPos(n);
function getCenterWorldPos3D(e);
function getRecursiveNodes(e, t);
function getRecursiveWorldBounds3D(e);
function getWorldBounds3D(n);
function snapPixel(e);
function snapPixelWihVec2(e);
}
declare module Editor.Utils {
/**
* Uuid 工具
*/
module UuidUtils {
/**
* 压缩后的 uuid 可以减小保存时的尺寸,但不能做为文件名(因为无法区分大小写并且包含非法字符)。
* 默认将 uuid 的后面 27 位压缩成 18 位,前 5 位保留下来,方便调试。
* 如果启用 min 则将 uuid 的后面 30 位压缩成 20 位,前 2 位保留不变。
* @param uuid
* @param min
*/
function compressUuid(uuid: string, min?: boolean): string;
function compressHex(hexString: string, reservedHeadLength?: number): string;
function decompressUuid(str: string): string;
function isUuid(str: string): boolean;
function uuid(): string;
}
}
declare interface BuildOptions {
actualPlatform: string;
android: { packageName: string };
'android-instant': {
REMOTE_SERVER_ROOT: string;
host: string;
packageName: string;
pathPattern: string;
recordPath: string;
scheme: string;
skipRecord: boolean;
}
apiLevel: string;
appABIs: string[];
appBundle: boolean;
buildPath: string;
buildScriptsOnly: boolean;
debug: string;
dest: string;
embedWebDebugger: boolean;
encryptJs: boolean;
excludeScenes: string[];
excludedModules: string[];
'fb-instant-games': object;
inlineSpriteFrames: boolean;
inlineSpriteFrames_native: boolean;
ios: { packageName: string };
mac: { packageName: string };
md5Cache: boolean;
mergeStartScene: boolean;
optimizeHotUpdate: boolean;
orientation: {
landscapeLeft: boolean;
landscapeRight: boolean;
portrait: boolean;
upsideDown: boolean;
};
packageName: string;
platform: string;
previewHeight: number;
previewWidth: number;
scenes: string[];
sourceMaps: boolean;
startScene: string;
template: string;
title: string;
useDebugKeystore: boolean;
vsVersion: string;
webOrientation: boolean;
win32: object;
xxteaKey: string;
zipCompressJs: string;
project: string;
projectName: string;
debugBuildWorker: boolean;
bundles: bundle[];
}
interface bundle {
/** bundle 的根目录 */
root: string;
/** bundle 的输出目录 */
dest: string;
/** 脚本的输出目录 */
scriptDest: string;
/** bundle 的名称 */
name: string;
/** bundle 的优先级 */
priority: number;
/** bundle 中包含的场景 */
scenes: string[];
/** bundle 的压缩类型 */
compressionType: 'subpackage' | 'normal' | 'none' | 'merge_all_json' | 'zip';
/** bundle 所构建出来的所有资源 */
buildResults: BuildResults;
/** bundle 的版本信息,由 config 生成 */
version: string;
/** bundle 的 config.json 文件 */
config: any;
/** bundle 是否是远程包 */
isRemote: boolean;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment