大型情感类技术连续剧-徒手撸一个 uTools(二)
前言
上篇手把手教你实现一个支持插件化的 uTools 工具箱我们介绍过了如何通过 electron 实现 utools 的插件功能体系,并按照 utools 的交互和设计做出了一套可以支持插件化的桌面端工具箱 Rubick。
本篇将继续为大家介绍如何再基于 electron 实现 utools 的搜索能力。
搜索能力实现
utools 搜索核心分为系统命令、插件命令、系统app功能搜索等几大类,下面我们来一一实现这3类功能的检索能力。
由于这 3 类搜索搜索出来的内容点击触发的交互不一样,所以我们设计了一个枚举类型来标记这三种检索内容,用于点击后触发不同的行为。
const SEARCH_TYPE = {
  DEV: 'dev', // 测试插件
  PROD: 'prod', // 已安装的插件
  SYSTEM: 'system', // 系统插件
  APP: 'app' // 应用 app
}
开发者插件
开发者插件分为已安装和本地开发 2 种类型,分别根据 SEARCH_TYPE.PROD 和 SEARCH_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(),
    });
}
再说一下点击后触发的逻辑步骤:
- 设置左上角标签内容为搜索关键词,并设置右上角 icon
 - 清空搜索内容
 - 打开 
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);
    }
  });
});
代码看的有点多,其实很简单,主要也是几步走:
- 根据定义好的路径查找所有 app 和 PreferencePanes
 - 应为下拉选项需要展示插件的 icon 所以对于 app 和 PreferencePanes 需要查找 icns
 - 根据默认规则查找 icns 如果找不到再用性能较低的方式模糊匹配
 - 检索成功后设置好下拉选项
 
最后一步就是点击呼出了:
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(二)的更多相关文章
- 徒手撸一个 Spring Boot 中的 Starter ,解密自动化配置黑魔法!
		
我们使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中.Starter 为我们带来了众多的自动化配置,有了这些自动化配置,我们可以不费吹灰之力就能搭建一个生产级开发环境,有的小 ...
 - 徒手撸一个简单的RPC框架
		
来源:https://juejin.im/post/5c4481a4f265da613438aec3 之前在牛逼哄哄的 RPC 框架,底层到底什么原理得知了RPC(远程过程调用)简单来说就是调用远程的 ...
 - 大型情感类电视连续剧--Android高德之旅(3)地图交互
		
总要说两句 前两篇讲到了地图的基础显示和地图类型,今天来记录下高德地图交互相关的设置.地图的绘制分很多层,层级的显示需要根据不同的场景来设置.地图的触摸事件也很丰富,有单击.双击.单指拖拽.双指拖拽. ...
 - 从零开始徒手撸一个vue的toast弹窗组件
		
相信普通的vue组件大家都会写,定义 -> 引入 -> 注册 -> 使用,行云流水,一气呵成,但是如果我们今天是要自定义一个弹窗组件呢? 首先,我们来分析一下弹窗组件的特性(需求): ...
 - 大型情感类电视连续剧--Android高德之旅(2)地图类型
		
总要说两句 今天继续我们的Android高德之旅,上一篇已经能够显示最主要的地图了.有主要的放大缩小功能.还有最后做的点击3D旋转.倾斜视角的效果.今天这篇文章来记录一下高德地图的5种地图类型. (其 ...
 - 使用 Go 语言徒手撸一个负载均衡器
		
负载均衡器在 Web 架构中扮演着非常重要的角色,被用于为多个后端分发流量负载,提升服务的伸缩性.负载均衡器后面配置了多个服务,在某个服务发生故障时,负载均衡器可以很快地选择另一个可用的服务,所以整体 ...
 - 手把手教你用netty撸一个ZkClient
		
原文地址: https://juejin.im/post/5dd296c0e51d4508182449a6 前言 有这个想法的缘由是前一阵子突发奇想, 想尝试能不能直接利用js连接到zookeeper ...
 - 徒手生撸一个验证框架,API 参数校验不再怕!
		
你们之中大概率早已练就了代码的拷贝.粘贴,无敌的码农神功,其实做久了业务功能开发,练就这两个无敌神功,那是迟早的事儿.今天先抛一个小问题,来打通你的任督二脉,就是很好奇的问一下:业务功能开发中,输入参 ...
 - 看了 Spring 官网脚手架真香,也撸一个 SpringBoot DDD 微服务的脚手架!
		
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么我们要去造轮子? 造轮子的核心目的,是为了解决通用共性问题的凝练和复用. 虽然 ...
 
随机推荐
- Msf--控制 Android手机
			
|>>>中华人民共和国网络安全法<<<|警告:请勿用于非法用途,后果自负! 0.环境 虚拟机 KaliLinux 手机 Redmi 6A 同一局域网下 1.查看控制 ...
 - commit信息修改
			
场景:向社区提交commit信息,code reviewer给你回复说,请添加TrivialFix并且完善commit信息.好吧,虽然这对代码的运行无关紧要,但是对于日后的代码管理是很有必要的. 解决 ...
 - gitlab的CI/CD实现
			
环境准备: gitlab账号公网账号:代码仓库和编译器 目标机:装有docker和gitlab-runner环境的服务器(Linux或类unix机器,我使用的时centos 项目代码:testgola ...
 - cron 任务的典型格式是:  分钟(0-59) 小时(0-24) 日(1-31) 月(1-12) 星期(0-6) 要执行的命令
			
https://linux.cn/article-9687-1.html Cron 是您可以在任何类 Unix 操作系统中找到的最有用的实用程序之一.它用于安排命令在特定时间执行.这些预定的命令或任务 ...
 - RHEL/CentOS 7 中配置 PXE 网络启动服务器
			
RHEL/CentOS 7 中配置 PXE 网络启动服务器 作者: Matei Cezar 译者: LCTT joeren | 2015-02-17 14:28 评论: 13 收藏: 8 分享: ...
 - Linux中级之windows下使用curl命令(cmd命令行)
			
在官网下载工具包:http://curl.haxx.se/download.html(根据自己电脑系统位数选择) 使用方式一:在curl.exe目录中使用 解压下载后的压缩文件,通过cmd命令进入到c ...
 - CentOS 下解决ssh登录 locale 警告
			
最近登录一台CentOS 6机器,发现每次登录都提示如下警告: -bash: warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF ...
 - Mui入门(Day_42)
			
开始体验Mui 1. 安装HbuilderX 下载地址:https://www.dcloud.io/hbuilderx.html 2. 新建Mui项目 3. 文件结构介绍 _ css : 样式表文件 ...
 - Django(38)mac安装redis
			
安装redis 1.使用Homebrew安装Redis brew install redis 执行上述命令后出现以下内容,则成功安装 Download failed: https://mirrors. ...
 - TVM 优化 ARM GPU 上的移动深度学习
			
TVM 优化 ARM GPU 上的移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与桌面平台上所做的类似,在移动设备中使用 GPU 既有利于推理速度,也有利于能源 ...