适配 3.x

This commit is contained in:
陈皮皮
2022-01-28 19:37:49 +08:00
parent d9dd7f2191
commit c9d54db4b4
23 changed files with 1663 additions and 2407 deletions

View File

@@ -94,7 +94,7 @@ Click on *Extension -> Skeleton Viewer -> View* option to open the view panel.
Click on *Extension -> Skeleton Viewer -> Settings* option to open the setting panel.
In the setting panel, you can choose a hotkey(shortcut, for opening the view panel quickly) in preset list, or customize one by yourself.
In the setting panel, you can choose a shortcut key(for opening the view panel quickly) in preset list, or customize one 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.

View File

@@ -40,17 +40,19 @@ module.exports = {
'canvasColor': 'Canvas Color',
// settings
'none': 'None',
'selectKey': 'Hotkey',
'selectKeyTooltip': 'Choose a hotkey',
'selectKey': 'Shortcut Key',
'selectKeyTooltip': 'Choose a shortcut key',
'customKey': 'Custom',
'customKeyPlaceholder': 'Choose a hotkey above or customize one by yourself',
'customKeyTooltip': 'You can also customize your own hotkey',
'autoCheck': 'Auto Check Update',
'autoCheckTooltip': 'Check if there is a new version when the extension is loaded',
'customKeyPlaceholder': 'Choose a shortcut key above or customize one by yourself',
'customKeyTooltip': 'You can also customize your own shortcut key',
'alwaysOnTop': 'Always On Top',
'alwaysOnTopTooltip': 'Keep view panel on top (3.3+)',
'autoCheckUpdate': 'Auto Check Update',
'autoCheckUpdateTooltip': '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',
'quoteError': 'Do not use double quotes!',
'customKeyError': 'Please specify a hotkey!',
'customKeyError': 'Please specify a shortcut key!',
};

View File

