前言

上篇手把手教你实现一个支持插件化的 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. C++ primer plus读书笔记——第7章 函数——C++的编程模块

    第7章 函数--C++的编程模块 1. 函数的返回类型不能是数组,但可以是其他任何一种类型,甚至可以是结构和对象.有趣的是,C++函数不能直接返回数组,但可以将数组作为结构或对象的组成部分来返回. 2 ...

  2. 【BUAA软工】技术规格说明书

    项目 内容 班级:北航2020春软件工程 博客园班级博客 作业:技术规格说明书 技术规格说明书 宏观技术 后端 WEB服务器 WEB服务器选取的是Springboot,作为当下Java语言最主流的WE ...

  3. 消息队列RabbitMQ(二):RabbitMQ的系统架构概述

    前言 RabbitMQ是基于AMQP协议的,要想深入理解RabbitMQ,就必须先了解AMQP是个什么东东? AMQP协议 AMQP即Advanced Message Queuing Protocol ...

  4. VS·调试过程中某个操作导致调试突然退出之解决方案

    阅文时长 | 0.11分钟 字数统计 | 232字符 主要内容 | 1.引言&背景 2.声明与参考资料 『VS·调试过程中某个操作导致调试突然退出之解决方案』 编写人 | SCscHero 编 ...

  5. JAVA基础——运算符号

    运算符(java) 算数运算符:+,-,*,/,%(取余),++,-- 赋值运算符:= 关系运算符:<, >, >= ,<= ,== , != 逻辑运算符:&& ...

  6. [刷题] PTA 03-树3 Tree Traversals Again

    用栈实现树遍历 1 #include<stdio.h> 2 #include<string.h> 3 #define MAXSIZE 30 4 5 int Pre[MAXSIZ ...

  7. Linux下记录登录用户历史操作

    前言:众所周知Linux是一个可以同时让多个用户登录的操作系统,每个用户的操作都影响着Linux运行,除了要做好安全工作以外,防止人为恶意损坏也是很关键的,比如有人恶意执行危险命令,要查找就得记录所有 ...

  8. 【odoo14】【用户侧】权限配置

    以下内容仅适用于odoo的客户,不适用于开发人员. 下文介绍中涉及的概念及UI均是在odoo14社区版中进行. 目录 一. odoo中的对象 二. 权限控制 2.1 实现原理 2.2 UI方式实现权限 ...

  9. 快速上手 Linkerd v2 Service Mesh(服务网格)

    在本指南中,我们将引导您了解如何将 Linkerd 安装到您的 Kubernetes 集群中. 然后我们将部署一个示例应用程序来展示 Linkerd 的功能. 安装 Linkerd 很容易.首先,您将 ...

  10. NOIP 模拟4 T2

    本题属于二和一问题 子问题相互对称 考虑对于问题一:知a求b 那么根据b数组定义式 显然能发现问题在于如何求dis(最短路) 有很多算法可供选择 dijsktra,floyed,bfs/dfs,spf ...