VueとElectronでタスク管理アプリを作りはじめた

![](https://monosnap-tady.s3.amazonaws.com/task-time-tracker.gif)


# 動機

いままで様々なタスク管理アプリを使ってきましたが、いまは[Toggl](https://toggl.com/)をメインに使っています。

しかしTogglは、どちらかと言うとタイムトラッキングアプリであって、ほんとうの意味でのタスク管理には向いていません。

個人的には、「カンバン」方式のタスク管理と、各タスクの所要時間を統一的に管理したいのですが、なかなかいいものが見つかりません。

[KanbanFlow](https://kanbanflow.com/)も試しましたが、個人で使うには重厚で、ちょっと馴染みませんでした。

そこで、VueとElectronで自作しようと考えました。

# 準備

[electron-vue](https://github.com/SimulatedGREG/electron-vue) で一発です。

詳細は http://qiita.com/SatoTakumi/items/fd79672d7eb8a9b4a0bb さんが説明されてれいます。

初めてElectronを使いましたが、どこにも詰まることなく導入できたのは驚きです。

Electron側からOSのAPIをいくつか利用できるので、例えば「現在アクティブなWindow」とか「マウスの位置」などを取得することで、Togglのような「非アクティブを検知してタスク消化を促す」仕組みも簡単に実装できそうです。

# Componentの構造

Componentはシンプルに以下のように分割しています。

![task-time-tracker.4li6c.2016-12-19.午前0時-15-06.png](https://qiita-image-store.s3.amazonaws.com/0/10272/cd4262a2-1e19-89b6-cabe-3bd5f853b8fb.png)

## 親Component

親Componentである `App.vue` はシンプルに、子Componentを回しているだけです。

要素のドラッグを実現するために [vue-dragula](https://github.com/Astray-git/vue-dragula) を利用しています。
このドラッグイベントをキャッチするために、グローバルはイベントバス `Vue.vueDragula.eventBus` を利用して「どこにドラッグされたか」を受け取り、 `mutation` を叩いています。

```html:App.vue
<template>
  <div id="app-main">
    <div id="app-left">
      <div>
        ToDo
        <button id="new-task" @click="newTask">New Task</button>
      </div>
      <div class="task-card-container" v-dragula="todoTasks" bag="bag" data-status="todo">
        <todo-task :task="task" v-for="task in todoTasks" :key="task.id"></todo-task>
      </div>
    </div>
    <div id="app-center">
      <div>Doing</div>
      <div class="task-card-container" v-dragula="doingTasks" bag="bag" data-status="doing">
        <doing-task :task="task" v-for="task in doingTasks" :key="task.id"></doing-task>
      </div>
    </div>
    <div id="app-right">
      <div>Done</div>
      <div class="task-card-container" v-dragula="doneTasks" bag="bag" data-status="done">
        <done-task :task="task" v-for="task in doneTasks" :key="task.id"></done-task>
      </div>
    </div>
  </div>
</template>

<script>
...
    mounted () {
      Vue.vueDragula.eventBus.$on('drop', ([bag, dropElm, target, source]) => {
        console.log(target.children)
        const index = Array.prototype.indexOf.call(target.children, dropElm)
        const taskId = dropElm.dataset.taskId
        const nextStatus = target.dataset.status

        this.$store.commit(t.UPDATE_TASK_STATUS, {taskId, nextStatus, index})
      })
    }
...
</script>
```


## 子Component

子Componentは、タスク名と経過時間を表示するだけなので、こちらもシンプルです。

```html:DoingTask.vue
<template>
  <div class="task-card" :data-task-id="task.id">
    <div class="task-left">{{task.name}}</div>
    <div class="task-right">{{duration}}</div>
  </div>
</template>

<script>
  import moment from 'moment'
  import utils from '../utils'

  export default {
    props: ['task'],
    data () {
      return {
        duration: utils.formatHour(this.task.duration + (moment().unix() - this.task.start))
      }
    },
    mounted () {
      setInterval(() => {
        this.duration = utils.formatHour(this.task.duration + (moment().unix() - this.task.start))
      }, 1000 / 60)
    }
  }
</script>
```

一点、秒数を表示する部分を毎秒更新したかったので、`this.duration` は `computed` ではなく、`data`にして`setInterval`で更新しています。

# Store

Storeのデータ構造は `tasks` の配列になっています。

```js:store.js
const state = {
  tasks: [
    {
      id: 1234,
      status: 'done',
      name: 'task a',
      duration: 60 * 60 * 3,
      position: 1,
      history: [
        {
          start: moment('2016-12-01 06:00:00').unix(),
          end: moment('2016-12-01 09:00:00').unix()
        },
        {
          start: moment('2016-12-01 08:00:00').unix(),
          end: moment('2016-12-01 10:00:00').unix()
        }
      ]
    },
...
```


# まとめ

今回のコードはこちらにあります。
https://github.com/tadyjp/hello-electron-vue

Vueを使うと結構簡単にアプリが作れることがわかりました。
OSの機能を利用したリッチなアプリケーションを作る時に、「Vue+Electron」はお手軽な選択肢になるのではないかと思います。

年明けくらいには、実際に配れるアプリにしたいなと考えてます。続報あったらまたお知らせします。



# 2017/01/26追記

全く進んでいないorz