从零到一,用 Electron 开发桌面效率工具

Electron 已经不算新技术,最先是 github 从 Atom 编辑器衍生出来的框架。经过编写 Javascript, HTML, CSS 能快速编译出跨系统的桌面 app。Electron 的出现使得做为前端开发工程师的咱们输出范围更广。css

分享最近用 Electron 作的一个基于番茄工做法的小应用,因为实现难度不大,市面上已经有很是多相似的app。咱们尝试用 Electron 来实现一个。前端

最终效果预览: react

effect

🍅 工做法

番茄工做法的核心是将任务颗粒拆分到单位时间内(25分钟)能够完成,在这25分钟内专一在这个任务三,不容许作任何与任务无关的事,在任务任务完成以后能够短暂休息一会,再继续工做。webpack

因此这个 app 的重点是让你建立任务,⏳ 25分钟,帮让 focus on 当前在作的任务。git

站在巨人的肩膀上开发

尝试新技术的时候,不要从零开始学习如何搭建技术栈,先作出来,遇到问题再查。 Electron 社区有不少优秀的沉淀,工具,模板,组件,教程等等。github

搜索 react 关键字,找到了 electron-react-boilerplate 这个样板库, 这个库已经集成了 react, redux, sass, flow, hmr webpack 等工具,同时准备好 electron-builder 打包工具,做为 electron 新手,咱们优先选择开箱即用的工具,快速开启业务开发。web

SVG 和 React Component

大概画了一下草图,准备进入开发阶段。考虑后面会用到 svg icon,先在 FlatIcon 上找些免费的图标,下载 SVG 文件。json

经过 SVGR 在线工具导入 svg 内容生成 React Component 代码。(svgr 也有 cli 等工具)redux

用 SVG Component 的好处是能够在代码上更灵活地控制样式,相比 png 图标可交互性强,复用率高。浏览器

SVGR

托盘和托盘弹窗

这个 app 启动的时候就隐藏在托盘菜单的一角,点击的时候显示 BrowserWindow,经过 Electron 提供的方法,能够得到托盘和托盘弹窗的 Bounds 信息,设置坐标位置。

// main.js
const tray = new Tray(path.join(__dirname, '../static', 'tray.png'));

const mainWindow = new BrowserWindow({
  // ...others
  frame: false,
  resizable: true,
  transparent: true
});

const showWindow = () => {
  const { x, y } = getPositionFromActiveDisplay();
  mainWindow.setPosition(x, y, true);
  mainWindow.show();
};

const getPositionFromActiveDisplay = () => {
  const trayBounds = tray.getBounds();
  const windowBounds = mainWindow.getBounds();

  const x = Math.round(trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2);
  const y = Math.round(trayBounds.y + trayBounds.height);

  return { x, y };
};
复制代码

tray

👆图的三角是由前端代码绘制的,加上 frame 和 electron 背景色,应该长这样。

渲染线程和主线程

app 须要倒计时功能,告诉用户距离任务完成时间还有多久。Electron 有渲染进程和主线程,BrowserWindow 不可见的时候,渲染进程会尽可能减小消耗,因此若是 Tick 在渲染进程的话,当 app 处于后台时会出现很是大的时间误差。这里使用 Electron 提供的 ipcMain 和 ipcRenderer 作进程通讯。

在主线程每秒发送 Tick 事件

// main.js
ipcMain.once('store-ready', event => {
  const run = () => {
    setTimeout(() => {
      run();
      event.sender.send('tick');
    }, 1000);
  };
  run();
});
复制代码

渲染进程就收事件并将 dispatch TICK action。

// app/index.js
const store = configureStore({
  tasks: electronStore.getTasks()
});

ipcRenderer.send('store-ready');
ipcRenderer.on('tick', () => {
  store.dispatch({
    type: TICK
  });
});
复制代码

redux store 里面判断当前执行的任务计算倒计时时间。

