[add] first

This commit is contained in:
建喵 2022-03-31 08:59:10 +08:00
commit 3e45d31437
37 changed files with 5003 additions and 0 deletions

60
README.md Normal file
View File

@ -0,0 +1,60 @@
# Cocos Creater OutputComponent
## 插件 安裝方式
1. 安裝在 **項目** 底下
> 把插件資料夾放到項目的packages(跟assets同層)
2. 安裝在 **全局** 底下
> 把插件資料夾放到C:\Users\%USERNAME%\.CocosCreator\packages
## 插件稍微說明(都是搬過來的資料 XD)
剩下沒寫的可以到參考資料裡面看看😀
1. 定義你的包描述文件:**package.json**
> **name** String - 定義了包的名字,包的名字是全局唯一的,他關係到你今後在官網服務器上登錄時的名字。
>
> **version** String - 版本號我們推薦使用semver格式管理你的包版本。
>
> **description** String可选 - 一句話描述你的包是做什麼的。
>
> **author** String可选 - 擴展包的作者
>
> **main** String (可选) - 入口程序
>
> **scene-script** String (可选) - 調用引擎API 和項目腳本
>
> **main-menu** Object (可选) - 主菜單定義
>
> **有要使用介面的話:**
>
> **panel** Object (可选) - 定義的面板在package裡的描述
>
> **注意panel的type有兩種**
>
> dockable可停靠面板打開該面板後可以通過拖拽面板標籤到編輯器裡實現擴展面板嵌入到編輯器中。下面我們介紹的面板入口程序都是按照可停靠面板的要求聲明的。
>
> simple簡單Web面板不可停靠到編輯器主窗口相當於一份通用的HTML前端頁面。詳細情況請見定義簡單面板。
>
> 在simple-package文件夾下面創建一個panel文件夾,然後在panel文件夾下創建一個index.js或者一個html文件都可以
2. 入口程序:**main.js**
3. 定義介面以及按鈕綁定的方法,和主進程的通信:**index.js**
3. 可以使用包括全部引擎API 和用戶組件腳本里聲明的方法和屬性:**scene-obtain..js**
> 可以在擴展包中獲取到場景裡的Canvas根節點有多少子節點當然還可以用來對場景節點進行更多的查詢和操作。
## 參考資料
* 你的第一個擴展包
> https://docs.cocos.com/creator/manual/zh/extension/your-first-extension.html
* CocosCreator拓展编辑器
> https://blog.csdn.net/qq_34772097/category_9577457.html
* Cocos Creator Editor 編輯器擴展API記錄
> https://blog.csdn.net/kingbook928/article/details/108659319
> https://blog.csdn.net/qq_17209641/article/details/106822296
> https://forum.cocos.org/t/creator-api/92605

136
core/FileUtil.js Normal file
View File

@ -0,0 +1,136 @@
let fs = require("fire-fs");
let path = require("fire-path");
let self = module.exports = {
getDirAllFiles(dirPath, result) {
let files = fs.readdirSync(dirPath);
files.forEach((val, index) => {
let fPath = path.join(dirPath, val);
let stats = fs.statSync(fPath);
if (stats.isDirectory()) {
this.getDirAllFiles(fPath, result);
} else if (stats.isFile()) {
result.push(fPath);
}
});
},
getFileString(fileList, options) {
let curIndex = 0;
let totalIndex = fileList.length;
let str = {};
for (let key in fileList) {
let filePath = fileList[key];
let b = this._isFileExit(filePath);
if (b) {
fs.readFile(filePath, 'utf-8', function (err, data) {
if (!err) {
self._collectString(data, str);
} else {
console.log("error: " + filePath);
}
self._onCollectStep(filePath, ++curIndex, totalIndex, str, options);
})
} else {
self._onCollectStep(filePath, ++curIndex, totalIndex, str, options);
}
}
},
_onCollectStep(filePath, cur, total, str, data) {
if (data && data.stepCb) {
data.stepCb(filePath, cur, total);
}
if (cur >= total) {
self._onCollectOver(str, data);
}
},
_onCollectOver(collectObjArr, data) {
let strArr = [];
let str = "";
for (let k in collectObjArr) {
str += k;
strArr.push(k);
}
// console.log("一共有" + strArr.length + "个字符, " + strArr);
console.log("一共有" + strArr.length + "个字符");
if (data && data.compCb) {
data.compCb(str);
}
},
mkDir(path) {
try {
fs.mkdirSync(path);
} catch (e) {
if (e.code !== 'EEXIST') throw e;
}
},
isFileExit(file) {
try {
fs.accessSync(file, fs.F_OK);
} catch (e) {
return false;
}
return true;
},
_collectString(data, collectObject) {
for (let i in data) {
let char = data.charAt(i);
if (collectObject[char]) {
collectObject[char]++;
} else {
collectObject[char] = 1;
}
}
},
emptyDir(rootFile) {
//删除所有的文件(将所有文件夹置空)
let emptyDir = function (fileUrl) {
let files = fs.readdirSync(fileUrl);//读取该文件夹
for (let k in files) {
let filePath = path.join(fileUrl, files[k]);
let stats = fs.statSync(filePath);
if (stats.isDirectory()) {
emptyDir(filePath);
} else {
fs.unlinkSync(filePath);
console.log("删除文件:" + filePath);
}
}
};
//删除所有的空文件夹
let rmEmptyDir = function (fileUrl) {
let files = fs.readdirSync(fileUrl);
if (files.length > 0) {
for (let k in files) {
let rmDir = path.join(fileUrl, files[k]);
rmEmptyDir(rmDir);
}
if (fileUrl !== rootFile) {// 不删除根目录
fs.rmdirSync(fileUrl);
console.log('删除空文件夹' + fileUrl);
}
} else {
if (fileUrl !== rootFile) {// 不删除根目录
fs.rmdirSync(fileUrl);
console.log('删除空文件夹' + fileUrl);
}
}
};
emptyDir(rootFile);
rmEmptyDir(rootFile);
},
/*
is_fileType($('#uploadfile').val(), 'doc,pdf,txt,wps,odf,md,png,gif,jpg')
* */
is_fileType(filename, types) {
types = types.split(',');
let pattern = '\.(';
for (let i = 0; i < types.length; i++) {
if (0 !== i) {
pattern += '|';
}
pattern += types[i].trim();
}
pattern += ')$';
return new RegExp(pattern, 'i').test(filename);
}
}

