Reactjsを使ったLive Codingもどき！


メモ取りながらは大変ですねー。

# 1/4 15:30 妄想

React.js面白いですねー。
っと、写経も飽きたのでそろそろ何か作りたいです。
でも明日から仕事なので今日までに！！
私はゲームエンジン作りたいという野望があるのですが、さすがに大きすぎです！
なので、当たり判定エディタもどきを作りましょう。

このアプリのフローはこんな感じでしょうか！！

* 画像を読み込む
* マウスで連続して直線を引く
* 直線をクローズして、当たり判定に変換
* 頂点をドラッグで動かす


さて、フロントサイドJavaScriptの知識はほとんどないんですが、はたして作れるのでしょうか！？

# 1/4 15:46 環境構築

* webpack
* CoffeeScript
* React.js
* react-art

を使います！もりもり構築します〜


```bash
npm install -save react react-art webpack coffee-script coffee-loader
```

はい、ではwebpack.config.jsを書きます！

```js
module.exports = {
    devtool: "source-map",
    entry: {
        app: ['webpack/hot/dev-server', './app/index.js']
    },
    output: {
        path: './build',
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: "./build"
    },
    module: {
        loaders: [
            { test: /\.coffee$/, loader: "coffee-loader" }
        ]
    },
    resolve: {
        extensions: ["", ".web.coffee", ".web.js", ".coffee", ".js"]
    }
};
```

とりあえずこれで！

app/index.jsに

```js
console.log("HOGE");
```

build/index.htmlに

```html
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="bundle.js"></script>
</head>
<body>
</body>
</html>
```


```bash
webpack-dev-server --hot
```

でlocalhost:8080にアクセス…見事HOGEがでました！！OKっぽいです！

# 1/4 15:58 dnd受け付け

http://www.html5rocks.com/ja/tutorials/file/dndfiles/
を読んでみます。ふむふむ。

もさっと組んでみます。API全然把握してなくて、DOM操作もままならないです……

```coffee
window.onload = ->
  target = document.getElementById 'target'

  target.ondragover = (ev)->
    ev.stopPropagation()
    ev.preventDefault()

  target.ondrop = (ev)->
    ev.stopPropagation()
    ev.preventDefault()

    file = ev.dataTransfer.files[0]
    if file?
      reader = new FileReader()
      reader.onload = (fileEvent)->
        img = new Image()
        img.src = fileEvent.target.result
        target.appendChild img

      reader.readAsDataURL file
```

index.htmlを以下のように書き換え。

```html
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="bundle.js"></script>
    <style>
        html,body,div{
            height: 100%;
            margin: 0;
        }
    </style>
</head>
<body>
<div id="target"></div>
</body>
</html>
```

動きました！！ではそろそろReact.jsにはいります！！

# 1/4 16:22 コンポーネントを考える
構成物を想像します。作りたい物を絵に描いて、操作を想像します。

* Workspace
* -Image
* -HitArea
* --Vertex

こんな階層構造になりそうですね。たぶん！

# 1/4 16:25 Workspaceの構築
数日前にreact-caffeineというマークアップラッパを作ったので、それをつかいます！

```coffee
#workspace.coffee
React = require 'react'
caffeine = require 'react-caffeine'

require './hit-area'
require './image'


caffeine.register
  Workspace:
    handleDragOver: (ev)->
      ev.stopPropagation()
      ev.preventDefault()
      
    handleDrop: (ev)->
      ev.stopPropagation()
      ev.preventDefault()
    
      file = ev.dataTransfer.files[0]
      if file?
        reader = new FileReader()
        reader.onload = (fileEvent)=>
          @setState src: fileEvent.target.result
        reader.readAsDataURL file

    getInitialState: ()->
      {src: null}
        
    render: ->
      caffeine @, ($)->
        @div
          onDrop: $.handleDrop
          onDragOver: $.handleDragOver
          style:
            width: @props.width
            height: @props.height
          , ->
            @Image src: $.state.src, width: @props.width, height: @props.height
            @HitArea hitArea for hitArea in @props.hitAreaList
```


