前言

上篇手把手教你实现一个支持插件化的 uTools 工具箱我们介绍过了如何通过 electron 实现 utools 的插件功能体系,并按照 utools 的交互和设计做出了一套可以支持插件化的桌面端工具箱 Rubick

Rubick 源码

本篇将继续为大家介绍如何再基于 electron 实现 utools 的搜索能力。

搜索能力实现

utools 搜索核心分为系统命令、插件命令、系统app功能搜索等几大类,下面我们来一一实现这3类功能的检索能力。

由于这 3 类搜索搜索出来的内容点击触发的交互不一样,所以我们设计了一个枚举类型来标记这三种检索内容,用于点击后触发不同的行为。

const SEARCH_TYPE = {
DEV: 'dev', // 测试插件
PROD: 'prod', // 已安装的插件
SYSTEM: 'system', // 系统插件
APP: 'app' // 应用 app
}

开发者插件

开发者插件分为已安装本地开发 2 种类型,分别根据 SEARCH_TYPE.PRODSEARCH_TYPE.DEV 来进行区分。

搜索内容的基础数据结构如下:

const item = {
name: '搜索的title',
icon: '插件的icon',
desc: '插件的描述信息',
type: 'SEARCH_TYPE 对应的插件类型',
click: '点击事件'
}

拿一个具体的搜索插件举例:

const item = {
// 搜索插件功能对应的 cmd
name: cmd,
// 插件icon 地址
icon: plugin.sourceFile ? 'image://' + path.join(plugin.sourceFile, `../${plugin.logo}`) : plugin.logo,
// 功能描述
desc: fe.explain,
// 类型
type: plugin.type,
// 点击后的动作
click: (router) => {
actions.openPlugin({commit}, {cmd, plugin, feature: fe, router});
}
}

整体来看已安装插件和本地插件交互展示上唯一需要区分的是本地插件需要打上一个 tag 用于标记,这样才不至于混淆线上插件和本地插件:

<a-tag v-show="item.type === 'dev'">开发者</a-tag>
<a-tag v-show="item.type === 'system'">系统</a-tag>

我们来看一下完成后的效果:

会带有开发者标记。

接下来就是实现openPlugin点击效果,点击核心能力是需要对 plugin 进行 webview 渲染。上篇问诊已经介绍过了如何实现这个 webview 这里就不在赘述,说一下点击逻辑:

openPlugin() {
commit('commonUpdate', {
// 点击后设置标签tag为搜索词
selected: {
key: 'plugin-container',
name: cmd,
icon: 'image://' + path.join(plugin.sourceFile, `../${plugin.logo}`),
},
// 清空搜索内容
searchValue: '',
// 展示 plugin webview
showMain: true
});
// 计算webview内容高度
ipcRenderer.send('changeWindowSize-rubick', {
height: getWindowHeight(),
});
}

再说一下点击后触发的逻辑步骤:

  1. 设置左上角标签内容为搜索关键词,并设置右上角 icon
  2. 清空搜索内容
  3. 打开 webview 加载插件,并动态计算插件高度

最后效果如下:

系统插件

系统插件是 utools 内置的,所以我们也需要将系统插件内置到 Rubick 中,这里我拿实现一个取色器来举例,去实现一个系统插件。首先先定义好系统插件的数据结构:

const SYSTEM_PLUFINS = [
{
"pluginName": "屏幕颜色拾取",
"logo": "https://alicdn.com/img/6a1b4b8a17da45d680ea30b53a91aca8.png",
"features": [
{
"code": "pick",
"explain": "rubick 帮助文档",
"cmds": [ "取色", "拾色", 'Pick color' ]
},
],
"tag": 'rubick-color',
}
]

字段说明:

  • pluginName:系统插件展示的名称
  • logo: 系统插件展示的logo
  • features: 系统插件的功能列表
  • feature.code: 系统插件执行的code码
  • tag: 系统插件唯一标记

系统插件的交互展示和开发者插件本无太大的差异,核心较大的差异在于点击后的功能和开发者插件不太一样,我们来看看系统插件的点击交互逻辑:

opnPlugin() {
// 如果点击的是系统插件
if (plugin.type === 'system') {
// 调用系统函数
systemMethod[plugin.tag][feature.code](); // 清空选择
commit('commonUpdate', {
selected: null,
showMain: false,
options: [],
}); // 设置高度为初始高度
ipcRenderer.send('changeWindowSize-rubick', {
height: getWindowHeight([]),
}); // 跳转到首页
router.push({
path: '/home',
});
}
}

