前言

上篇手把手教你实现一个支持插件化的 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. 《Spring 手撸专栏》第 2 章:小试牛刀(让新手能懂),实现一个简单的Bean容器

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 上学时,老师总说:不会你就问,但多数时候都不知道要问什么! 你总会在小傅哥的文章前言 ...

  2. C++ primer plus读书笔记——第8章 函数探幽

    第8章 函数探幽 1. 对于内联函数,编译器将使用相应的函数代码替换函数调用,程序无需跳到一个位置执行代码,再调回来.因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存. 2. 要使用内 ...

  3. CAS的理解

    CAS(CompareAndSweep)工作方式 ​ CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被 ...

  4. java并发编程:深入了解synchronized

    简介 synchronized是Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.同时它还保证了共享变量的内存可见性. ...

  5. [刷题] 203 Remove Linked List Elements

    要求 在链表中删除值为val的所有节点 示例 如 1->2->3->4->5->6->NULL,要求删除值为6的节点 返回1->2->3->4-& ...

  6. CSS中的颜色、长度、角度、时间

    一.颜色的表示方法 颜色是通过对红.绿和蓝光的组合来显示的. 1.颜色名 1 <!DOCTYPE html> 2 <html lang="en"> 3 &l ...

  7. 攻防世界(一)baby_web

    攻防世界系列:baby_web 方法一: 按照提示,初始界面自然想到index.php,访问后界面(注意到URL)仍是1.php 打开hackbar查看响应,发现确实有index.php点开看到了Fl ...

  8. 拉勾、Boss直聘、内推、100offer

    BOSS直聘 拉勾.Boss直聘.内推.100offer  

  9. IT菜鸟之网线制作

    网线是属于OSI七层模型中的物理层:网络中的数据传输媒介 备注:OSI七层模型后面会记录 网线制作所需要的资源素材: 1.网线 2.水晶头(类型:电话线RJ11,宽带线RJ45) 3.网线钳(非必需) ...

  10. 3.13eval函数

    eval 函数 eval() 函数十分强大 -- 将字符串 当成 有效的表达式 来求值 并 返回计算结果 ```python 基本的数学计算 In [1]: eval("1 + 1" ...