55
i18n/en.js Normal file
View File

@ -0,0 +1,55 @@
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',
};

55
i18n/zh.js Normal file
View File

@ -0,0 +1,55 @@
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': '引用查找结果将需要手动展开,拯救你的控制台',
};

1649
lib/node-fetch.js Normal file

File diff suppressed because it is too large Load Diff

7
lib/vue.global.prod.js Normal file

File diff suppressed because one or more lines are too long

174
main.js Normal file
View File

@ -0,0 +1,174 @@
"use strict";
let fs = require("fs");
let path = require("path");
const FileUtil = require('./core/FileUtil');
const { print, translate } = require('./src/eazax/editor-main-util');
const EditorAPI = require('./src/main/editor-api');
const Parser = require('./src/main/parser');
const Finder = require('./src/main/finder');
const Printer = require('./src/main//printer');
module.exports = {
load() {
this.init();
},
unload() {
// Editor.log("卸載執行");
},
/** 初始化 */
init() {
// this.createDirectory();
},
/** 創建Spine節點 */
sourcecheck(...args) {
let self = this;
try {
// Editor.log("文件1: " + JSON.stringify(args[0]._value));
Editor.Scene.callSceneScript("sourcecheck", "getFsPath", { args: args }, async function (err, response) {
// Editor.log("getFsPath response: " + response);
let res = JSON.parse(response);
if (!FileUtil.isFileExit(res.fsPath)) {
Editor.error(`路徑錯誤: ${res.fsPath}`);
Editor.log(`檢查停止`);
return;
}
Editor.log(`檢查路徑: ${res.url}`);
let files = [];
files = self.fileSearch(res.fsPath, res.url, files);
// Editor.log(`總共有${files.length}個物件`);
for (let i = 0; i < files.length; i++) {
// for (let i = 0; i < 1; i++) {
await self.checkreq(files[i][0], files[i][1]);
}
Editor.log(`檢查完畢`);
});
} catch (error) {
Editor.log(`sourcecheck error: ${error}`);
}
},
/**
*
* @param fsPath fsPath
*/
fileSearch(fsPath, url, files) {
try {
// Editor.log(`fsPath: ${fsPath}`);
if (FileUtil.isFileExit(fsPath)) {
fs.readdirSync(fsPath).forEach(file => {
// Editor.log(`file: ${file}`);
let path = `${fsPath}\\${file}`
if (this.checkfile(path)) {
files = this.fileSearch(path, url, files);
} else if (this.checkusefile(path)) {
// this.checkreq(path, url);
files.push([path, url])
}
});
return files;
} else {
// Editor.error(`fsPath error: ${fsPath}`);
return files;
}
} catch (error) {
Editor.error(`fileSearch error: ${error}`);
}
},
/**
* 檢查引用
* @param fsPath fsPath
*/
async checkreq(fsPath, myurl) {
// Editor.log(`file: ${fsPath}`);
try {
let uuid = EditorAPI.fspathToUuid(fsPath);
// Editor.log(`uuid: ${uuid}, file: ${fsPath}`);
const assetInfo = EditorAPI.assetInfoByUuid(uuid),
shortUrl = assetInfo.url.replace('db://', '');
// 查找引用
// print('log', '🔍', `${translate('find-asset-refs')} ${shortUrl}`);
let refs = null;
try {
refs = await Finder.findByUuid(uuid);
} catch (error) {
Editor.error(`findByUuid error: ${error}, fsPath: ${fsPath}`);
return;
}
// Editor.log(`refs: ${JSON.stringify(refs)}`);
let newrefs = [];
for (let i = 0; i < refs.length; i++) {
let ref = refs[i];
let url = ref.url;
if (url.indexOf(myurl) === -1) {
// Editor.log(`url: ${url}, myurl: ${myurl}, fsPath: ${EditorAPI.fspathToUrl(fsPath)}`);
newrefs.push(ref);
}
}
if (newrefs.length > 0) {
// 打印结果
Printer.printResult({
type: assetInfo.type,
uuid: uuid,
url: assetInfo.url,
path: assetInfo.path,
refs: newrefs,
});
}
} catch (error) {
Editor.error(`checkreq error: ${error}, fsPath: ${fsPath}`);
}
},
/**
* 檢查副檔名
* @param fsPath fsPath
*/
checkusefile(fsPath) {
try {
if ((fsPath.indexOf(".PNG") !== -1 || fsPath.indexOf(".png") !== -1 || fsPath.indexOf(".jpg") !== -1 || fsPath.indexOf(".anim") !== -1 || fsPath.indexOf(".fnt") !== -1) && fsPath.indexOf(".meta") === -1) {
return true;
}
return false;
} catch (error) {
Editor.error(`checkfile error: ${error}`);
return false;
}
},
/**
* 檢查副檔名
* @param fsPath fsPath
*/
checkfile(fsPath) {
try {
if (fsPath.indexOf(".fire") === -1 && fsPath.indexOf(".fnt") === -1 && fsPath.indexOf(".anim") === -1 && fsPath.indexOf(".prefab") === -1 && fsPath.indexOf(".meta") === -1 && fsPath.indexOf(".pac") === -1 && fsPath.indexOf(".PNG") === -1 && fsPath.indexOf(".png") === -1 && fsPath.indexOf(".jpg") === -1) {
return true;
}
return false;
} catch (error) {
Editor.error(`checkfile error: ${error}`);
return false;
}
},
messages: {
/** 打開面板 */
"open-panel"() {
Editor.Panel.open("sourcecheck");
},
/** 保存按鈕點擊 */
"run-click"(event, ...args) {
this.sourcecheck(...args);
},
// /** 面板加載完成 */
// "panel-load-finish"(evnet, ...args) {
// Editor.Scene.callSceneScript("sourcecheck", "get-default-info", { args: args }, function (err, response) {
// // Editor.log("callSceneScript: " + response);
// Editor.Ipc.sendToPanel("sourcecheck", "setDefault", response);
// });
// },
},
}

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "sourcecheck",
"version": "0.0.1",
"description": "Sample-Package",
"author": "Sample",
"main": "main.js",
"scene-script": "scene-obtain.js",
"engineSupport": true,
"main-menu": {
"扩展/Source Check": {
"message": "sourcecheck:open-panel"
}
},
"panel": {
"main": "panel/index.js",
"type": "dockable",
"title": "Source Check",
"width": 400,
"height": 350
}
}