所以对系统插件来说,由于系统插件本身并无 webview 所以不需要打开 webview 来承载插件,而是调用系统函数,比如 color-pick 调用的对应系统函数如下:

export default {
'rubick-color': {
pick() {
ipcRenderer.send('start-picker')
}
},
}

main 进程发送取色能力。如何取色将在后面章节介绍。实现后的交互如下:

系统app功能搜索

针对于 macos 用户,所安装的系统 App 都放在了 /System/Applications/Applications 下,所以要实现 app 搜索,就是需要对 /System/Applications/Applications 目录下的 app 进行检索。但有的时候除了 app 需要搜索,一些系统功能也需要搜索,比如偏好设置之类的。偏好设置一般存方的路径在 /System/Library/PreferencePanes 中。

接下来第一步需要做的是检束所有 app 和 PreferencePanes:

const APP_FINDER_PATH = [
'/System/Applications',
'/Applications',
'/System/Library/PreferencePanes',
]; APP_FINDER_PATH.forEach((searchPath) => {
// 搜索对应目录
fs.readdir(searchPath, (err, files) => {
// 查询所有 app 和 PreferencePanes
try {
for (let i = 0; i < files.length; i++) {
const appName = files[i];
const extname = path.extname(appName);
const appSubStr = appName.split(extname)[0]; if ((extname === '.app' || extname === '.prefPane') >= 0 ) {
// 查找 应用程序的 icon
try {
const path1 = path.join(searchPath, `${appName}/Contents/Resources/App.icns`);
const path2 = path.join(searchPath, `${appName}/Contents/Resources/AppIcon.icns`);
const path3 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr}.icns`);
const path4 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr.replace(' ', '')}.icns`);
let iconPath = path1;
if (fs.existsSync(path1)) {
iconPath = path1;
} else if (fs.existsSync(path2)) {
iconPath = path2;
} else if (fs.existsSync(path3)) {
iconPath = path3;
} else if (fs.existsSync(path4)) {
iconPath = path4;
} else {
// 性能最低的方式
const resourceList = fs.readdirSync(path.join(searchPath, `${appName}/Contents/Resources`));
const iconName = resourceList.filter(file => path.extname(file) === '.icns')[0];
iconPath = path.join(searchPath, `${appName}/Contents/Resources/${iconName}`);
} // 创建图片
nativeImage.createThumbnailFromPath(iconPath, {width: 64, height: 64}).then(img => {
// 创建搜索项
fileLists.push({
name: appSubStr,
value: 'plugin',
icon: img.toDataURL(),
desc: path.join(searchPath, appName),
type: 'app',
action: `open ${path.join(searchPath, appName).replace(' ', '\\ ')}`
})
})
} catch (e) {
} }
}
} catch (e) {
console.log(e);
}
});
});

代码看的有点多,其实很简单,主要也是几步走:

  1. 根据定义好的路径查找所有 app 和 PreferencePanes
  2. 应为下拉选项需要展示插件的 icon 所以对于 app 和 PreferencePanes 需要查找 icns
  3. 根据默认规则查找 icns 如果找不到再用性能较低的方式模糊匹配
  4. 检索成功后设置好下拉选项

最后一步就是点击呼出了:

openPlugin() {
if (plugin.type === 'app') {
// 呼出 app
execSync(plugin.action);
commit('commonUpdate', {
selected: null,
showMain: false,
options: [],
searchValue: '',
});
ipcRenderer.send('changeWindowSize-rubick', {
height: getWindowHeight([]),
});
return;
} }

最后来看一下系统app检索效果:

结语

本篇主要介绍如何实现一个类似于 utools 的插件搜索功能,当然这远远不是 utools 的全部,下期我们再继续介绍如何实现 utools 其他能力。欢迎大家前往体验 Rubick 有问题可以随时提 issue 我们会及时反馈。

另外,如果觉得设计实现思路对你有用,也欢迎给个 Star:https://github.com/clouDr-f2e/rubick

