我智商爆棚
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

318 lines
7.8 KiB

const { ipcRenderer } = require('electron');
/** 包名 */
const PACKAGE_NAME = 'ccc-quick-finder';
// 创建 Vue 实例
new Vue({
/**
* 挂载目标
* @type {string | Element}
*/
el: "#app",
/**
* 数据对象
*/
data: {
/** 输入框占位符文本 */
placeholder: '',
/** 确认按钮文本 */
button: '',
/** 输入的关键字 */
keyword: '',
/** 关键词匹配返回的结果 */
results: [],
/** 当前选中的项目 */
selected: null,
/** 当前选中的项目下标 */
selectedIndex: -1,
/** 分段加载定时器 */
loadHandler: null,
},
/**
* 实例函数
* @type {{ [key: string]: Function }}
*/
methods: {
/**
* 输入框更新回调
* @param {*} event
*/
onInputChange(event) {
// 取消分帧加载
if (this.loadHandler) {
clearTimeout(this.loadHandler);
this.loadHandler = null;
}
// 取消当前选中
this.selected = null;
this.selectedIndex = -1;
// 关键字为空或无效时不进行搜索
const keyword = this.keyword;
if (keyword === '' || keyword.includes('...')) {
this.results.length = 0;
return;
}
// 发消息给主进程进行关键词匹配
ipcRenderer.send(`${PACKAGE_NAME}:match-keyword`, keyword);
},
/**
* 确认按钮点击回调
* @param {*} event
*/
onEnterBtnClick(event) {
const selected = this.selected;
if (!selected) {
if (this.keyword !== '') {
// 输入框文本错误动画
const input = this.$refs.input;
input.classList.add('search-input-error');
setTimeout(() => input.classList.remove('search-input-error'), 500);
}
} else {
this.keyword = selected.name;
// 发消息给主进程
ipcRenderer.send(`${PACKAGE_NAME}:open`, selected.path);
}
// 聚焦到输入框(此时焦点在按钮或列表上)
this.focusOnInputField();
},
/**
* 上箭头按键回调
* @param {*} event
*/
onUpBtnClick(event) {
// 阻止默认事件(光标移动)
event.preventDefault();
// 循环选择
if (this.selectedIndex > 0) {
this.selectedIndex--;
} else {
this.selectedIndex = this.results.length - 1;
}
// 更新选择
this.updateSelected();
},
/**
* 下箭头按键回调
* @param {*} event
*/
onDownBtnClick(event) {
// 阻止默认事件(光标移动)
event.preventDefault();
// 循环选择
if (this.selectedIndex >= this.results.length - 1) {
this.selectedIndex = 0;
} else {
this.selectedIndex++;
}
// 更新选择
this.updateSelected();
},
/**
* 更新当前的选择
*/
updateSelected() {
this.selected = this.results[this.selectedIndex];
this.keyword = this.selected.name;
// 只有当目标元素不在可视区域内才滚动
const id = `item-${this.selectedIndex}`;
document.getElementById(id).scrollIntoViewIfNeeded(false);
},
/**
* 左箭头按键回调
* @param {*} event
*/
onLeftBtnClick(event) {
// 是否已选中项目
if (!this.selected) return;
// 阻止默认事件(光标移动)
event.preventDefault();
// 在资源管理器中显示并选中文件
this.showFileInAssets();
},
/**
* 右箭头按键回调
* @param {*} event
*/
onRightBtnClick(event) {
// 是否已选中项目
if (!this.selected) return;
// 阻止默认事件(光标移动)
event.preventDefault();
// 在资源管理器中显示并选中文件
this.showFileInAssets();
},
/**
* 在资源管理器中显示并选中文件
*/
showFileInAssets() {
// 当前选中文件路径
const path = this.selected.path;
// 发消息给主进程
ipcRenderer.send(`${PACKAGE_NAME}:focus`, path);
},
/**
* 结果点击回调
* @param {{ name: string, path: string, extname: string }} value 数据
* @param {number} index 下标
*/
onResultClick(value, index) {
this.selectedIndex = parseInt(index);
this.selected = value;
this.keyword = value.name;
// 添加组件
this.onEnterBtnClick(null);
// 聚焦到输入框(此时焦点在列表上)
// 换成在 onEnterBtnClick 里统一处理了
// this.focusOnInputField();
},
/**
* 聚焦到输入框
*/
focusOnInputField() {
this.$refs.input.focus();
},
/**
* (主进程)获取语言回调
* @param {*} event
* @param {string} lang 语言
*/
onGetLangReply(event, lang) {
const texts = lang.includes('zh') ? zh : en;
this.placeholder = texts.placeholder;
this.button = texts.button;
},
/**
* (主进程)匹配关键词回调
* @param {*} event
* @param {{ name: string, path: string, extname: string }[]} results 结果
*/
onMatchKeywordReply(event, results) {
// 确保清除已有数据
this.results.length = 0;
// 当只有一个结果时直接选中
if (results.length === 1) {
this.results = results;
this.selectedIndex = 0;
this.selected = results[0];
return;
}
// 结果数量多时分段加载
if (results.length >= 300) {
// 每次加载的数量
const threshold = 150;
// 分段加载函数
const load = () => {
const length = results.length,
count = length >= threshold ? threshold : length,
part = results.splice(0, count);
// 加载一部分
this.results.push(...part);
// 是否还有数据
if (results.length > 0) {
// 下一波继续
this.loadHandler = setTimeout(load);
} else {
// Done
this.loadHandler = null;
}
}
// 开始加载
load();
return;
}
// 数量不多,更新结果列表
this.results = results;
},
/**
* 获取图标
* @param {string} extname 扩展名
* @returns {string}
*/
getIcon(extname) {
const iconName = iconMap[extname] || 'asset';
return `../../images/assets/${iconName}.png`;
},
},
/**
* 生命周期:实例被挂载
*/
mounted() {
// 监听事件
ipcRenderer.on(`${PACKAGE_NAME}:get-lang-reply`, this.onGetLangReply.bind(this));
ipcRenderer.on(`${PACKAGE_NAME}:match-keyword-reply`, this.onMatchKeywordReply.bind(this));
// 发送事件:获取语言
ipcRenderer.send(`${PACKAGE_NAME}:get-lang`);
// (下一帧)聚焦到输入框
this.$nextTick(() => this.focusOnInputField());
},
/**
* 生命周期:实例销毁前
*/
beforeDestroy() {
// 取消事件监听
ipcRenderer.removeAllListeners(`${PACKAGE_NAME}:get-lang-reply`);
ipcRenderer.removeAllListeners(`${PACKAGE_NAME}:match-keyword-reply`);
},
});
/** 多语言:中文 */
const zh = {
placeholder: '请输入文件名称...',
button: 'GO'
}
/** 多语言:英语 */
const en = {
placeholder: 'Enter file name...',
button: 'GO'
}
/** 文件扩展名对应图标表 */
const iconMap = {
'.anim': 'animation-clip',
'.prefab': 'prefab',
'.fire': 'scene',
'.scene': 'scene',
'.effect': 'shader',
'.mesh': 'mesh',
'.FBX': 'mesh',
'.mtl': 'material',
'.pmtl': 'physics-material',
'.pac': 'auto-atlas',
'.ts': 'typescript',
'.js': 'javascript',
'.coffee': 'coffeescript',
'.json': 'json',
'.md': 'markdown',
'.html': 'html',
'.css': 'css',
'.txt': 'text',
'.ttf': 'ttf-font',
'.fnt': 'bitmap-font',
'.mp3': 'audio-clip',
'.png': 'atlas',
'.jpg': 'atlas',
'.plist': 'sprite-atlas',
}