猎豹Cheetah插件开发 - uTools 篇
本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
序本系列前面的两篇文章《概念篇》、《核心篇》中已经详细说明了猎豹跨平台插件开发所需要的技术,以及针对对不同平台做的核心模块抽取流程。
本文主要讲解如何接入我们抽取的核心模块如何应用在 uTools 平台的插件开发中,并且介绍一下 uTools 插件开发用到的 API,以及 uTools 插件的发布流程。
项目配置uTools 为开发者提供了一个工具插件,可以在插件市场直接搜索“uTools 开发者工具”安装,项目的新建、运行,发布都在这个插件内操作。
安装开发者工具创建项目安装完开发者工具后,直接在uTools输入框搜索打开开发者工具,点击左下角的新建项目,打开项目创建面板,根据提示完成项目创建。
新建项目根据官网文档指引,需要为项目新建一个plugin.json文件,用于配置项目信息,功能入口等等。
开发者可以围绕plugin.json文件创建项目,使用自己熟悉的构建工具完成项目资源构建,plugin.json中的preload字段指向构建结果中的入口文件即可。
plugin 配置{
"version":"1.0.0",//插件版本
"preload":"index.js",//插件功能入口
"logo":"assets/logo.png",//插件Logo
"platform":[//插件支持的平台
"win32",//Windows
"darwin"//MacOS
],
"features":[//插件应用功能
{
"code":"open",//功能唯一标识,搜索项目并使用与命令相关的软件打开
"explain":"搜索并打开项目",
"cmds":[//搜索这些字符都可以进入open功能,其值将作为action参数传入后续执行的函数
"open",
"git_gui_open",
"terminal_open",
"folder_open",
"set_application",
"编辑器",
"Git应用",
"终端",
"文件夹",
"设置项目默认应用"
]
},
{
"code":"setting",//功能唯一标识,打开插件设置面板
"explain":"打开设置面板",
"cmds":[//这俩命令都是打开设置面板
"setting",
"设置"
]
}
]
}
设置面板从上面配置中可以看出,除了open功能外,还有一个打开设置面板功能,这么做的原因是 uTools 的插件管理没有提供现成的偏好设置功能,一些个性化的配置想要用户自己输入时比较麻烦,所以这边添加了一个命令,用于打开咱们自己开发的配置面板,配置的数据可以存储在 uTools 提供的存储环境中。
命令面板可以使用任意框架开发,最后构建成html、css、js即可通过 uTools 提供的 APIutools.createBrowserWindow打开窗口。
插一句:uTools 基于 Electron 开发,熟悉 Electron 的小伙伴应该很容易上手。
下面看一看设置面板打开以后得样子:
设置面板文件结构主要的功能文件使用rollup.js构建,设置面板使用vite + vue3,输出到package目录下,将plugin.json也放置其内,处理好引用关系,插件的项目配置就基本完成了,结构如下:
cheetah-for-utools
├─lib
│├─common
││├─constant.ts
││├─core.ts
││├─index.ts
││└─utils.ts
│├─index.ts
│└─mount.ts
├─package
│├─assets
││├─empty.png
││├─logo.png
││├─refresh.png
││└─type
││├─android.png
││├─applescript.png
││├─dart.png
││├─hexo.png
││├─javascript.png
││├─nuxt.png
││├─react.png
││├─react_ts.png
││├─rust.png
││├─typescript.png
││├─unknown.png
││├─vscode.png
││└─vue.png
│├─plugin.json
├─package.json
├─rollup.config.js
├─tsconfig.json
├─types.d.ts
├─vite.config.ts
└─window
├─index.html
└─src
├─App.vue
├─assets
│└─logo.png
├─components
│├─app-choose-item.vue
│├─cell-button.vue
│└─workspace-manager.vue
├─env.d.ts
├─main.ts
├─pages
│└─index.vue
└─router
└─index.ts
rollup的配置与核心篇大同小异,最大的区别是,构建结果不能压缩,因为发布时要保证preload.js明文可读,否则审核会不通过,审核人员会查看插件是否会造成对用户系统的危害。
引入核心模块核心篇中已经阐述了如何开发、发布插件,并且已经将猎豹的核心模块cheetah-core发布到npm,现在直接在 uTools 插件项目中安装使用即可:
npminstallcheetah-core
#or
yarnaddcheetah-core
功能实现设置面板上面提到设置面板主要是为了让用户进行一些插件功能的设置,主要包括下面这些选项。
工作区设置工具将在以下工作区目录搜索 Git 项目,可配置多个,每个工作区目录可以包含多个 Git 项目。
默认编辑器open 命令选择项目默认由此编辑器打开
Git GUI 应用git_gui_open 命令选择项目由此应用打开
终端terminal_open 命令选择项目由此终端打开
以上这些设置以后通过utools.dbStorage.setItem写入本地数据库,方便open命令执行时获取使用。
那么怎么打开设置面板呢?
在preload.ts中添加:
window.exports={
open:{...},//open命令后续讲解
setting:{
mode:'none',
args:{
enter:(action:any)={
constsettingWindow=utools.createBrowserWindow(
'./window/index.html',
{
show:false,
title:'设置',
resizable:false,
minimizable:false,
maximizable:false,
height:800,
width:500,
webPreferences:{
preload:'index.js',
},
},
()={
settingWindow.show();
}
utools.hideMainWindow();
},
},
},
}
上面的代码表示在 uTools 输入框中输入setting相关命令并运行后,使用utools.createBrowserWindow打开咱们构建的页面,并且载入preload文件为rollup构建的入口文件。
在入口文件中引入了mount.ts文件,其作用是向window对象挂载一系列工具函数:
//index.ts
importmount.ts
...
//mount.ts
import{
chooseFile,
chooseFolder,
getValue,
setValue,
notice,
getAllDefaultApp,
setDefaultApp,
platform,
onClearCache
}from'./common';
Object.assign(window,{
platform,
chooseFile,
chooseFolder,
notice,
getValue,
setValue,
getAllDefaultApp,
setDefaultApp,
onClearCache,
});
这样在设置面板中就可以直接使用window上挂载的函数完成配置了。
搜索项目项目搜索的逻辑在核心篇中有详细讲解,此处不多赘述,只讲解用法。
其实前面open功能中定义的命令,触发的搜索都一致,只是在搜索结果选择以后执行的操作不同,所以不同的命令进来只要执行相同的搜索函数即可。
uTools 为开发者提供了几个插件模板,猎豹主要使用了list这个模板完成功能开发。
importcpfrom'child_process';
import'./mount';//在window挂载上挂载工具函数
import{
filterWithCache,
filterWithSearchResult,
updateHits,
Project,
ResultItem,
COMMAND,
getOpenCommand,
setProjectApp,
}from'cheetah-core';//从核心模块中引入
import{
initCore,
output,
chooseFile,
commandMap,
getAllDefaultApp,
errorHandle,
}from'./common';//uTools功能适配的函数
constrefreshKeyword='[refresh]';//刷新关键字,搜索字符串中包含此关键字时会忽略缓存,重新搜索项目
/**
*@description:搜索项目
*@param{any}action
*@param{string}keyword搜索关键字
*@param{any}callbackSetList渲染候选列表的回调
*@return{*}
*/
asyncfunctionsearch(
action:any,
keyword:string,
callbackSetList:any
):Promisevoid{
try{
initCore();
constneedRefresh:boolean=keyword.includes(refreshKeyword);
constsearchKeyword=keyword.replace(refreshKeyword,'');
if(!searchKeyword)returncallbackSetList();
letprojects:Project[]=awaitfilterWithCache(searchKeyword);
letfromCache=true;
//如果缓存结果为空或者需要刷新缓存,则重新搜索
if(!projects.length||needRefresh){
projects=awaitfilterWithSearchResult(searchKeyword);
fromCache=false;
}
constresult:ResultItem[]=output(projects);
if(fromCache){
result.push({
title:'忽略缓存重新搜索',
description:'以上结果从缓存中获得,选择本条将重新搜索项目并更新缓存',
icon:'assets/refresh.png',
arg:searchKeyword,
}
if(!result.length){
result.push({
title:`没有找到名称包含${searchKeyword}的项目`,
description:'请尝试更换关键词,回车返回重新搜索',
icon:'assets/empty.png',
type:'empty',
}
callbackSetList(result);
}catch(error:any){
errorHandle(error);
}
}
/**
*@description:处理点击结果,如果是重新搜索会返回跳过
*@param{ResultItem}itemData被点击的条目
*@return{boolean}是否需要跳过
*/
functioncommonSelect(itemData:ResultItem):boolean{
const{arg,type}=itemData;
//搜索结果为空的条目被点击则置空输入框
if(type==='empty'){
utools.setSubInputValue('');
returntrue;
}
//重新搜索时重置搜索框内容为[refresh]+关键字
constskip=arg!==nullarg!==undefined;
if(skip){
utools.setSubInputValue(`${refreshKeyword}${arg}`);
}
returnskip;
}
window.exports={
open:{
mode:'list',
args:{
search,
//在搜索列表内选择项目并回车以后,将执行此函数,action为当前执行
select:async(action:any,itemData:ResultItem)={
try{
if(commonSelect(itemData))return;
initCore();
const{payload}:{payload:string}=action;
constcommandType=commandMap[payload];
if(commandType===COMMAND.SET_APPLICATION){
constappPath:string=chooseFile();
setProjectApp(itemData.path!,appPath);
utools.hideMainWindow();
utools.outPlugin();
return;
}
constdefaultAppPath=getAllDefaultApp()?.[commandType]??'';
constcommand=awaitgetOpenCommand(
itemData,
commandType,
defaultAppPath
cp.exec(command,{windowsHide:true},(error:any)={
if(error){
utools.showNotification(error?.message??'未知错误');
}
updateHits(itemData.path!);
utools.hideMainWindow();
utools.outPlugin();
}catch(error:any){
errorHandle(error);
}
},
},
},
setting:{...}
}
open下的mode字段配置为list表示使用list模板,args下为list模板在使用时触发的函数,定义如下:
searchuTools 插件子输入框内容变化时调用,第一个参数是描述当前打开open功能的命令,第二个参数是输入框的值,第三个参数为Callback函数,将列表按照文档要求传入即可渲染。
搜索列表select在search返回的列表中选择项目后回车时执行,第一个参数与search一致,第二个参数为被选择项目的详细信息。
明白了 list 模板的使用后,就能开始功能的开发了。
首先在 search 中使用核心模块提供的项目搜索函数,filterWithSearchResult,执行搜索后结果会被缓存,下一次使用时将直接调用filterWithCache从缓存中筛选结果,为了防止项目变动后搜索结果不准确,添加了一个手动刷新的候选项,最后将搜索结果通过Callback返回给 uTools 渲染列表。
忽略缓存直接搜索在搜索结果中选择目标项目回车后执行select函数,根据action.payload的值以及itemData执行不同的操作:
在itemData.type为empty时,搜索结果为空,选择空提示项目时仅将子输入框内容置空,可以重新输入关键字搜索。当itemData.arg不为空时,将子输入框内容改为[refresh]${itemData.arg},触发新一轮搜索,因为关键词中包含刷新关键字,将直接调用filterWithSearchResult忽略缓存直接搜索项目。排除以上两种情况后,将根据action.payload的值使用关联的应用打开项目。使用指定应用打开项目Mac OS 下使用指定应用打开文件或者文件夹非常方便,使用以下命令即可:
open-a应用名称项目绝对路径
#例
open-a"VisualStudioCode"/Users/***/Documents/works/test
Windows 下可用的软件比较少,编辑器可用的有VSCode,WebStorm,Sublime,其余未做更多测试,Git GUI仅支持Fork,终端仅CMD、PowerShell可用,软件的使用命令为:
"应用路径""项目路径"
#例
"D:\ProgramFilesJetBrains\WebStorm2021.1.1\webstorm.exe""D:\works\test"
Windows 下终端运行需要经过特殊处理:
/**
*@description:windows下终端命令处理
*@param{string}realAppPath应用路径
*@param{string}projectPath项目路径
*@return{*}
*/
functionwindowsTerminal(realAppPath:string,projectPath:string):string{
if(/powershell/i.test(realAppPath)){
return`startpowershell-NoExit-Command"cd${projectPath}"`;
}
if(/cmd/i.test(realAppPath)){
return`start/D${projectPath}`;
}
thrownewError('109');
}
编辑器打开项目的优先级规则为:缓存文件内配置的项目编辑器 缓存文件内配置的项目类型编辑器 设置面板内配置的默认编辑器。
在没有配置任何编辑器时,兜底的是文件浏览器,Mac OS 下为Finder,Windows 下为explorer.exe。
使用 Git GUI 与终端时也会从本地存储中读取设置面板中配置的相关应用。
清除缓存设置面板最下方有一个清除缓存按钮,可以将缓存文件删除,核心模块中也提供了相关函数,在 uTools 项目中直接调用即可,调用成功后使用 uTools 提供的通知方式提示清除成功。
exportasyncfunctiononClearCache():Promisevoid{
try{
initCore();
awaitclearCache();
notice('缓存清除成功');
}catch(error:any){
errorHandle(error);
}
}
为项目设置软件猎豹可以为每个项目配置编辑器,在缓存的项目描述中有个字段是idePath,将编辑器名称或者路径配置在此即可。但是每次配置都要打开缓存文件找到项目,复制编辑器名称粘贴非常麻烦,所以直接配置了设置项目应用的命令,此命令也属于open功能下,在项目搜索列表选择项目后,直接调用 uTools 提供的文件选择 APIutools.showOpenDialog,获取到应用信息后直接设置,方便快捷,老人小孩都会用。
exportfunctionchooseFile(){
returnutools.showOpenDialog({
filters:[{extensions:['app','exe','lnk']}],
properties:['openFile'],
}
...
const{payload}:{payload:string}=action;
constcommandType=commandMap[payload];
if(commandType===COMMAND.SET_APPLICATION){
constappPath:string=chooseFile();
setProjectApp(itemData.path!,appPath);//此函数由核心模块提供
utools.hideMainWindow();
utools.outPlugin();
return;
}
...
错误处理核心模块中函数会在某些情况下抛出错误,错误信息是以code及映射Map方式提供,方便后续做国际化,在获取到提示文本后,通过 uTools 提供的通知 APIutools.showNotification提示用户。
import{ErrorCodeMessage}from'cheetah-core';
exportfunctionnotice(message:string){
utools.showNotification(message)
}
exportfunctionerrorHandle(error:any){
consterrorCode:number=error.message;
notice(ErrorCodeMessage[errorCode]);
utools.hideMainWindow();
}
提交审核完成功能开发后,后面的工作就是发布到插件市场啦~
发布流程如下:
构建项目打开 uTools 开发者工具选择需要发布的项目选中“发布”选项卡点击右下角的“发布版本”按钮填写版本号,确认打包plugin.json所在目录填写版本说明、插件介绍,上传插件截图提交审核??preload.js需要保持明文可读,否则审核直接不通过。
提交以后就可以去 uTools 开发者交流群催管理员审核了,哈哈哈哈哈。
小结至此 Cheetah for uTools 的开发就完成了,当前版本已经更新到1.2.0,欢迎各位看官前往下载体验,Cheetah 项目现已开源,如果对插件安全方面有疑虑的可以逐行检查。
Cheetah 插件只使用了 uTools 很小一部分能力,还有很多强大的功能没有用到,有兴趣的朋友可以移步开发者文档。
下一篇将介绍猎豹 for Alfred 适配核心模块的过程,敬请期待~
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线