大型情感类技术连续剧-徒手撸一个 uTools(二)的更多相关文章

  1. 徒手撸一个 Spring Boot 中的 Starter ,解密自动化配置黑魔法!

    我们使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中.Starter 为我们带来了众多的自动化配置,有了这些自动化配置,我们可以不费吹灰之力就能搭建一个生产级开发环境,有的小 ...

  2. 徒手撸一个简单的RPC框架

    来源:https://juejin.im/post/5c4481a4f265da613438aec3 之前在牛逼哄哄的 RPC 框架,底层到底什么原理得知了RPC(远程过程调用)简单来说就是调用远程的 ...

  3. 大型情感类电视连续剧--Android高德之旅(3)地图交互

    总要说两句 前两篇讲到了地图的基础显示和地图类型,今天来记录下高德地图交互相关的设置.地图的绘制分很多层,层级的显示需要根据不同的场景来设置.地图的触摸事件也很丰富,有单击.双击.单指拖拽.双指拖拽. ...

  4. 从零开始徒手撸一个vue的toast弹窗组件

    相信普通的vue组件大家都会写,定义 -> 引入 -> 注册 -> 使用,行云流水,一气呵成,但是如果我们今天是要自定义一个弹窗组件呢? 首先,我们来分析一下弹窗组件的特性(需求): ...

  5. 大型情感类电视连续剧--Android高德之旅(2)地图类型

    总要说两句 今天继续我们的Android高德之旅,上一篇已经能够显示最主要的地图了.有主要的放大缩小功能.还有最后做的点击3D旋转.倾斜视角的效果.今天这篇文章来记录一下高德地图的5种地图类型. (其 ...

  6. 使用 Go 语言徒手撸一个负载均衡器

    负载均衡器在 Web 架构中扮演着非常重要的角色,被用于为多个后端分发流量负载,提升服务的伸缩性.负载均衡器后面配置了多个服务,在某个服务发生故障时,负载均衡器可以很快地选择另一个可用的服务,所以整体 ...

  7. 手把手教你用netty撸一个ZkClient

    原文地址: https://juejin.im/post/5dd296c0e51d4508182449a6 前言 有这个想法的缘由是前一阵子突发奇想, 想尝试能不能直接利用js连接到zookeeper ...

  8. 徒手生撸一个验证框架,API 参数校验不再怕!

    你们之中大概率早已练就了代码的拷贝.粘贴,无敌的码农神功,其实做久了业务功能开发,练就这两个无敌神功,那是迟早的事儿.今天先抛一个小问题,来打通你的任督二脉,就是很好奇的问一下:业务功能开发中,输入参 ...

  9. 看了 Spring 官网脚手架真香,也撸一个 SpringBoot DDD 微服务的脚手架!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么我们要去造轮子? 造轮子的核心目的,是为了解决通用共性问题的凝练和复用. 虽然 ...

随机推荐

  1. 持续集成和持续交付工具-jenkins

    jenkins说明 jenkins是一款由Java编写的开源的持续集成工具,它运行在Servlet容器中(例如Apache Tomcat).它支持软件配置管理(SCM)工具(包括AccuRev SCM ...

  2. [DB] Memcache

    什么是Memcache Redis的前身 严格来说只能叫缓存,不支持持久化,停电后数据丢失 Strom.Spark Streaming实时计算的结果一般会保存在Redis中 JDBC是性能瓶颈 关系型 ...

  3. Scala 中为什么不建议用 return 关键字

    在scala中使用 return 的话,编译的时候会提示the latest statement is method is automatically returned, use of th retu ...

  4. 关于STM32的可编程电压检测器的使用方法

    关于STM32的可编程电压检测器的使用方法 思维导图总览: 代码: 1 #include "sys.h" 2 #include "delay.h" 3 #inc ...

  5. Linux中级之windows下使用curl命令(cmd命令行)

    在官网下载工具包:http://curl.haxx.se/download.html(根据自己电脑系统位数选择) 使用方式一:在curl.exe目录中使用 解压下载后的压缩文件,通过cmd命令进入到c ...

  6. 10.12 telnet:远程登录主机

    telnet命令 以前是用于登录远程主机,对远程主机进行管理的.但是因为telnet是采用明文传送报文的,其安全性不好,因此现在很多Linux服务器都不开放telnet服务,而是改用更安全的SSH服务 ...

  7. Redis()- 布隆过滤器

    一.布隆过滤器 布隆过滤器:一种数据结构.由二进制数组(很长的二进制向量)组成的.布隆过滤器可以用于检索一个元素是否在一个集合中.它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识 ...

  8. Python+Selenium学习笔记3 - 二维码生成

    用qrcode模块生成二维码 # coding = utf-8 import qrcode qr = qrcode.QRCode( version=1, error_correction=qrcode ...

  9. 物联网安全Wi-Fi漫游

    物联网安全Wi-Fi漫游 根据Statistica的最新报告,到2021年,全球正在使用的Wi-Fi连接设备的数量预计将增长到222亿.这种Wi-Fi的广泛使用不仅包括消费者的Wi-Fi使用,而且还包 ...

  10. 无监督域对抗算法:ICCV2019论文解析

    无监督域对抗算法:ICCV2019论文解析 Drop to Adapt: Learning Discriminative Features for Unsupervised Domain Adapta ...