```coffee
#main.coffee
React = require 'react'
art = require 'react-art'
caffeine = require 'react-caffeine'

require './workspace'

caffeine.registerClass art

hitArea = []

window.onload = ->
  target = document.getElementById 'target'


  React.render (caffeine ->
    @Workspace
      width: target.clientWidth
      height: target.clientHeight
      hitAreaList: hitArea
  ), target
```

```coffee
#image.coffee
React = require 'react'
art = require 'react-art'
caffeine = require 'react-caffeine'

caffeine.register
  Image:
    handleOnLoad: (ev)->
      img = @getDOMNode()
      cx = @props.width / 2
      cy = @props.height / 2
      dx = img.width / 2
      dy = img.height / 2
      @setState left: cx - dx, top: cy - dy
    
    getInitialState: ()->
      {left: 0, top: 0}
    
    render: ->
      caffeine @, ($)->
        @img
          onLoad: $.handleOnLoad
          src: @props.src
          style:
            position: 'absolute'
            left: $.state.left
            top: $.state.top
```

```coffee
#hit-area.coffee
React = require 'react'
art = require 'react-art'
caffeine = require 'react-caffeine'


caffeine.register
  HitArea:
    render: ->
      caffeine @, ($)->
        @div
```

これでdndしたイメージが中央に表示されるようになりました！！

# 1/4 17:05 操作を考える
どこでもいいので、クリックしたらライン作成をスタート、そして終点付近をクリックしたら、パスを閉じる、という感じにしましょう。

# 1/4 17:10 ライン描画の実装
hitAreaにポイントをプロットするLineToolクラスを作成です。

```coffee
#line-tool.coffee

CLOSE_DISTANCE = 10

class LineTool
  constructor: ()->
    @current = @newHitArea()
    
  clear: ()->
    @current = @newHitArea()
    
  newHitArea: ()->
    points: []
    closed: false
      
  addPoint: (origin, ev)->
    @current.points.push @computeDelta origin, ev
    
  modifyPoint: (origin, ev)->
    @current.points[@current.points.length-1] = @computeDelta origin, ev    

  computeDelta: (b, ev)->
    {x: ev.clientX - b.x, y: ev.clientY - b.y}

  nearStartPoint: (p)->
    if @current.points[2]?
      b = @current.points[0]
      dx = p.x - b.x
      dy = p.y - b.y
      dx*dx+dy*dy < CLOSE_DISTANCE*CLOSE_DISTANCE

  hitArea: ()->
    @current
        
  move: (origin, ev)->
    if !@current.closed and @current.points[0]?
      @modifyPoint origin, ev
      return true
    return false
  
  up: (origin, ev)->
    @modifyPoint origin, ev
    p = @computeDelta origin, ev
    if @nearStartPoint p
      @current.closed = true
      @current.points.pop()
    else
      @addPoint origin, ev

  down: (origin, ev)->
    if @current.points.length == 0
      @addPoint origin, ev
    

module.exports = LineTool
```

雑ですがだいたいこんな感じになりました！！

# 1/4 18:10 再開、レンダリング

ちょっとめんどうな所なのですが、イメージの大きさはImageComponentが計算しています。よってImageComponentからイメージの大きさを聞き出さないといけません。これにはreact.jsのrefsの機能を使います。

親コンポーネントが問い合わせて、子コンポーネントに伝播する仕組みです。

# 1/4 18:50 いったん完成！！
考慮外の操作をするとへんなことが起きますが、一応完成しました！ばんざい！！ここで一旦コミットして、付加機能を考えます！！

# 1/4 18:50 頂点の移動機能を考える

次は頂点の移動です。
頂点の移動処理自体はそこまで難しくはないでしょうが、問題は実装箇所です。Workspaceのハンドラを切り替えないといけません！ですが、現状どうなってるでしょうかね……

