go.tools present hack

Gree techtalk #6でGoのお話をしました。その際go tools presentを使っていたのですが
いろいろと用途に合わない所があったので１事例として。

一般的な解ではないので役に立たないかもしれませんが、参考までにどうぞ。

## 目的

- goのナイスなプレゼンツールを使って楽したい

## 背景

- コード書きながら資料作ってたのでそんなに時間なかった
- ニコ生での配信があるので映像配信側と同期したかった

## go.tools presentの課題

- 1) 文字サイズが小さい
- 2) レイアウトが細かく指定出来ない
- 3) websocketでの同期に対応していない

### 解決案

presentのコードを変更するぐらいしか方法がなかったのでひとまずそれで対応
これによりgo-talks.appspot.comでホストする方法が使えなくなってしまったがまぁ、仕方ない。

#### 問題1) 文字サイズが小さい

![difference.png](https://qiita-image-store.s3.amazonaws.com/0/24324/64907018-844b-2815-59da-248f43f97375.png)


社内向けのtalkをやった時に文字サイズが小さいという指摘があったので修正。

cmd/present/static/styles.cssのslide > articleをいじる

```
-  font-size: 26px;
-  line-height: 36px;
+  font-size: 38px;
+  line-height: 42px;
```

h3のサイズを変更する

```
-  font-size: 30px;
+  font-size: 40px;
```

今度は逆に文字サイズがでかくなりすぎて入らなくなったので文字サイズを小さくする書式を追加した。

#### 問題2) レイアウトが細かく指定出来ない

go presentは下記のような書式で画像ファイルを表示できるがレイアウトの指定ができない。

```
  .image file.png _ _
```

どうしょうもないのでalignを指定できるようにさくっと修正

cmd/present/templates/action.tmpl

```
   +  <img src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}} {{with .Align}} align="{{.}}"{{end}}>
```

```
diff -r ca10bbb8fab3 present/code.go
--- a/present/code.go   Tue Sep 16 16:24:52 2014 -0400
+++ b/present/code.go   Thu Sep 25 11:16:24 2014 +0900
@@ -238,6 +238,12 @@
                        res[i] = v
                case '$':
                        res[i] = "$"
+               case 'r':
+                       res[i] = "right"
+               case 'c':
+                       res[i] = "center"
+               case 'l':
+                       res[i] = "left"
                case '_':
                        if len(v) == 1 {
                                // Do nothing; "_" indicates an intentionally empty parameter.
diff -r ca10bbb8fab3 present/image.go
--- a/present/image.go  Tue Sep 16 16:24:52 2014 -0400
+++ b/present/image.go  Thu Sep 25 11:16:24 2014 +0900
@@ -17,6 +17,7 @@
        URL    string
        Width  int
        Height int
+       Align string
 }
 
 func (i Image) TemplateName() string { return "image" }
@@ -43,6 +44,16 @@
                if v, ok := a[1].(int); ok {
                        img.Width = v
                }
+       case 3:
+               if v, ok := a[0].(int); ok {
+                       img.Height = v
+               }
+               if v, ok := a[1].(int); ok {
+                       img.Width = v
+               }
+               if v, ok := a[2].(string); ok {
+                       img.Align = v
+               }
        default:
                return nil, fmt.Errorf("incorrect image invocation: %q", text)
        }
```


背景指定はいろいろ考えると面倒臭かったので1100x700の画像を用意してslide > articleに指定して背景にした。



### 問題3) websocketでの同期に対応していない

ニコ生でのスライド表示で手動でがんばるという話を聞いていたのでそれはかわいそうだと思いとりあえずつけた。
操作元のコネクションと操作される側のコネクションをどう判別しようか迷った挙句とりあえずquery parameterつけて判別させるようにした。

真面目にやるときはoauthとかで認証させたほうがいいけどもはやそこまでいくと別ツール

```
diff -r ca10bbb8fab3 cmd/present/local.go
--- a/cmd/present/local.go	Tue Sep 16 16:24:52 2014 -0400
+++ b/cmd/present/local.go	Thu Sep 25 11:19:26 2014 +0900
@@ -17,7 +17,11 @@
 	"os"
 	"runtime"
 	"strings"
+	//"time"
+//	"bytes"
+//	"io"
 
+	"code.google.com/p/go.net/websocket"
 	"code.google.com/p/go.tools/playground/socket"
 	"code.google.com/p/go.tools/present"
 )
@@ -91,6 +95,48 @@
 	}
 	http.Handle("/static/", http.FileServer(http.Dir(basePath)))
 
+	connections := make(map[int]*websocket.Conn)
+
+	http.Handle("/slave", websocket.Handler(func(ws *websocket.Conn) {
+		fmt.Printf("w: %s\n", ws.RemoteAddr().String())
+		id := len(connections)
+		connections[id] = ws
+
+		for {
+			var in []byte
+			if err := websocket.Message.Receive(ws, &in); err != nil {
+				delete(connections, id)
+				return
+			}
+
+			for _, con := range connections {
+				websocket.Message.Send(con, string(in))
+				fmt.Printf("Send: %s\n", string(in))
+			}
+		}
+	}))
+	http.Handle("/master", websocket.Handler(func(ws *websocket.Conn) {
+			key := ws.Request().URL.Query().Get("master")
+			if key != "%%PUT YOUR KEY HERE%%" {
+				fmt.Printf("master is not same: %s\n", key)
+				return
+			}
+
+			fmt.Printf("accept master connection\n")
+			for {
+				var in []byte
+				if err := websocket.Message.Receive(ws, &in); err != nil {
+					return
+				}
+
+				for _, con := range connections {
+					websocket.Message.Send(con, string(in))
+					fmt.Printf("Send: %s\n", string(in))
+				}
+			}
+		}))
+
+
 	if !ln.Addr().(*net.TCPAddr).IP.IsLoopback() &&
 		present.PlayEnabled && !*nativeClient {
 		log.Print(localhostWarning)
diff -r ca10bbb8fab3 cmd/present/static/slides.js
--- a/cmd/present/static/slides.js	Tue Sep 16 16:24:52 2014 -0400
+++ b/cmd/present/static/slides.js	Thu Sep 25 11:19:26 2014 +0900
@@ -9,6 +9,21 @@
 var PM_TOUCH_SENSITIVITY = 15;
 
 var curSlide;
+var masterws;
+var slavews;
+
+var urlParams;
+(window.onpopstate = function () {
+    var match,
+        pl     = /\+/g,  // Regex for replacing addition symbol with a space
+        search = /([^&=]+)=?([^&]*)/g,
+        decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+        query  = window.location.search.substring(1);
+
+    urlParams = {};
+    while (match = search.exec(query))
+        urlParams[decode(match[1])] = decode(match[2]);
+})();
 
 /* ---------------------------------------------------------------------- */
 /* classList polyfill by Eli Grey
@@ -204,6 +219,9 @@
   if (curSlide > 0) {
     curSlide--;
 
+    if (masterws) {
+      masterws.send("{\"page\": " + curSlide + "}")
+    }
     updateSlides();
   }
 };
@@ -212,10 +230,19 @@
   if (curSlide < slideEls.length - 1) {
     curSlide++;
 
+    if (masterws) {
+        masterws.send("{\"page\": " + curSlide + "}")
+    }
     updateSlides();
   }
 };
 
+function gotoSlide(slide) {
+    curSlide = slide;
+    updateSlides();
+};
+
+
 /* Slide events */
 
 function triggerEnterEvent(no) {
@@ -495,6 +522,21 @@
   } else {
     document.addEventListener('DOMContentLoaded', handleDomLoaded, false);
   }
+
+  // websocket slide control
+  if (urlParams["master"]) {
+      masterws = new WebSocket("ws://" + window.location.host +  "/master?master=" + urlParams["master"])
+  }
+
+  slavews = new WebSocket("ws://" + window.location.host +  "/slave")
+  slavews.onmessage = function(msg) {
+      d = JSON.parse(msg.data)
+      if (d) {
+          gotoSlide(d.page)
+      } else {
+          console.log("AREEE")
+      }
+  }
 }
 
 // If ?debug exists then load the script relative instead of absolute
```

js力が低いのはご愛嬌。これも前日に入れた

## その他

QoS関連のアニメーションも前日にカッとなって入れた。Flash + Create.js + iframe。
見て分かる通りアニメーション力は皆無だけどQoSがわかりやすかったんじゃなかったかと

http://techtalk.chobie.net/practicalgo.slide#36

とりあえずぽこぽこおいて動いてくれたので良かった。.htmlでの埋め込みもやってみたが
create.jsの吐き出すファイルをいじるが面倒だったのでiframeで逃げ。



あんまりhtml系のプレゼンツールの事前調査を行わずにやってしまったのでこんな感じだったが
パワポでポコポコやるよりかは考える事が少なくなったので編集がとても楽でした。

当日はawsに乗せてnaclとplaygroundを有効にした状態で配信。イベント終わってからはplaygroundは
無効にして自前のサーバーに載せ替えておしまい。


これでpdfで書き出しとかできれば素敵なんだけどなぁ


おしまい