switch  (action.type) {
  case TICK:
    return {
      ...state,
      rows: state.rows.map(task =>
          task.id === state.currentId
            ? {
                ...task,
                remain: Math.max(task.remain - 1, 0)
              }
            : task
      )
    };
复制代码

数据持久存储

数据持久化有不少种方案,由于是前端浏览器,咱们能够选择 localStorage, Cookie,indexDB 等等。考虑可靠性,持久化以及存储空间,还能够经过 Electron 写文件的方式,把数据写入到应用路径下。这样即便 app 被卸载了,只要数据没被清空,用户数据还在。

经过 Electron app getPath 能够得到应用存储路径

import { app } from 'electron';
app.getPath('userData');
复制代码

mac 下应用 app 的路径是 /Users/user/Library/Application Support/focus。更简单的方式能够直接用开源库 electron-store,以 key-value 的格式存储 json 文件。

{
  "tasks": {
    "rows": [
      {
        "name": "任务名称",
        "id": "91ac7f05-76f4-46ea-addb-f392a3a29b54",
        "created_at": 1553398427806,
        "plan": 1500,
        "remain": 0,
        "done": true
      }
    ],
    "currentId": "91ac7f05-76f4-46ea-addb-f392a3a29b54"
  }
}
复制代码

倒计时 UI

有些样式可能用 css 实现难度较大,而用 svg 的方式实现起来很是简单。好比倒计时 UI,路径圆角和路径长度用 CSS 实现复杂度较高。能够在 Sketch 上直接绘制处理,导出成 svg,直接经过 react 代码控制。

export default function(props: Props) {
  const offset = percentage * totalLength;
  const cx =
    Math.cos(percentage * Math.PI * 2 - Math.PI * 0.5) * radius + radius;
  const cy =
    Math.sin(percentage * Math.PI * 2 - Math.PI * 0.5) * radius + radius;
  return (
    <svg>
      ...others
      <circle
        id="path-1"
        cx={cx}
        cy={cy}
        r="32"
        fill="white"
        style={{ transition: '1s linear' }}
      />
      <path
        ...others
        strokeLinecap="round"
        strokeDasharray={totalLength}
        strokeDashoffset={offset}
        style={{ transition: '1s linear' }}
      />
    </svg>
  );
}
复制代码

临界状态判断

app 在任务时间结束时须要有 Notification,因为👆的 Tick 设计,判断任务是否完成能够放在 redux middleware 上。

// middlewares/tasks
export default ({ getState }) => next => action => {
  if (typeof action === 'object' && action.type === 'TICK') {
    const beforeCount = getTimeEndTaksCount(getState);
    next(action);
    const afterCount = getTimeEndTaksCount(getState);

    if (beforeCount !== afterCount) {
      new Notification('Focus,任务完成了吗?');
    }
  } else {
    next(action);
  }
};
复制代码

通过一个 Tick action 以后,判断任务完成数是否有变化,并使用 HTML5 Notification 通知用户。

notification

Travis CI

功能开发完毕以后,使用 electron-builder 进行打包发布,构建以后推到 github release 下,用户能够直接在这下载到最新的包。

一样的, boilerplate 已经准备好 .travis.yml 文件,惟一须要咱们操做的是在 github.com/settings/to… 上生成 token,在 www.travis-ci.org/ 构建以前配置 Environment VariablesGH_TOKEN

tirgger build, 成功以后就能看到构建成功过的包,下载使用

总结

使用 Electron,前端开发者可使用本身的武器构建跨系统的桌面端应用,并且不用学习其余技术,缺点是一个小小的功能打包完的体积是 70M。

这个 app 从有想法到最终实现比预期的简单,感兴趣的同窗也能够本身 DIY 些小玩意儿。完整的代码在 github 上github.com/HelKyle/foc…,欢迎体验,同时也欢迎 star~

demo
相关文章
相关标签/搜索