爱意满满的作品展示区。
muwoo

为了提高开发效率,我实现了 uTools 的超级面板

  •  1
     
  •   muwoo · Jul 5, 2021 · 3438 views
    This topic created in 1810 days ago, the information mentioned may be changed or developed.

    前言

    为了进一步提高开发工作效率,最近我们基于 electron 开发了一款媲美 uTools 的开源工具箱 rubick。该工具箱不仅仅开源,最重要的是可以使用 uTools 生态内所有开源插件!这将是巨大的能力,意味着 uTools 生态内所有插件可以无差异化使用到 rubick 中。

    设计交互上为了更能提高用户的使用效率,我们又尝试去实现了 uTools 中非常优秀的一些设计,比如:超级面板。该功能可以通过鼠标快速唤起uTools 插件能力,而不用再打开应用。比如上传图片,只要我们安装了图床插件,那么当鼠标选择桌面上某张图片时,即可快速呼出上传图片的菜单选项,方便省事。接下来一起看看实现方式吧!

    代码仓库

    Rubick github

    功能截图:

    文件夹下长按右建

    选择文件后长按右键

    选择文字后长按右键

    实现原理

    获取选中文案

    要实现改功能核心是要读取当前用户选中的文案或者文件,根据当前选择内容进行不同功能展示。但是核心有一个问题是如何来实现获取当前选中的内容。这个问题思考了很久很久,要想获取选中的文案,感觉唯一的办法是使用 ctrl + c 或者 command + c 来先复制到剪切板,再通过 electron clipboard 来获取当前剪切板内容。但是 utools 可不是通过先复制再长按这样的操作来实现的,而是直接选中文本或者文件长按后呼起超级面板。所以一定要在右击长按前获取到当前选中的内容。

    如果要这么干,可能真的无解了,之前就因为这么想,才被无解了。正确的思路应该是先长按再获取选中的内容。别看只是掉了个个,但实现确实天壤之别:

    1. 先获取选中内容:这就要求我们必须监听原生系统选中事件,但是 electron 并没有提供能力,我们也无法监听系统选择事件。
    2. 先右击,后获取内容,这样的好处在于先右击可以通过监听鼠标右击事件,相比选择事件更加容易。

    所以思路就有了,先监听长按右击事件:

    // macos
    const mouseEvents = require("osx-mouse");
    
    const mouseTrack = mouseEvents();
    // 按下去的 time
    let down_time = 0;
    
    // 是否弹起
    let isPress = false;
    
    // 监听右击
    mouseTrack.on('right-down', () => {
        isPress = true;
        down_time = Date.now();
        // 长按 500ms 后触发
        setTimeout(async () => {
          if (isPress) {
            // 获取选中内容
            const copyResult = await getSelectedText();
        }, 500);
    })
    mouseTrack.on('right-up', () => {
        isPress = false;
    });
    
    

    接下来一步就是要去实现获取选中内容,要获取选中内容有个比较骚的操作,就是:

    1. 通过 clipboard 先获取当前剪切板内容,并存下 A
    2. 通过 robot.js 来调用系统 command + c 或者 ctrl + c
    3. 再通过 clipboard 先获取当前剪切板内容,并存下 B
    4. 再将 A 写到剪切板中,返回 B

    先存剪切板内容的目的在于我们是偷偷帮用户执行了复制动作,当读取完用户选择内容后,需要回复用户之前的剪切板内容。接下来看一下简单的实现:

    const getSelected = () => {
      return new Promise((resolve) => {
        // 缓存之前的文案
        const lastText = clipboard.readText('clipboard');
    
        const platform = process.platform;
        
        // 执行复制动作
        if (platform === 'darwin') {
          robot.keyTap('c', 'command');
        } else {
          robot.keyTap('c', 'control');
        }
    
        setTimeout(() => {
          // 读取剪切板内容
          const text = clipboard.readText('clipboard') || ''
          const fileUrl = clipboard.read('public.file-url');
          
          // 恢复剪切板内容
          clipboard.writeText(lastText);
    
          resolve({
            text,
            fileUrl
          })
        }, 300);
      })
    }
    

    通知超级面板窗口当前选中内容

    当获取到了选中内容后,接下来就是需要创建超级面板的 BrowserWindow:

    const { BrowserWindow, ipcMain, app } = require("electron");
    
    module.exports = () => {
      let win;
    
      let init = (mainWindow) => {
        if (win === null || win === undefined) {
          createWindow();
        }
      };
    
      let createWindow = () => {
        win = new BrowserWindow({
          frame: false,
          autoHideMenuBar: true,
          width: 250,
          height: 50,
          show: false,
          alwaysOnTop: true,
          webPreferences: {
            webSecurity: false,
            enableRemoteModule: true,
            backgroundThrottling: false,
            nodeIntegration: true,
            devTools: false,
          },
        });
        win.loadURL(`file://${__static}/plugins/superPanel/index.html`);
        win.once('ready-to-show', () => win.show());
        win.on("closed", () => {
          win = undefined;
        });
      };
    
      let getWindow = () => win;
    
      return {
        init: init,
        getWindow: getWindow,
      };
    };
    

    然后再通知 superPanel 进行内容展示:

     win.webContents.send('trigger-super-panel', {
      ...copyResult,
      optionPlugin: optionPlugin.plugins,
    });
    

    超级面板点击操作

    接下来要实现超级面板点击操作,这块也是比较简单的了,直接上代码好了:

    1. 打开 Terminal

    const { spawn } = require ('child_process');
    
    spawn('open', [ '-a', 'Terminal', fileUrl ]);
    

    2. 新建文件

    remote.dialog.showSaveDialog({
      title: "请选择要保存的文件名",
      buttonLabel: "保存",
      defaultPath: fileUrl.replace('file://', ''),
      showsTagField: false,
      nameFieldLabel: '',
    }).then(result => {
      fs.writeFileSync(result.filePath, '');
    });
    

    3. 复制路径

    clipboard.writeText(fileUrl.replace('file://', ''))
    

    最后

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

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

    11 replies    2021-07-10 10:46:24 +08:00
    Chemist
        1
    Chemist  
       Jul 5, 2021
    骚是真的骚,牛也是真的牛。
    lqzhgood
        2
    lqzhgood  
       Jul 6, 2021
    utools 挺难受的就是 插件 enter 参数
    wox 那样 插件 空格 参数 爽很多。
    ljinkai
        3
    ljinkai  
       Jul 6, 2021
    想法挺棒👍的,赞一个
    yolee599
        4
    yolee599  
       Jul 7, 2021 via Android
    小工具用 electron,感觉挺吃内存的
    muwoo
        5
    muwoo  
    OP
       Jul 7, 2021
    @yolee599 老哥 这可不是小工具
    ob
        6
    ob  
       Jul 7, 2021 via Android
    utools 的内网穿透插件你这个实现了吗?
    muwoo
        7
    muwoo  
    OP
       Jul 8, 2021
    @ob 计划中
    uyZL3221XZ2xGZ3D
        8
    uyZL3221XZ2xGZ3D  
       Jul 9, 2021
    这,不会有侵权的问题吗?
    xFrye
        9
    xFrye  
       Jul 9, 2021
    推广自己的东西就推嘛,一直碰瓷人家 utools 什么意思?
    a570295535
        10
    a570295535  
       Jul 10, 2021
    哇,这个很不错!
    只有 mac 版,
    拜了个拜!
    dreasky
        11
    dreasky  
       Jul 10, 2021
    需要加个设置某些窗口关闭此功能,某些窗口长按右键会严重影响用户操作,比如游戏窗口
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1035 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 67ms · UTC 18:09 · PVG 02:09 · LAX 11:09 · JFK 14:09
    ♥ Do have faith in what you're doing.