62
panel/index.js Normal file
View File

@ -0,0 +1,62 @@
Editor.Panel.extend({
style: `
:host { margin: 5px; }
h2 { color: #f90; }
.bottom {
height: 30px;
}
`,
template: `
<h2 style="text-align:center">Source Check</h2>
<hr />
<div>
<!-- 1. 把要檢查的資料夾拉進來<br> -->
1. 填入要檢查的資料夾(從assets開始算)<br>
2. 執行<br>
PS. 目前只有檢查pngjpganimfnt
</div>
<hr />
<div>
要檢查的資料夾 db://asstes/
<ui-input placeholder="UIScenes/Backpack" id="filepath"></ui-input>
<!-- <ui-asset class="flex-1" type="native-asset" droppable="asset" id="node"></ui-asset> -->
</div>
<hr />
<hr />
<div style="text-align:right">
<ui-button id="run" class="blue">執行</ui-button>
</div>
`,
$: {
/** filepath */
filepath: "#filepath",
/** 生成按鈕 */
run: "#run",
},
ready() {
// Editor.Ipc.sendToMain("sourcecheck:panel-load-finish");
this.onClickRun();
},
/** 保存按鈕點擊事件 */
onClickRun() {
this.$run.addEventListener("confirm", () => {
if (!this.$filepath._value) {
Editor.error("請輸入要檢查的資料夾");
return;
}
Editor.Ipc.sendToMain("sourcecheck:run-click", this.$filepath);
});
},
messages: {
// "setDefault": function (event, ...agrs) {
// if (event.reply) {
// //if no error, the first argument should be null
// event.reply(null, "Fine, thank you!");
// }
// }
}
});