```coffee
#workspace.coffee
React = require 'react'
caffeine = require 'react-caffeine'

LineTool = require './line-tool'

require './hit-area'
require './image'


caffeine.register
  Workspace:
    handleDragOver: (ev)->
      ev.stopPropagation()
      ev.preventDefault()
      
    handleMouseUp: (ev)->
      tool = @state.tool
      tool.up @origin(), ev
      if tool.hitArea().closed
        tool.clear()
        @setState editing: false

    handleMouseDown: (ev)->
      tool = @state.tool
      tool.down @origin(), ev
     
      unless @state.editing
        @props.hitAreaList.push tool.hitArea()
        @setState editing: true
      @forceUpdate()

    handleMouseMove: (ev)->
      tool = @state.tool
      @forceUpdate() if tool.move @origin(), ev
    
    origin: ()->
      s = @refs.image.state
      {x: s.left, y: s.top}
      
    handleDrop: (ev)->
      ev.stopPropagation()
      ev.preventDefault()
    
      file = ev.dataTransfer.files[0]
      if file?
        reader = new FileReader()
        reader.onload = (fileEvent)=>
          @setState src: fileEvent.target.result
        reader.readAsDataURL file

    getInitialState: ()->
      {src: null, tool: new LineTool(), editing: false}
        
    render: ->
      tool = @state.tool
      caffeine @, ($)->
        @div
          onDrop: $.handleDrop
          onDragOver: $.handleDragOver
          onMouseDown: $.handleMouseDown
          onMouseUp: $.handleMouseUp
          onMouseMove: $.handleMouseMove
          style:
            width: @props.width
            height: @props.height
          , ->
            @Image
              src: $.state.src
              width: @props.width
              height: @props.height
              ref: 'image'

            @Surface
              style:
                position: 'absolute'
              width: @props.width
              height: @props.height
              , ->
                for hitArea in @props.hitAreaList
                  @HitArea
                    hitArea: hitArea
                    origin: $.origin()
```

わお！ハンドラ部の責務分割が微妙におかしいです！！
これだと切り替えるにもきりかえられません！！
まずはハンドラをリファクタリングですねー。

見てみると、editingというステートが悪さをしているようですので、これをToolの中に押し込めます。


```coffee
    handleMouseUp: (ev)->
      @forceUpdate() if @state.tool.up @origin(), ev

    handleMouseDown: (ev)->
      @forceUpdate() if @state.tool.down @origin(), ev

    handleMouseMove: (ev)->
      @forceUpdate() if @state.tool.move @origin(), ev
```

ハンドラ部はこうなりました、すっきりです！！ここで一旦コミットです。

# 1/4 19:06 頂点編集ツールの作成
では改めて頂点ツールを作りましょう！こんな感じでしょうか！！

```coffee
#vertex-tool.coffee

BaseTool = require './tool'

class VertexTool extends BaseTool
  constructor: (args)->
    @target = args.target
    @hitArea = args.hitArea
    
  modifyVertex: (origin, ev)->
    p = @computeDelta origin, ev
    @hitArea.points[@target] = p    
    
  up: (origin, ev)->
    @modifyVertex origin, ev
    true
    
  move: (origin, ev)->
    @modifyVertex origin, ev
    true
    
  down: ()->
    true
```


# 1/4 19:14 ツールの切り替え処理
さて、ツール切り替えのタイミングは、頂点でマウスが押されたタイミングです。これを知ってるのはHitAreaですね。
して復帰タイミングは、マウスが上がったタイミングです！これを知ってるのはWorkspaceです。
こまりました！いろんなところに散らばってます。
なので、Toolをまとめあげる、ToolBoxクラスを作成しましょう。

```coffee
Tools = 
  vertex: require './vertex-tool'
  line: require './line-tool'

class ToolBox
  constructor: ()->
    @toolStack = []
    @pushTool('line')
    
  isBusy: ()->
    @currentTool().isBusy()
    
  currentTool: ()->
    @toolStack[@toolStack.length-1]
    
  popTool: ()->
    @toolStack.pop()
  
  pushTool: (name, args)->
    args = args || {}
    args.toolBox = @
    console.log args
    @toolStack.push new Tools[name](args)
    
  setHitAreaList: (list)->
    tool.setHitAreaList?(list) for tool in @toolStack
    null
  
module.exports = ToolBox

```

これを使うように各々修正します。
一応想定された機能は作り終えました！

つくったものはここへー
https://github.com/necoco/react-sample-stage-editor

# 思った事

## Virtual DOM強い！！
強いですねー。DOM操作を差分でしなくていいのはとても楽です。

## Component間通信はpropsで行う
親から子はすんなりいきますが、子から親への通信は、propsに関数なりオブジェクトなりを渡して、コールバックを行う必要があります。

以上です！！今年初めはReact.js三昧でした！！