M app/makefile => app/makefile +3 -1
@@ 7,7 7,9 @@ clean:
rm -f src/*.lua
rm -rf node_modules
-dist/main.js: src/app.lua src/index.js
+dist/main.js: src/react-helpers.lua src/app.lua src/index.js
yarn && yarn run build
src/app.lua: src/app.fnl
modules/fennel/fennel --compile $^ > $@
+src/react-helpers.lua: src/react-helpers.fnl
+ modules/fennel/fennel --compile $^ > $@
M app/package.json => app/package.json +1 -1
@@ 5,7 5,7 @@
"author": "Benaiah Mischenko",
"license": "AGPL",
"scripts": {
- "build": "webpack --config webpack.config.js"
+ "build": "webpack --mode development --config webpack.config.js"
},
"devDependencies": {
"fengari-loader": "^0.0.1",
M app/src/app.fnl => app/src/app.fnl +74 -3
@@ 3,8 3,79 @@
(local React (require "react"))
(local ReactDOM (require "react-dom"))
+(local {:use-state use-state
+ :use-ref use-ref
+ :create-context create-context
+ :use-context use-context
+ :get-children-as-array get-children-as-array}
+ (require "./react-helpers.lua"))
+
+(component! LogLine [{:children message}]
+ (c! :div {:className :log-message} message))
+
+(component! Log [{:children maybe-children}]
+ (let [children (get-children-as-array maybe-children)]
+ (: children :map (fn [_ child i] (c! LogLine {:key i} child)))))
+
+(component! WebSocketDebugForm
+ [{:connect connect :disconnect disconnect :on-message on-message}]
+ (let [(current-message set-current-message) (use-state "")]
+ (c! [:div {}
+ [:form {:onSubmit (fn []
+ (on-message current-message)
+ (set-current-message ""))}
+ [:button {:type :button :onClick connect} :Connect]
+ [:button {:type :button :onClick disconnect} :Disconnect]
+ [:input {:id :text
+ :type :text
+ :value current-message
+ :onChange (fn [_ e] (set-current-message e.target.value))}]
+ [:button {:type :submit} :Send]]])))
+
+(fn create-websocket
+ [{:ws-url ws-url
+ :on-open onopen
+ :on-close onclose
+ :on-error onerror
+ :on-message onmessage}]
+ (let [ws (js.new js.global.WebSocket ws-url)]
+ (set ws.onopen onopen)
+ (set ws.onerror onerror)
+ (set ws.onmessage onmessage)
+ (set ws.onclose onclose)
+ ws))
+
+(local ws-url "ws://localhost:8090/wss")
+(component! App []
+ (let [(log-messages set-log-messages) (use-state (js! []))
+ (ws set-ws) (use-state nil)
+ log (fn [message]
+ (: js.global.console :log message)
+ (set-log-messages
+ (fn [_ prev-messages] (: prev-messages :concat message))))
+ ws-on-open (fn [] (log "connected"))
+ ws-on-error (fn [_ err] (log err))
+ ws-on-message (fn [_ e] (log (.. "recv: " e.data)))
+ ws-on-close (fn [] (log "disconnected") (set-ws nil))
+ connect
+ (fn []
+ (if ws (log "already connected")
+ (set-ws (create-websocket
+ {:ws-url ws-url
+ :on-open ws-on-open
+ :on-error ws-on-error
+ :on-message ws-on-message
+ :on-close ws-on-close}))))
+ disconnect (fn [] (if ws (log "already disconnected") (: ws :close)))
+ send-message (fn [message]
+ (if (not ws) (log "please connect first")
+ (log (.. "send: " message) (: ws :send message))))]
+ (c! [React.Fragment {}
+ [WebSocketDebugForm {:connect connect
+ :disconnect disconnect
+ :on-message send-message}]
+ [Log {} log-messages]])))
+
(: ReactDOM :render
- (c! [:h1 {} "Hello from React!"])
+ (c! App {})
(: js.global.document :getElementById :react-root))
-
-(print "Hello world!")
A app/src/react-helpers.fnl => app/src/react-helpers.fnl +26 -0
@@ 0,0 1,26 @@
+(local React (require "react"))
+
+(fn use-state [initial]
+ (let [val (React.useState nil initial)
+ state (. val 0)
+ set-state (. val 1)]
+ (values state (fn [new-state] (set-state nil new-state)))))
+
+(fn use-ref [initial] (React.useRef nil initial))
+
+(fn create-context [initial] (React.createContext nil initial))
+(fn use-context [context] (React.useContext nil context))
+
+(fn use-effect [effect dependencies]
+ (React.useEffect nil effect dependencies))
+
+(fn get-children-as-array [maybe-children]
+ (if (: js.global.Array :isArray maybe-children)
+ maybe-children
+ (js! [maybe-children])))
+
+{:use-state use-state
+ :use-ref use-ref
+ :create-context create-context
+ :use-context use-context
+ :get-children-as-array get-children-as-array}
M server/server.fnl => server/server.fnl +0 -9
@@ 1,8 1,6 @@
(global ngx ngx)
(local html (require :server.html))
-(local script "var ws = null; function connect() { if (ws !== null) return log('already connected'); ws = new WebSocket('ws://127.0.0.1:8090/wss/'); ws.onopen = function () { log('connected'); }; ws.onerror = function (error) { log(error); }; ws.onmessage = function (e) { log('recv: ' + e.data); }; ws.onclose = function () { log('disconnected'); ws = null; }; return false;}function disconnect() { if (ws === null) return log('already disconnected'); ws.close(); return false;}function send() { if (ws === null) return log('please connect first'); var text = document.getElementById('text').value; document.getElementById('text').value = ''; log('send: ' + text); ws.send(text); return false;}function log(text) { var li = document.createElement('li'); li.appendChild(document.createTextNode(text)); document.getElementById('log').appendChild(li); return false;}")
-
(local homepage
[:html {}
[:head {}
@@ 10,13 8,6 @@
[:script {} script]]
[:body {}
[:div {:id :react-root}]
- [:div {}
- [:form {:onSubmit "return send();"}
- [:button {:type :button :onClick "return connect();"} :Connect]
- [:button {:type :button :onClick "return disconnect();"} :Disconnect]
- [:input {:id :text :type :text}]
- [:button {:type :submit} :Send]]]
- [:ol {:id :log}]
[:script {:src "app.js"}]]])
(ngx.say (.. "<!doctype html>\n" (html homepage)))