37
scene-obtain.js Normal file
View File

@ -0,0 +1,37 @@
"use strict";
let fs = require("fs");
let path = require("path");
module.exports = {
/**
* 獲取場景節點下的配置信息
* @param event event
* @param data 這邊把panel的Node帶進來
*/
"getFsPath": function (event, request) {
let self = this;
var response = null;
var args = request.args;
try {
let url = `db://assets/${args[0]._value}`;
url = url.replace(/\\/g, "/");
// let url = `db://assets/=SceneLobby`;
// Editor.log(`url: ${url}`);
let fsPath = Editor.remote.assetdb.urlToFspath(url);
// Editor.log(`fsPath: ${fsPath}`);
// this.fileSearch(fsPath);
let res = {
url: url,
fsPath, fsPath
}
response = JSON.stringify(res);
} catch (error) {
Editor.log(`getFsPath error: ${error}`);
}
if (event.reply) {
event.reply(null, response);
}
},
};

View File

@ -0,0 +1,104 @@
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;

61
src/eazax/browser-util.js Normal file
View File

@ -0,0 +1,61 @@
/**
* 浏览器工具
* @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;

37
src/eazax/color-util.js Normal file
View File

@ -0,0 +1,37 @@
/**
* 颜色工具
* @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;

View File

@ -0,0 +1,201 @@
/*
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;
}

197
src/eazax/css/cocos-tag.css Normal file
View File

@ -0,0 +1,197 @@
/*
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;
}

View File

@ -0,0 +1,18 @@
: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;
}

View File

@ -0,0 +1,53 @@
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;

View File

@ -0,0 +1,147 @@
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;

View File

@ -0,0 +1,26 @@
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;

158
src/eazax/file-util.js Normal file
View File

@ -0,0 +1,158 @@
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;

36
src/eazax/i18n.js Normal file
View File

@ -0,0 +1,36 @@
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;

81
src/eazax/main-event.js Normal file
View File

@ -0,0 +1,81 @@
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;

47
src/eazax/package-util.js Normal file
View File

@ -0,0 +1,47 @@
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;

View File

@ -0,0 +1,80 @@
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;

92
src/eazax/updater.js Normal file
View File

@ -0,0 +1,92 @@
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;

61
src/eazax/version-util.js Normal file
View File

@ -0,0 +1,61 @@
/**
* 版本工具
* @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;

80
src/eazax/window-util.js Normal file
View File

@ -0,0 +1,80 @@
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;

98
src/main/editor-api.js Normal file
View File

@ -0,0 +1,98 @@
/**
* 编辑器 API用于抹平不同版本编辑器之间的差异
* @author 陈皮皮 (ifaswind)
* @version 20210830
*/
const EditorAPI = {
/**
* 当前语言
* @returns {string}
*/
getLanguage() {
return Editor.lang || Editor.I18n.getLanguage();
},
/**
* 绝对路径转为UUID
* @param {string} fspath
*/
fspathToUuid(fspath) {
return Editor.assetdb.fspathToUuid(fspath);
},
/**
* 绝对路径转为编辑器资源路径
* @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;

248
src/main/finder.js Normal file
View File

@ -0,0 +1,248 @@
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 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);
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);
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) {
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),
});
}
}
};
// 遍历资源目录下的文件
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;

155
src/main/index.js Normal file
View File

@ -0,0 +1,155 @@
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,
};

70
src/main/object-util.js Normal file
View File

@ -0,0 +1,70 @@
/**
* 对象工具
* @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;

86
src/main/panel-manager.js Normal file
View File

@ -0,0 +1,86 @@
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;

161
src/main/parser.js Normal file
View File

@ -0,0 +1,161 @@
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;

108
src/main/printer.js Normal file
View File

@ -0,0 +1,108 @@
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;

View File

@ -0,0 +1,61 @@
* {
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;
}

View File

@ -0,0 +1,90 @@
<!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>

View File

@ -0,0 +1,191 @@
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');