├── .gitignore ├── README.md ├── project.clj ├── test └── joker │ └── test │ └── handler.clj ├── resources └── public │ └── app.js └── src └── joker ├── views.clj ├── chat.clj ├── handler.clj └── style.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | *sw? 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # joker live-coding 2 | 3 | How to start server 4 | 5 | ``` 6 | git clone https://github.com/niquola/joker-live-coding 7 | cd joker-live-coding 8 | lein repl 9 | 10 | > (require '[joker.handler :as jh]) 11 | > (jh/start) 12 | ``` 13 | 14 | ## License 15 | 16 | Copyright © 2014 nicola 17 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject joker "0.1.0-SNAPSHOT" 2 | :description "live coding joker conf 2014" 3 | :url "https://github.com/niquola/joker-live-coding" 4 | :min-lein-version "2.0.0" 5 | :dependencies [[org.clojure/clojure "1.6.0"] 6 | [http-kit "2.1.19"] 7 | [hiccup "1.0.5"] 8 | [garden "1.2.3"] 9 | [compojure "1.1.9"]] 10 | :profiles 11 | {:dev {:dependencies [[im.chit/vinyasa "0.2.2"] 12 | [javax.servlet/servlet-api "2.5"]]}}) 13 | -------------------------------------------------------------------------------- /test/joker/test/handler.clj: -------------------------------------------------------------------------------- 1 | (ns joker.test.handler 2 | (:require [clojure.test :refer :all] 3 | [joker.handler :refer :all] 4 | [ring.mock.request :as mock])) 5 | 6 | (deftest test-app 7 | (testing "main route" 8 | (let [response (app (mock/request :get "/"))] 9 | (is (= (:status response) 200)) 10 | (is (= (:body response) "Hello World")))) 11 | 12 | (testing "not-found route" 13 | (let [response (app (mock/request :get "/invalid"))] 14 | (is (= (:status response) 404))))) 15 | -------------------------------------------------------------------------------- /resources/public/app.js: -------------------------------------------------------------------------------- 1 | console.log('ups') 2 | 3 | var l = window.location 4 | var ws = new WebSocket('ws://'+l.host + '/chat' + (l.pathname == '/' ? '/anonimous' : l.pathname)) 5 | 6 | 7 | var byId = function(id){ 8 | return document.getElementById(id) 9 | } 10 | 11 | var inp = byId('inp') 12 | var out = byId('out') 13 | 14 | inp.onkeyup = function(e){ 15 | if(e.ctrlKey && e.keyCode == 13){ 16 | console.log('send', inp.value) 17 | ws.send(inp.value) 18 | } 19 | } 20 | ws.onmessage = function(e){ 21 | console.log(e.data) 22 | out.innerHTML += e.data 23 | } 24 | -------------------------------------------------------------------------------- /src/joker/views.clj: -------------------------------------------------------------------------------- 1 | (ns joker.views 2 | (:require 3 | [garden.core :as gc] 4 | [joker.style :as jc] 5 | [hiccup.core :as hc])) 6 | 7 | (defn style [g] 8 | [:style (gc/css g)]) 9 | 10 | (defn js [pth] 11 | [:script {:src pth}]) 12 | 13 | (defn layout [title & cnt] 14 | (hc/html 15 | [:html 16 | [:head 17 | [:title title] 18 | (style jc/main-style)] 19 | [:body 20 | [:div.container cnt] 21 | (js "/assets/app.js")]])) 22 | 23 | (defn index [data] 24 | (layout "index" 25 | [:div#out] 26 | [:textarea#inp])) 27 | 28 | -------------------------------------------------------------------------------- /src/joker/chat.clj: -------------------------------------------------------------------------------- 1 | (ns joker.chat 2 | (:require [org.httpkit.server :as ohs] 3 | [hiccup.core :as hc] 4 | [clojure.stacktrace :as cs])) 5 | 6 | (def users (atom {})) 7 | 8 | (defn fmt-message [user code res] 9 | (hc/html 10 | [:div.message 11 | [:b (:name user)] 12 | [:div.res 13 | [:pre 14 | code 15 | "\n----\n" 16 | res]]])) 17 | 18 | (defn my-eval [cd] 19 | (with-out-str 20 | (try (println (eval (read-string cd))) 21 | (catch Exception e 22 | (println (str e)) 23 | (println (cs/print-stack-trace e)))))) 24 | 25 | 26 | (defn add-usr [ch usr] 27 | (swap! users assoc ch usr)) 28 | 29 | (defn rm-usr [ch] 30 | (swap! users dissoc ch)) 31 | 32 | (defn b-cast [msg] 33 | (doseq [[ch u] @users] 34 | (ohs/send! ch msg))) 35 | 36 | (defn on-msg [ch msg] 37 | (let [u (get @users ch) 38 | res (my-eval msg)] 39 | (println "eval res" res) 40 | (b-cast (fmt-message u msg res)))) 41 | 42 | (comment 43 | (b-cast "Hi clients") 44 | (println @users)) 45 | -------------------------------------------------------------------------------- /src/joker/handler.clj: -------------------------------------------------------------------------------- 1 | (ns joker.handler 2 | (:require [compojure.core :refer :all] 3 | [compojure.handler :as handler] 4 | [org.httpkit.server :as ohs] 5 | [hiccup.core :as hc] 6 | [joker.views :as jv] 7 | [joker.chat :as jc] 8 | [compojure.route :as route])) 9 | 10 | 11 | (defn index [{params :params}] 12 | {:body (jv/index params) 13 | :status 200}) 14 | 15 | (defn chat [{params :params :as req}] 16 | (ohs/with-channel req ch 17 | (jc/add-usr ch params) 18 | (ohs/on-receive ch (fn [msg] (jc/on-msg ch msg))) 19 | (ohs/on-close ch (fn [st] (jc/rm-usr ch))))) 20 | 21 | (defroutes app-routes 22 | (GET "/" [] #'index) 23 | (GET "/:name" [] #'index) 24 | (GET "/chat/:name" [] #'chat) 25 | (route/resources "/assets/") 26 | (route/not-found "Not Found")) 27 | 28 | (def app 29 | (handler/site app-routes)) 30 | 31 | (defn start [] 32 | (def stop 33 | (ohs/run-server #'app {:port 8080}))) 34 | 35 | 36 | (comment 37 | (require '[vinyasa.pull :as vp]) 38 | (vp/pull 'http-kit) 39 | (vp/pull 'hiccup) 40 | (vp/pull 'garden)) 41 | -------------------------------------------------------------------------------- /src/joker/style.clj: -------------------------------------------------------------------------------- 1 | (ns joker.style) 2 | 3 | (defn em [x] (str x "em")) 4 | 5 | (defn box [nm] 6 | {:padded {:display "block" 7 | :padding (em 1)}}) 8 | 9 | (defn color [nm] 10 | (nm {:normal {:background "white" :color "#333"} 11 | :scale {:background "#333" :color "white"} 12 | :inverse {:background "black" :color "white"}})) 13 | 14 | (defn text [sz] 15 | (sz {:large {:font-size (em 1.5)} 16 | :normal {:font-size (em 1)} 17 | :small {:font-size (em 0.7)}})) 18 | 19 | (def textarea 20 | {:min-height (em 10) 21 | :width "100%" 22 | :display "block"}) 23 | 24 | (defn message [w] 25 | [:div.message 26 | [:b {:width (em w) 27 | :float "left"}] 28 | [:div.res {:margin-left (em w)} 29 | [:pre (merge (color :inverse) 30 | (box :padded) 31 | {:color "#ddd" 32 | :padding (em 1) 33 | :overflow "hidden"})]]]) 34 | 35 | (def main-style 36 | [:body 37 | (merge (box :padded) 38 | (color :scale)) 39 | (message 5) 40 | [:textarea#inp 41 | (merge textarea 42 | (color :inverse) 43 | (text :large))]]) 44 | 45 | (println main-style) 46 | --------------------------------------------------------------------------------