lipre

Stream text files for live (coding) representations
Log | Files | Refs

commit e76843a741019a37a7af5c2034d836edb873d87c
Author: Vetle Haflan <vetle@haflan.dev>
Date:   Fri, 22 Jan 2021 23:17:58 +0100

Initial commit with proof of concept

Diffstat:
Aindex.html | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alipre.go | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 183 insertions(+), 0 deletions(-)

diff --git a/index.html b/index.html @@ -0,0 +1,85 @@ +<html> +<head> + <title>lipre</title> +</head> +<body> + <div id="roomEntry"> + <input id="roomCode"/> + <button onclick="present(getRoomCode())">Present</button> + <button onclick="view(getRoomCode())">View</button> + <div id="statusMessage"></div> + </div> + <textarea id="editor" style="display:none;height:100%; width:100%"></textarea> + <pre><code id="viewer" style="display:none;height:100%; width:100%; white-space: pre"></code></pre> + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script> + <script> + let getRoomCode = element => document.getElementById("roomCode").value + let setStatus = (message) => document.getElementById("statusMessage").innerText = message + let setCode = (message) => document.getElementById("viewer").innerText = message + let getCode = (message) => document.getElementById("editor").value + let hide = element => document.getElementById(element).style.display = "none" + let show = element => document.getElementById(element).style.display = "inline" + function connect(roomCode, present, openCallback) { + let url = "ws://" + document.location.host + (present ? "/pres/" : "/view/") + roomCode + window.ws = new WebSocket(url) + ws.onclose = () => { + setStatus("No connection to the given room") + } + ws.onopen = () => { + setStatus("Connected") + openCallback() + } + ws.onmessage = (msg) => { + setCode(msg.data) + hljs.highlightBlock(document.getElementById("viewer")) + } + } + function present(roomCode) { + connect(roomCode, true, () => { + hide("roomEntry") + hide("viewer") + show("editor") + }) + } + function view(roomCode) { + connect(roomCode, false, () => { + hide("roomEntry") + hide("editor") + show("viewer") + }) + } + // Allow direct connection with room code + let qParams = new URLSearchParams(window.location.search) + let roomCode = qParams.get("room") + let presentFlag = qParams.get("present") + if (roomCode) { + if (presentFlag) { + present(roomCode) + } else { + view(roomCode) + } + } + let editor = document.getElementById("editor") + editor.addEventListener("keyup", function(e) { + if (e.keyCode === 13) { + window.ws.send(getCode()) + } + }) + editor.addEventListener("keydown", function(e) { + if (e.keyCode === 9 || e.which === 9) { + console.log("arsiontarst") + e.preventDefault(); + var start = this.selectionStart; + var end = this.selectionEnd; + + // set textarea value to: text before caret + tab + text after caret + this.value = this.value.substring(0, start) + "\t" + this.value.substring(end); + + // put caret at right position again + this.selectionStart = this.selectionEnd = start + 1; + } + }) + + </script> +</body> +</html> diff --git a/lipre.go b/lipre.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/gorilla/websocket" +) + +const temporaryCorrectRoomCode = "tester" + +type Room struct { + code string + presenter *websocket.Conn + // TODO: + viewers []*websocket.Conn +} + +// TODO: Lock on this for concurrency? +var rooms = make(map[string]*Room) + +func (room *Room) listen() { + for { + _, message, err := room.presenter.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("error: %v", err) + } + delete(rooms, room.code) + return + } + for _, viewerConn := range room.viewers { + if viewerConn == nil { + break + } + viewerConn.WriteMessage(websocket.TextMessage, message) + } + } +} + +// HTTP +var wsUpgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +func indexHandler(w http.ResponseWriter, r *http.Request) { + htmlData, err := ioutil.ReadFile("index.html") + if err != nil { + panic(err) + } + w.Write(htmlData) + return +} + +func presentHandler(w http.ResponseWriter, r *http.Request) { + roomCode := mux.Vars(r)["roomCode"] + /*if roomCode != temporaryCorrectRoomCode { + w.WriteHeader(http.StatusBadRequest) + return + }*/ + fmt.Println("Upgrading connection") + conn, err := wsUpgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + room := &Room{code: roomCode, presenter: conn} + rooms[roomCode] = room + go room.listen() +} + +func viewHandler(w http.ResponseWriter, r *http.Request) { + roomCode := mux.Vars(r)["roomCode"] + room := rooms[roomCode] + if room == nil { + return + } + conn, err := wsUpgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + room.viewers = append(rooms[roomCode].viewers, conn) +} + +func main() { + fmt.Println("Server starting") + router := mux.NewRouter() + router.HandleFunc("/", indexHandler) + router.HandleFunc("/pres/{roomCode}", presentHandler) + router.HandleFunc("/view/{roomCode}", viewHandler) + http.Handle("/", router) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", 8080), nil)) +}