@@ -45,8 +45,10 @@ module.exports = {
'customKey': '自定义',
'customKeyPlaceholder': '在上方选择一个快捷键或自定义一个快捷键',
'customKeyTooltip': '自定义快捷键',
'autoCheck': '自动检查更新',
'autoCheckTooltip': '扩展启动时自动检查是否有新版本',
'alwaysOnTop': '窗口置顶',
'alwaysOnTopTooltip': '预览窗口将始终显示在最前端 (3.3+)',
'autoCheckUpdate': '自动检查更新',
'autoCheckUpdateTooltip': '扩展启动时自动检查是否有新版本',
'reference': '· 快捷键自定义请参考:',
'accelerator': '键盘快捷键',
'repository': '· 本扩展的 Git 仓库:',

BIN
images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 551 B

After

Width:  |  Height:  |  Size: 551 B

View File

@@ -1,9 +1,10 @@
{
"name": "ccc-skeleton-viewer",
"version": "1.3.0.20210915",
"package_version": 2,
"version": "1.4.0.20220128",
"description": "用于快速预览骨骼动画,提供独立窗口,也可以附着在编辑器中...",
"author": {
"name": "陈皮皮 (ifaswind)",
"author": "陈皮皮 (ifaswind)",
"author-info": {
"email": "952157129@qq.com",
"url": "https://chenpipi.cn",
"wechat": "im_chenpipi",
@@ -13,43 +14,104 @@
"repository": "https://gitee.com/ifaswind/ccc-skeleton-viewer",
"license": "MIT",
"main": "src/main/index.js",
"scene-script": "src/main/scene-worker.js",
"main-menu": {
"i18n:MAIN_MENU.package.title/i18n:ccc-skeleton-viewer.name/i18n:ccc-skeleton-viewer.view": {
"message": "ccc-skeleton-viewer:open-view-panel",
"icon": "/images/view.png"
"contributions": {
"menu": [
{
"path": "i18n:menu.extension/i18n:ccc-skeleton-viewer.name",
"label": "i18n:ccc-skeleton-viewer.view",
"message": "open-view-panel",
"icon": "/images/view.png"
},
{
"path": "i18n:menu.extension/i18n:ccc-skeleton-viewer.name",
"label": "i18n:ccc-skeleton-viewer.settings",
"message": "open-settings-panel",
"icon": "/images/settings.png"
},
{
"path": "i18n:menu.extension/i18n:ccc-skeleton-viewer.name",
"label": "i18n:ccc-skeleton-viewer.checkUpdate",
"message": "menu-check-update",
"icon": "/images/update.png"
},
{
"path": "i18n:menu.extension/i18n:ccc-skeleton-viewer.name",
"label": "v1.3.0.20210915",
"message": "menu-version",
"icon": "/images/version.png"
}
],
"messages": {
"open-view-panel": {
"public": true,
"description": "打开预览面板",
"methods": [
"openViewPanel"
]
},
"open-settings-panel": {
"public": true,
"description": "打开设置面板",
"methods": [
"openSettingsPanel"
]
},
"menu-check-update": {
"public": true,
"description": "检查扩展是否有更新",
"methods": [
"menuCheckUpdate"
]
},
"menu-version": {
"public": false,
"methods": [
"menuVersion"
]
},
"scene:ready": {
"methods": [
"onSceneReady"
]
},
"selection:select": {
"methods": [
"onSelectionSelect"
]
}
},
"i18n:MAIN_MENU.package.title/i18n:ccc-skeleton-viewer.name/i18n:ccc-skeleton-viewer.settings": {
"message": "ccc-skeleton-viewer:open-settings-panel",
"icon": "/images/setting.png"
},
"i18n:MAIN_MENU.package.title/i18n:ccc-skeleton-viewer.name/i18n:ccc-skeleton-viewer.checkUpdate": {
"message": "ccc-skeleton-viewer:menu-check-update",
"icon": "/images/update.png"
},
"i18n:MAIN_MENU.package.title/i18n:ccc-skeleton-viewer.name/v1.3.0.20210915": {
"message": "ccc-skeleton-viewer:menu-version",
"icon": "/images/version.png"
"shortcuts": [
{
"message": "open-view-panel",
"mac": "",
"win": ""
}
],
"assets": {
"menu": {
"methods": "src/editor/assets-menu.js",
"createMenu": "",
"assetMenu": "onAssetMenu",
"dbMenu": "",
"panelMenu": ""
}
}
},
"panel.view": {
"main": "src/renderer/view/entry.js",
"type": "dockable",
"title": "i18n:ccc-skeleton-viewer.name",
"width": 500,
"height": 600,
"min-width": 300,
"min-height": 250
},
"reload": {
"renderer": [
"src/renderer/**/*"
],
"ignore": [
"config.json",
"CHANGELOG.md",
"README.md",
"README.en.md"
]
"panels": {
"view": {
"title": "i18n:ccc-skeleton-viewer.name",
"type": "dockable",
"main": "src/renderer/view/entry.js",
"icon": "/images/icon.png",
"size": {
"width": 500,
"height": 600,
"min-width": 300,
"min-height": 250
},
"flags": {
"alwaysOnTop": true
}
}
}
}

View File

@@ -1,6 +1,5 @@
const Path = require('path');
const Fs = require('fs');
const PackageUtil = require('../eazax/package-util');
/** 配置文件路径 */
const CONFIG_PATH = Path.join(__dirname, '../../config.json');
@@ -8,14 +7,8 @@ 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 = 'view';
/** package.json 中的菜单项 key */
const MENU_ITEM_KEY = `i18n:MAIN_MENU.package.title/i18n:${PACKAGE_NAME}.name/i18n:${PACKAGE_NAME}.${ACTION_NAME}`;
/** 快捷键消息 */
const SHORTCUT_MESSAGE = 'open-view-panel';
/**
* 配置管理器
@@ -36,8 +29,8 @@ const ConfigManager = {
* 读取配置
*/
get() {
// 配置
const config = ConfigManager.defaultConfig;
// 配置
if (Fs.existsSync(CONFIG_PATH)) {
const localConfig = JSON.parse(Fs.readFileSync(CONFIG_PATH));
for (const key in config) {
@@ -46,54 +39,82 @@ const ConfigManager = {
}
}
}
// 快捷键
config.hotkey = ConfigManager.getAccelerator();
// 快捷键和置顶
const packageConfig = ConfigManager.getPackageConfig();
config.shortcutKey = packageConfig.shortcutKey;
config.alwaysOnTop = packageConfig.alwaysOnTop;
// Done
return config;
},
/**
* 保存配置
* @param {*} 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);
ConfigManager.setPackageConfig({
shortcutKey: value.shortcutKey,
alwaysOnTop: value.alwaysOnTop,
});
},
/**
* 获取快捷键
* @returns {string}
* 获取 package 配置
* @returns {{ shortcutKey: string, alwaysOnTop: boolean }}
*/
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'];
getPackageConfig() {
const config = {
shortcutKey: '',
alwaysOnTop: false,
};
const package = JSON.parse(Fs.readFileSync(PACKAGE_PATH));
// 快捷键
const shortcuts = package['contributions']['shortcuts'];
if (shortcuts && shortcuts.length > 0) {
config.shortcutKey = shortcuts[0]['win'] || shortcuts[0]['mac'] || '';
}
// 置顶
config.alwaysOnTop = package['panels']['view']['flags']['alwaysOnTop'];
// Done
return config;
},
/**
* 设置 package 配置
* @param {{ shortcutKey: string, alwaysOnTop: boolean }} config
*/
setPackageConfig(config) {
const package = JSON.parse(Fs.readFileSync(PACKAGE_PATH));
// 快捷键
let shortcuts = package['contributions']['shortcuts'];
if (!shortcuts) {
shortcuts = package['contributions']['shortcuts'] = [];
}
let item = shortcuts[0];
if (!item) {
item = shortcuts[0] = {
message: SHORTCUT_MESSAGE,
mac: '',
mac: '',
};
}
if (config.shortcutKey != undefined) {
item['win'] = item['mac'] = config.shortcutKey;
} else {
item['win'] = item['mac'] = '';
}
// 置顶
package['panels']['view']['flags']['alwaysOnTop'] = config.alwaysOnTop;
// 写入
Fs.writeFileSync(PACKAGE_PATH, JSON.stringify(package, null, 2));
},

View File

@@ -0,0 +1,125 @@
/**
* 编辑器适配器
*/
const EditorAdapter = {
/**
* 获取编辑器语言
* @returns {string}
*/
getLanguage() {
return Editor.I18n.getLanguage();
},
/**
* 获取资源信息
* @param {string} uuid
* @returns {Promise<any>}
*/
getAssetInfoByUuid(uuid) {
return Editor.Message.request('asset-db', 'query-asset-info', uuid);
},
/**
* 获取资源 META
* @param {string} uuid
* @returns {Promise<any>}
*/
getAssetMetaByUuid(uuid) {
return Editor.Message.request('asset-db', 'query-asset-meta', uuid);
},
/**
* 获取资源绝对路径
* @param {string} uuid
* @returns {Promise<string>}
*/
getPathByUuid(uuid) {
return Editor.Message.request('asset-db', 'query-path', uuid);
},
/**
* 获取资源绝对路径
* @param {string} url
* @returns {Promise<string>}
*/
getPathByUrl(url) {
return Editor.Message.request('asset-db', 'query-path', url);
},
/**
* 获取资源 uuid
* @param {string} path
* @returns {Promise<string>}
*/
getUuidByPath(path) {
return Editor.Message.request('asset-db', 'query-uuid', path);
},
/**
* 面板
*/
Panel: {
/**
* 打开面板
* @param {string} panel
*/
open(panel) {
Editor.Panel.open(panel);
},
/**
* 关闭面板
* @param {string} panel
*/
close(panel) {
Editor.Panel.close(panel);
},
},
/**
* 选择
*/
Selection: {
/**
* 清除编辑器选中
* @param {'asset' | 'node'} type
*/
clear(type) {
Editor.Selection.clear(type);
},
/**
* 清除编辑器选中
* @param {'asset' | 'node'} type
* @param {string} uuid
*/
select(type, uuid) {
Editor.Selection.select(type, uuid);
},
/**
* 获取编辑器选中的类型
* @returns {'asset' | 'node'}
*/
getSelectedType() {
return Editor.Selection.getLastSelectedType();
},
/**
* 获取编辑器选中
* @param {'asset' | 'node'} type
* @returns {string}
*/
getSelected(type) {
return Editor.Selection.getSelected(type);
},
},
};
module.exports = EditorAdapter;

View File

@@ -1,5 +1,5 @@
/*
Cocos Creator 风格样式
Cocos Creator (2.x) 编辑器风格样式
版本: 20210911
作者: 陈皮皮 (ifaswind)
主页: https://gitee.com/ifaswind

View File

@@ -1,5 +1,5 @@
/*
Cocos Creator 风格标签 (橙黑)
Cocos Creator (2.x) 编辑器风格标签 (橙黑)
版本: 20210725
作者: 陈皮皮 (ifaswind)
主页: https://gitee.com/ifaswind

View File

@@ -1,3 +1,11 @@
/*
Cocos Creator (2.x) 编辑器风格颜色 (橙黑)
版本: 20210725
作者: 陈皮皮 (ifaswind)
主页: https://gitee.com/ifaswind
公众号: 菜鸟小栈
*/
:root {
/* 背景颜色 */
--eazax-bg-color: #454545;

36
src/editor/assets-menu.js Normal file
View File

@@ -0,0 +1,36 @@
const RendererEvent = require("../eazax/renderer-event");
// 资源管理器菜单
exports.onAssetMenu = function (assetInfo) {
if (test(assetInfo.file)) {
return [
// 骨骼查看器 -> 预览
{
label: 'i18n:ccc-skeleton-viewer.name',
submenu: [
{
label: 'i18n:ccc-skeleton-viewer.view',
enabled: true,
click() {
// (主进程)预览
RendererEvent.send('view', assetInfo.uuid);
},
},
],
},
];
}
return [];
};
/**
* 测试
* @param {string} name
*/
function test(name) {
return (
name.endsWith('json') || name.endsWith('skel') ||
name.endsWith('png') ||
name.endsWith('atlas') || name.endsWith('txt')
);
}

View File

@@ -1,19 +1,28 @@
'use strict';
const MainEvent = require('../eazax/main-event');
const EditorMainKit = require('../eazax/editor-main-kit');
const { checkUpdate } = require('../eazax/editor-main-util');
const { checkUpdate, reload } = require('../eazax/editor-main-util');
const { openRepository } = require('../eazax/package-util');
const ConfigManager = require('../common/config-manager');
const Opener = require('./opener');
const PanelManager = require('./panel-manager');
const Updater = require('../eazax/updater');
const EditorAdapter = require('../common/editor-adapter');
/**
* 生命周期:加载
*/
function load() {
// 设置仓库分支
Updater.branch = 'v3';
// 监听事件
EditorMainKit.register();
MainEvent.on('ready', onReadyEvent);
MainEvent.on('close', onCloseEvent);
MainEvent.on('reload', onReloadEvent);
MainEvent.on('select', onSelectEvent);
MainEvent.on('view', onViewEvent);
}
/**
@@ -23,18 +32,49 @@ function unload() {
// 取消事件监听
EditorMainKit.unregister();
MainEvent.removeAllListeners('ready');
MainEvent.removeAllListeners('close');
MainEvent.removeAllListeners('reload');
MainEvent.removeAllListeners('select');
MainEvent.removeAllListeners('view');
}
/**
* (渲染进程)就绪事件回调
* (渲染进程)预览面板就绪事件回调
* @param {Electron.IpcMainEvent} event
*/
function onReadyEvent(event) {
// 保存预览面板的 WebContents
PanelManager.viewWebContents = event.sender;
// 检查编辑器选中
Opener.checkEditorCurSelection();
}
/**
* (渲染进程)预览面板关闭事件回调
* @param {Electron.IpcMainEvent} event
*/
function onCloseEvent(event) {
PanelManager.viewWebContents = null;
}
/**
* (渲染进程)重新加载事件回调
* @param {Electron.IpcMainEvent} event
*/
function onReloadEvent(event) {
reload();
}
/**
* (渲染进程)预览事件回调
* @param {Electron.IpcMainEvent} event
*/
function onViewEvent(event, uuid) {
PanelManager.openViewPanel();
EditorAdapter.Selection.clear('asset');
EditorAdapter.Selection.select('asset', uuid);
}
/**
* (渲染进程)选择文件事件回调
* @param {Electron.IpcMainEvent} event
@@ -48,74 +88,65 @@ function onSelectEvent(event) {
* @param {string} type 类型
* @param {string[]} uuids uuids
*/
function onEditorSelection(type, uuids) {
if (PanelManager.getViewPanel()) {
function onSelectionSelect(type, uuids) {
if (PanelManager.getViewPanelWebContents()) {
Opener.identifySelection(type, uuids);
}
}
module.exports = {
exports.load = load;
exports.unload = unload;
exports.methods = {
/**
* 扩展消息
* 打开预览面板
*/
messages: {
/**
* 编辑器选中事件回调
* @param {Electron.IpcMainEvent} event
* @param {string} type 类型
* @param {string[]} uuids uuids
*/
'selection:selected'(event, type, uuids) {
onEditorSelection(type, uuids);
},
/**
* 打开预览面板
*/
'open-view-panel'() {
PanelManager.openViewPanel();
},
/**
* 打开设置面板
*/
'open-settings-panel'() {
PanelManager.openSettingsPanel();
},
/**
* 检查更新
*/
'menu-check-update'() {
checkUpdate(true);
},
/**
* 版本
* @param {*} event
*/
'menu-version'(event) {
openRepository();
},
/**
* 场景面板加载完成后
* @param {*} event
*/
'scene:ready'(event) {
// 自动检查更新
const config = ConfigManager.get();
if (config.autoCheckUpdate) {
checkUpdate(false);
}
},
openViewPanel() {
PanelManager.openViewPanel();
},
load,
/**
* 打开设置面板
*/
openSettingsPanel() {
PanelManager.openSettingsPanel();
},
unload,
/**
* 检查更新
*/
menuCheckUpdate() {
checkUpdate(true);
},
/**
* 版本号
*/
menuVersion() {
openRepository();
},
/**
* 场景编辑器就绪后
*/
onSceneReady() {
// 自动检查更新
const config = ConfigManager.get();
if (config.autoCheckUpdate) {
checkUpdate(false);
}
},
/**
* 编辑器选中事件回调
* @param {'asset' | 'node'} type 类型
* @param {string} uuid uuid
*/
onSelectionSelect(type, uuid) {
const uuids = EditorAdapter.Selection.getSelected(type);
onSelectionSelect(type, uuids);
},
};

View File

@@ -1,28 +1,25 @@
const { dialog } = require('electron');
const Fs = require('fs');
const Path = require('path');
const EditorAdapter = require('../common/editor-adapter');
const { print, translate } = require('../eazax/editor-main-util');
const MainEvent = require('../eazax/main-event');
const PackageUtil = require('../eazax/package-util');
const PanelManager = require('./panel-manager');
/** 包名 */
const PACKAGE_NAME = PackageUtil.name;
/**
* 资源检索器
*/
const Opener = {
/**
* 编辑器选择
* @param {string} type
* @param {'asset' | 'node'} type
* @param {string[]} uuids
*/
async identifySelection(type, uuids) {
// 选中资源
if (type === 'asset') {
if (type === 'asset') { // 选中资源
Opener.identifyByUuids(uuids);
}
// 选中节点
else if (type === 'node') {
} else if (type === 'node') { // 选中节点
const skeletonUuid = await Opener.querySkeletonOnNode(uuids[0]);
if (skeletonUuid) {
Opener.identifyByUuids([skeletonUuid]);
@@ -38,9 +35,10 @@ const Opener = {
* 检查编辑器当前选中
*/
checkEditorCurSelection() {
const { type, id } = Editor.Selection.curGlobalActivate();
if (type && id) {
Opener.identifySelection(type, [id]);
const type = EditorAdapter.Selection.getSelectedType(),
uuids = EditorAdapter.Selection.getSelected(type);
if (type && uuids && uuids.length > 0) {
Opener.identifySelection(type, uuids);
} else {
Opener.updateView(null);
}
@@ -48,15 +46,21 @@ const Opener = {
/**
* 查找节点上引用的骨骼资源
* @param {string} uuid
* @param {string} nodeUuid
* @returns {Promise<string>}
*/
querySkeletonOnNode(uuid) {
return new Promise(res => {
Editor.Scene.callSceneScript(PACKAGE_NAME, 'query-skeleton', uuid, (error, uuid) => {
res(error ? null : uuid);
});
});
async querySkeletonOnNode(nodeUuid) {
const node = await Editor.Message.request('scene', 'query-node', nodeUuid);
if (node && node['__comps__']) {
const components = node['__comps__'];
for (let i = 0; i < components.length; i++) {
if (components[i].type === 'sp.Skeleton') {
const uuid = components[i].value.skeletonData.value.uuid;
return (uuid !== '' ? uuid : null);
}
}
}
return null;
},
/**
@@ -85,40 +89,36 @@ const Opener = {
* 通过 uuid 识别资源
* @param {string[]} uuids
*/
identifyByUuids(uuids) {
async identifyByUuids(uuids) {
// 资源路径
let spinePath, texturePath, atlasPath;
let skeletonPath, texturePath, atlasPath;
// 遍历选中的资源 uuid
for (let i = 0; i < uuids.length; i++) {
const assetInfo = Editor.assetdb.assetInfoByUuid(uuids[i]),
{ type, path } = assetInfo;
if (type === 'spine') {
spinePath = path; // Spine 资源
} else if (type === 'texture') {
texturePath = path; // 纹理资源
} else if (path.endsWith('.atlas') || path.endsWith('.txt')) {
atlasPath = path; // 图集资源
const assetInfo = await EditorAdapter.getAssetInfoByUuid(uuids[0]),
{ type, file } = assetInfo;
if (type === 'sp.SkeletonData') {
skeletonPath = file; // 骨骼资源
} else if (type === 'cc.ImageAsset') {
texturePath = file; // 纹理资源
} else if (file.endsWith('.atlas') || file.endsWith('.txt')) {
atlasPath = file; // 图集资源
}
// 只识别一套资源
if (spinePath && texturePath && atlasPath) {
if (skeletonPath && texturePath && atlasPath) {
break;
}
}
// 是否有选中 Spine 资源
if (!spinePath) {
// 未选中骨骼资源
// if (!skeletonPath) {
// return;
// }
// 无效
if (!skeletonPath && !texturePath && !atlasPath) {
Opener.updateView(null);
return;
}
// 是否有选中图集资源
if (!texturePath) {
// 读取 Spine 资源的 meta 信息中获取关联的纹理资源
const spineMeta = Editor.assetdb.loadMetaByPath(spinePath);
if (spineMeta.textures[0]) {
texturePath = Editor.assetdb.uuidToFspath(spineMeta.textures[0]);
}
}
// 处理路径
let paths = { spinePath, texturePath, atlasPath };
const paths = { skeletonPath, texturePath, atlasPath };
const assets = Opener.collectAssets(paths);
Opener.updateView(assets);
},
@@ -129,7 +129,7 @@ const Opener = {
*/
identifyByPaths(paths) {
// 资源路径
let spinePath, texturePath, atlasPath;
let skeletonPath, texturePath, atlasPath;
// 遍历选中的文件路径
for (let i = 0; i < paths.length; i++) {
const path = paths[i],
@@ -137,7 +137,7 @@ const Opener = {
switch (extname) {
case '.json':
case '.skel': {
spinePath = path;
skeletonPath = path;
break;
}
case '.png': {
@@ -151,65 +151,85 @@ const Opener = {
}
}
// 只识别一套资源
if (spinePath && texturePath && atlasPath) {
if (skeletonPath && texturePath && atlasPath) {
break;
}
}
// 是否有选中 spine 资源
if (!spinePath) {
print('warn', translate('noSkeleton'));
// 未选中骨骼资源
// if (!skeletonPath) {
// print('warn', translate('noSkeleton'));
// return;
// }
// 无效
if (!skeletonPath && !texturePath && !atlasPath) {
// print('warn', translate('noSkeleton'));
return;
}
// 处理路径
paths = { spinePath, texturePath, atlasPath };
paths = { skeletonPath, texturePath, atlasPath };
const assets = Opener.collectAssets(paths);
Opener.updateView(assets);
},
/**
* 收集资源
* @param {{ spinePath: string, texturePath: string, atlasPath: string }} paths 资源路径
* @param {{ skeletonPath: string, texturePath: string, atlasPath: string }} paths 资源路径
*/
collectAssets(paths) {
let { spinePath, texturePath, atlasPath } = paths;
let { skeletonPath, texturePath, atlasPath } = paths;
const testPath = skeletonPath || texturePath || atlasPath;
// 骨骼资源
if (!skeletonPath) {
// 暴力查找
skeletonPath = Opener.getRelatedFile(testPath, 'json');
// 还没有的话再试试 skel 格式
if (!skeletonPath) {
skeletonPath = Opener.getRelatedFile(testPath, 'skel');
}
// 找不到骨骼啊
if (!skeletonPath) {
// print('warn', translate('noSkeleton'));
return null;
}
}
// 纹理资源
if (!texturePath) {
// 暴力查找
texturePath = Opener.getRelatedFile(spinePath, 'png');
}
// 找不到纹理啊
if (!texturePath) {
print('warn', translate('noTexture'));
return null;
texturePath = Opener.getRelatedFile(testPath, 'png');
// 找不到纹理啊
if (!texturePath) {
print('warn', translate('noTexture'));
return null;
}
}
// 图集资源
if (!atlasPath) {
// 暴力查找
atlasPath = Opener.getRelatedFile(spinePath, 'atlas');
atlasPath = Opener.getRelatedFile(testPath, 'atlas');
// 还没有的话再试试 txt 格式
if (!atlasPath) {
atlasPath = Opener.getRelatedFile(spinePath, 'txt');
atlasPath = Opener.getRelatedFile(testPath, 'txt');
}
// 还没有的话再试试 atlas.txt 格式
if (!atlasPath) {
atlasPath = Opener.getRelatedFile(spinePath, 'atlas.txt');
atlasPath = Opener.getRelatedFile(testPath, 'atlas.txt');
}
// 找不到图集啊
if (!atlasPath) {
print('warn', translate('noAtlas'));
return null;
}
}
// 找不到图集啊
if (!atlasPath) {
print('warn', translate('noAtlas'));
return null;
}
// 文件类型json 或 skel
const spineType = Path.extname(spinePath);
const skeletonType = Path.extname(skeletonPath);
// 打包资源信息
const assets = {
// 目录路径
dir: undefined,
// 骨骼数据JSON
json: (spineType === '.json') ? spinePath : undefined,
json: (skeletonType === '.json') ? skeletonPath : undefined,
// 骨骼数据(二进制)
skel: (spineType === '.skel') ? spinePath : undefined,
skel: (skeletonType === '.skel') ? skeletonPath : undefined,
// 纹理
png: texturePath,
// 图集
@@ -247,7 +267,7 @@ const Opener = {
* @param {{ dir: string, json: string, atlas: string, png: string } | null} assets
*/
updateView(assets) {
const webContents = PanelManager.getViewPanel();
const webContents = PanelManager.getViewPanelWebContents();
if (webContents) {
MainEvent.send(webContents, 'assets-selected', assets);
}

View File

@@ -2,7 +2,8 @@ const { BrowserWindow } = require('electron');
const { join } = require('path');
const PackageUtil = require('../eazax/package-util');
const { language, translate } = require('../eazax/editor-main-util');
const { calcWindowPosition } = require('../eazax/window-util');
const { calcWindowPositionByFocused } = require('../eazax/window-util');
const EditorAdapter = require('../common/editor-adapter');
/** 包名 */
const PACKAGE_NAME = PackageUtil.name;
@@ -15,22 +16,33 @@ const EXTENSION_NAME = translate('name');
*/
const PanelManager = {
/**
* 预览面板的 WebContents
* @type {Electron.WebContents}
*/
viewWebContents: null,
/**
* 打开预览面板
*/
openViewPanel() {
Editor.Panel.open(`${PACKAGE_NAME}.view`);
EditorAdapter.Panel.open(`${PACKAGE_NAME}.view`);
},
/**
* 获取预览面板
* @returns {Electron.WebContents | null}
* 关闭预览面板
*/
getViewPanel() {
const panel = Editor.Panel.findWindow(`${PACKAGE_NAME}.view`);
if (panel) {
const webContents = panel.nativeWin.webContents;
return webContents;
closeViewPanel() {
EditorAdapter.Panel.close(`${PACKAGE_NAME}.view`);
},
/**
* 获取预览面板的 WebContents
* @returns {Electron.WebContents}
*/
getViewPanelWebContents() {
if (PanelManager.viewWebContents && !PanelManager.viewWebContents.isDestroyed()) {
return PanelManager.viewWebContents;
}
return null;
},
@@ -50,9 +62,9 @@ const PanelManager = {
PanelManager.settings.show();
return;
}
// 窗口高度和位置
const winSize = [500, 290],
winPos = calcWindowPosition(winSize, 'center');
// 窗口尺寸和位置macOS 标题栏高 28px
const winSize = [500, 350],
winPos = calcWindowPositionByFocused(winSize, 'center');
// 创建窗口
const win = PanelManager.settings = new BrowserWindow({
width: winSize[0],
@@ -61,7 +73,6 @@ const PanelManager = {
minHeight: winSize[1],
x: winPos[0],
y: winPos[1] - 100,
useContentSize: true,
frame: true,
title: `${EXTENSION_NAME} | Cocos Creator`,
autoHideMenuBar: true,
@@ -78,7 +89,7 @@ const PanelManager = {
contextIsolation: false,
},
});
// 就绪后展示避免闪烁)
// 就绪后展示避免闪烁)
win.on('ready-to-show', () => win.show());
// 关闭后
win.on('closed', () => (PanelManager.settings = null));
@@ -86,7 +97,7 @@ const PanelManager = {
win.webContents.on('before-input-event', (event, input) => {
if (input.key === 'Escape') PanelManager.closeSettingsPanel();
});
// 调试用的 devtoolsdetach 模式需要取消失焦自动关闭)
// 调试用的 devtools
// win.webContents.openDevTools({ mode: 'detach' });
// 加载页面
const path = join(__dirname, '../renderer/settings/index.html');

View File

@@ -1,32 +0,0 @@
module.exports = {
/**
* 查询节点上的骨骼资源
* @param {*} event
* @param {string} uuid
* @returns
*/
'query-skeleton'(event, uuid) {
// 获取节点
const node = cc.engine.getInstanceById(uuid);
if (!node) {
event.reply(null, null);
return;
}
// 获取节点上的骨骼组件
const spine = node.getComponent('sp.Skeleton');
if (!spine) {
event.reply(null, null);
return;
}
// 获取骨骼数据
const skeletonData = spine.skeletonData;
if (!skeletonData) {
event.reply(null, null);
return;
}
// 返回资源的 uuid
event.reply(null, skeletonData._uuid);
},
};

View File

@@ -40,11 +40,21 @@
<input v-model="customKey" :placeholder="t('customKeyPlaceholder')" />
</div>
</div>
<!-- 窗口置顶 -->
<div class="property">
<div class="label">
<span class="text">{{ t('alwaysOnTop') }}</span>
<span class="tooltip">{{ t('alwaysOnTopTooltip') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="alwaysOnTop" />
</div>
</div>
<!-- 自动检查更新 -->
<div class="property">
<div class="label">
<span class="text">{{ t('autoCheck') }}</span>
<span class="tooltip">{{ t('autoCheckTooltip') }}</span>
<span class="text">{{ t('autoCheckUpdate') }}</span>
<span class="tooltip">{{ t('autoCheckUpdateTooltip') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="autoCheckUpdate" />

View File

@@ -39,6 +39,8 @@ const App = {
const selectKey = ref('');
// 自定义
const customKey = ref('');
// 窗口置顶
const alwaysOnTop = ref(true);
// 自动检查更新
const autoCheckUpdate = ref(false);
@@ -66,27 +68,31 @@ const App = {
*/
function getConfig() {
const config = ConfigManager.get();
if (!config) return;
if (!config) {
return;
}
// 窗口置顶
alwaysOnTop.value = config.alwaysOnTop;
// 自动检查更新
autoCheckUpdate.value = config.autoCheckUpdate;
// 快捷键
const hotkey = config.hotkey;
if (!hotkey || hotkey === '') {
const shortcutKey = config.shortcutKey;
if (!shortcutKey || shortcutKey === '') {
selectKey.value = '';
customKey.value = '';
return;
}
// 预设快捷键
for (let i = 0, l = presets.length; i < l; i++) {
if (presets[i].key === hotkey) {
selectKey.value = hotkey;
for (let i = 0, l = presets.value.length; i < l; i++) {
if (presets.value[i].key === shortcutKey) {
selectKey.value = shortcutKey;
customKey.value = '';
return;
}
}
// 自定义快捷键
selectKey.value = 'custom';
customKey.value = hotkey;
customKey.value = shortcutKey;
}
/**
@@ -94,8 +100,9 @@ const App = {
*/
function setConfig() {
const config = {
alwaysOnTop: alwaysOnTop.value,
autoCheckUpdate: autoCheckUpdate.value,
hotkey: null,
shortcutKey: null,
};
if (selectKey.value === 'custom') {
// 自定义输入是否有效
@@ -109,12 +116,14 @@ const App = {
EditorRendererKit.print('warn', translate('quoteError'));
return;
}
config.hotkey = customKey.value;
config.shortcutKey = customKey.value;
} else {
config.hotkey = selectKey.value;
config.shortcutKey = selectKey.value;
}
// 保存到本地
ConfigManager.set(config);
// 重新加载扩展
RendererEvent.send('reload');
}
/**
@@ -164,6 +173,7 @@ const App = {
presets,
selectKey,
customKey,
alwaysOnTop,
autoCheckUpdate,
repositoryUrl,
packageName,

View File

@@ -1,57 +1,70 @@
'use strict';
const { readFileSync } = require('fs');
const { join } = require('path');
// ⚠️ 2.4.5 以上版本可以直接导入
// 但是 2.4.5 以下版本无法正常访问 __dirname
// const Vue = require('../../../lib/vue.global.prod');
// const App = require('./index');
// 插件内置的 Vue
const Vue = require('../../../lib/vue.global.prod');
// 面板代码
const App = require('./index');
// ⚠️ 2.4.5 以下版本只能通过 Editor.url 来获取插件路径
const PACKAGE_NAME = 'ccc-skeleton-viewer';
const PACKAGE_PATH = Editor.url(`packages://${PACKAGE_NAME}/`);
const DIR_PATH = join(PACKAGE_PATH, 'src/renderer/view/');
// 面板 Vue 实例
let app = null;
const Vue = require(join(PACKAGE_PATH, 'lib/vue.global.prod'));
const App = require(join(DIR_PATH, 'index'));
// html 文本
exports.template = readFileSync(join(__dirname, 'index.html'), 'utf8');
// 创建面板
Editor.Panel.extend({
// 样式文本
exports.style = '';
/** HTML */
// template: readFileSync(join(__dirname, 'index.html'), 'utf8'),
template: readFileSync(join(DIR_PATH, 'index.html'), 'utf8'),
// 渲染后 html 选择器
exports.$ = {
app: '#app',
};
/**
* 面板渲染成功
*/
ready() {
const root = this.shadowRoot;
// 加载样式表
// loadCss(root, join(__dirname, '../../eazax/css/cocos-tag.css'));
// loadCss(root, join(__dirname, '../../eazax/css/cocos-class.css'));
// loadCss(root, join(__dirname, 'index.css'));
loadCSS(root, join(PACKAGE_PATH, 'src/eazax/css/cocos-tag.css'));
loadCSS(root, join(PACKAGE_PATH, 'src/eazax/css/cocos-class.css'));
loadCSS(root, join(DIR_PATH, 'index.css'));
// 先替换掉编辑器内置的 Vue
const oldVue = window.Vue;
window.Vue = Vue;
// 创建实例
const app = Vue.createApp(App);
// 挂载
app.mount(root);
// 把编辑器的 Vue 换回去
window.Vue = oldVue;
},
// 面板上的方法
exports.methods = {};
});
// 面板上触发的事件
exports.listeners = {};
// 当面板渲染成功后触发
exports.ready = async function () {
const root = this.$.app.parentNode;
// 加载样式表
loadCss(root, join(__dirname, '../../eazax/css/cocos-tag.css'));
loadCss(root, join(__dirname, '../../eazax/css/cocos-class.css'));
loadCss(root, join(__dirname, 'index.css'));
// 先替换掉编辑器内置的 Vue理论上 3.x 编辑器不内置 Vue
const builtinVue = window.Vue;
window.Vue = Vue;
// 创建 Vue 实例
app = Vue.createApp(App);
// 挂载 Vue 实例
app.mount(root);
// 把编辑器的 Vue 换回去
window.Vue = builtinVue;
};
// 尝试关闭面板的时候触发
exports.beforeClose = async function () {
// 卸载 Vue 实例
app.unmount();
};
// 当面板实际关闭后触发
exports.close = async function () { };
/**
* 加载样式表
* @param {HTMLElement} root 根元素
* @param {string} path CSS 文件路径
*/
function loadCSS(root, path) {
function loadCss(root, path) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';

View File

@@ -1,10 +1,18 @@
#app {
--background-color: #474747;
--content-color: #2f2f2f;
--font-color: #bdbdbd;
--hover-color: #09f;
--active-color: #fd942b;
}
#app {
width: 100%;
height: 100%;
background-color: #454545;
background-color: var(--background-color);
box-sizing: border-box;
padding: 10px;
color: #bdbdbd;
color: var(--font-color);
animation: fade-in 0.3s;
animation-fill-mode: forwards;
}
@@ -31,7 +39,7 @@
.view {
width: 100%;
min-height: 0;
background-color: #4c4c4c;
background-color: var(--content-color);
position: relative;
flex: 1;
}
@@ -40,7 +48,7 @@
.canvas {
width: 100%;
height: 100%;
background-color: #4c4c4c;
background-color: var(--content-color);
}
/* 版本号 */
@@ -52,7 +60,8 @@
right: 6px;
line-height: 16px;
font-size: 11px;
color: #bdbdbd;
color: var(--font-color);
user-select: none;
}
/* 按钮容器 */
@@ -76,7 +85,7 @@
.button {
width: 16px;
height: 16px;
color: #bdbdbd;
color: var(--font-color);
display: flex;
align-items: center;
justify-content: center;
@@ -86,12 +95,12 @@
/* 信息,按钮:虚指 */
.info:hover,
.button:hover {
color: #09f;
color: var(--hover-color);
}
/* 按钮:激活 */
.button:active {
color: #fd942b;
color: var(--active-color);
}
/* 信息,按钮 图标 */
@@ -117,6 +126,7 @@
margin-right: 2px;
line-height: 12px;
font-size: 11px;
user-select: none;
}
/* 位置 按钮 */

View File

@@ -1,159 +1,148 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div ref="app" id="app" style="opacity: 0;">
<!-- 内容布局 -->
<div ref="layout" class="layout">
<!-- 预览区 -->
<div class="view">
<!-- 画布 -->
<canvas ref="canvas" class="canvas"></canvas>
<!-- 按钮容器 -->
<div class="buttons">
<!-- 资源信息按钮 -->
<div class="button" :title="assetsInfo" @click="onInfoBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M4.395 25.605c-2.825-2.731-4.579-6.556-4.579-10.789 0-8.284 6.716-15 15-15 4.234 0 8.058 1.754 10.785 4.575l0.004 0.004c2.605 2.695 4.211 6.37 4.211 10.421 0 8.284-6.716 15-15 15-4.050 0-7.726-1.605-10.425-4.215l0.004 0.004zM13.5 16.5v6h3v-9h-3v3zM13.5 7.5v3h3v-3h-3z">
</path>
</svg>
</div>
<!-- 选择资源按钮 -->
<div class="button" :title="t('selectAssets')" @click="onSelectBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M15 30c-8.284 0-15-6.716-15-15s6.716-15 15-15v0c8.284 0 15 6.716 15 15s-6.716 15-15 15v0zM11.82 11.82l-5.31 11.67 11.67-5.31 5.31-11.67-11.67 5.31zM15 16.5c-0.828 0-1.5-0.672-1.5-1.5s0.672-1.5 1.5-1.5v0c0.828 0 1.5 0.672 1.5 1.5s-0.672 1.5-1.5 1.5v0z">
</path>
</svg>
</div>
<!-- 重置按钮 -->
<div class="button" :title="t('reset')" @click="onResetBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M4.395 25.605c-2.825-2.731-4.579-6.556-4.579-10.789 0-8.284 6.716-15 15-15 4.234 0 8.058 1.754 10.785 4.575l0.004 0.004c2.605 2.695 4.211 6.37 4.211 10.421 0 8.284-6.716 15-15 15-4.050 0-7.726-1.605-10.425-4.215l0.004 0.004zM17.1 15l4.245-4.245-2.115-2.115-4.23 4.245-4.245-4.245-2.115 2.115 4.245 4.245-4.245 4.245 2.115 2.115 4.245-4.245 4.245 4.245 2.115-2.115-4.245-4.245z">
</path>
</svg>
</div>
<div ref="app" id="app" style="opacity: 0;">
<!-- 内容布局 -->
<div ref="layout" class="layout">
<!-- 预览区 -->
<div class="view">
<!-- 画布 -->
<canvas ref="canvas" class="canvas"></canvas>
<!-- 按钮容器 -->
<div class="buttons">
<!-- 资源信息按钮 -->
<div class="button" :title="assetsInfo" @click="onInfoBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M4.395 25.605c-2.825-2.731-4.579-6.556-4.579-10.789 0-8.284 6.716-15 15-15 4.234 0 8.058 1.754 10.785 4.575l0.004 0.004c2.605 2.695 4.211 6.37 4.211 10.421 0 8.284-6.716 15-15 15-4.050 0-7.726-1.605-10.425-4.215l0.004 0.004zM13.5 16.5v6h3v-9h-3v3zM13.5 7.5v3h3v-3h-3z">
</path>
</svg>
</div>
<!-- 运行时版本 -->
<div class="version">
<span :title="t('spineRuntime') + ': ' + version">{{ t('spineRuntime') }}: {{ version }}</span>
<!-- 选择资源按钮 -->
<div class="button" :title="t('selectAssets')" @click="onSelectBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M15 30c-8.284 0-15-6.716-15-15s6.716-15 15-15v0c8.284 0 15 6.716 15 15s-6.716 15-15 15v0zM11.82 11.82l-5.31 11.67 11.67-5.31 5.31-11.67-11.67 5.31zM15 16.5c-0.828 0-1.5-0.672-1.5-1.5s0.672-1.5 1.5-1.5v0c0.828 0 1.5 0.672 1.5 1.5s-0.672 1.5-1.5 1.5v0z">
</path>
</svg>
</div>
<!-- 颜色 -->
<div class="color">
<!-- 取色器 -->
<input type="color" v-model="canvasColor" :title="t('canvasColor')" />
</div>
<!-- 位置 -->
<div class="position">
<!-- 文本 -->
<div class="label" :title="offset">{{ offset }}</div>
<!-- 复位按钮 -->
<div class="button" :title="t('reposition')" @click="onRepositionBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M15 30s-10.5-13.695-10.5-19.5c0-5.799 4.701-10.5 10.5-10.5s10.5 4.701 10.5 10.5v0c0 5.805-10.5 19.5-10.5 19.5zM15 13.5c1.657 0 3-1.343 3-3s-1.343-3-3-3v0c-1.657 0-3 1.343-3 3s1.343 3 3 3v0z">
</path>
</svg>
</div>
<!-- 重置按钮 -->
<div class="button" :title="t('reset')" @click="onResetBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M4.395 25.605c-2.825-2.731-4.579-6.556-4.579-10.789 0-8.284 6.716-15 15-15 4.234 0 8.058 1.754 10.785 4.575l0.004 0.004c2.605 2.695 4.211 6.37 4.211 10.421 0 8.284-6.716 15-15 15-4.050 0-7.726-1.605-10.425-4.215l0.004 0.004zM17.1 15l4.245-4.245-2.115-2.115-4.23 4.245-4.245-4.245-2.115 2.115 4.245 4.245-4.245 4.245 2.115 2.115 4.245-4.245 4.245 4.245 2.115-2.115-4.245-4.245z">
</path>
</svg>
</div>
</div>
<!-- 选项区 -->
<div ref="properties" class="properties">
<!-- 预览缩放 -->
<div class="property">
<div class="label">
<span class="text">{{ t('viewScale') }}</span>
</div>
<div class="content">
<input type="number" step="0.1" v-model="viewScale" />
</div>
</div>
<!-- 皮肤 -->
<div class="property">
<div class="label">
<span class="text">{{ t('skin') }}</span>
</div>
<div class="content">
<select v-model="skin">
<option v-for="name in skins" :key="name" :value="name">{{ name }}</option>
</select>
</div>
</div>
<!-- 动画 -->
<div class="property">
<div class="label">
<span class="text">{{ t('animation') }}</span>
</div>
<div class="content">
<select v-model="animation">
<option v-for="name in animations" :key="name" :value="name">{{ name }}</option>
</select>
</div>
</div>
<!-- 循环 -->
<div class="property">
<div class="label">
<span class="text">{{ t('loop') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="loop" />
</div>
</div>
<!-- 预乘 -->
<div class="property">
<div class="label">
<span class="text">{{ t('premultipliedAlpha') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="premultipliedAlpha" />
</div>
</div>
<!-- 时间缩放 -->
<div class="property">
<div class="label">
<span class="text">{{ t('timeScale') }}</span>
</div>
<div class="content">
<input type="number" step="0.1" v-model="timeScale" />
</div>
</div>
<!-- 绘制骨骼 -->
<div class="property">
<div class="label">
<span class="text">{{ t('drawBones') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="drawBones" />
</div>
</div>
<!-- 绘制包围盒 -->
<div class="property">
<div class="label">
<span class="text">{{ t('drawBoundingBoxes') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="drawBoundingBoxes" />
</div>
</div>
<!-- 绘制网格三角形 -->
<div class="property">
<div class="label">
<span class="text">{{ t('drawMeshTriangles') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="drawMeshTriangles" />
</div>
<!-- 运行时版本 -->
<div class="version">
<span :title="t('spineRuntime') + ': ' + version">{{ t('spineRuntime') }}: {{ version }}</span>
</div>
<!-- 颜色 -->
<div class="color">
<!-- 取色器 -->
<input type="color" v-model="canvasColor" :title="t('canvasColor')" />
</div>
<!-- 位置 -->
<div class="position">
<!-- 文本 -->
<div class="label" :title="offset">{{ offset }}</div>
<!-- 复位按钮 -->
<div class="button" :title="t('reposition')" @click="onRepositionBtnClick">
<svg class="icon" viewBox="0 0 30 30">
<path fill="currentColor"
d="M15 30s-10.5-13.695-10.5-19.5c0-5.799 4.701-10.5 10.5-10.5s10.5 4.701 10.5 10.5v0c0 5.805-10.5 19.5-10.5 19.5zM15 13.5c1.657 0 3-1.343 3-3s-1.343-3-3-3v0c-1.657 0-3 1.343-3 3s1.343 3 3 3v0z">
</path>
</svg>
</div>
</div>
</div>
<!-- 选项区 -->
<div ref="properties" class="properties">
<!-- 预览缩放 -->
<div class="property">
<div class="label">
<span class="text">{{ t('viewScale') }}</span>
</div>
<div class="content">
<input type="number" step="0.1" v-model="viewScale" />
</div>
</div>
<!-- 皮肤 -->
<div class="property">
<div class="label">
<span class="text">{{ t('skin') }}</span>
</div>
<div class="content">
<select v-model="skin">
<option v-for="name in skins" :key="name" :value="name">{{ name }}</option>
</select>
</div>
</div>
<!-- 动画 -->
<div class="property">
<div class="label">
<span class="text">{{ t('animation') }}</span>
</div>
<div class="content">
<select v-model="animation">
<option v-for="name in animations" :key="name" :value="name">{{ name }}</option>
</select>
</div>
</div>
<!-- 循环 -->
<div class="property">
<div class="label">
<span class="text">{{ t('loop') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="loop" />
</div>
</div>
<!-- 预乘 -->
<div class="property">
<div class="label">
<span class="text">{{ t('premultipliedAlpha') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="premultipliedAlpha" />
</div>
</div>
<!-- 时间缩放 -->
<div class="property">
<div class="label">
<span class="text">{{ t('timeScale') }}</span>
</div>
<div class="content">
<input type="number" step="0.1" v-model="timeScale" />
</div>
</div>
<!-- 绘制骨骼 -->
<div class="property">
<div class="label">
<span class="text">{{ t('drawBones') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="drawBones" />
</div>
</div>
<!-- 绘制包围盒 -->
<div class="property">
<div class="label">
<span class="text">{{ t('drawBoundingBoxes') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="drawBoundingBoxes" />
</div>
</div>
<!-- 绘制网格三角形 -->
<div class="property">
<div class="label">
<span class="text">{{ t('drawMeshTriangles') }}</span>
</div>
<div class="content">
<input type="checkbox" v-model="drawMeshTriangles" />
</div>
</div>
</div>
<br>
</div>
</body>
</html>
<br>
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff