從零到一,用 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
相關文章
相關標籤/搜索