Progress commit
- Slynk server - Formatting output - wait-for-clack-handler - The WebSocket snippet from https://lispcookbook.github.io/cl-cookbook/websockets.html adatpted instead of hx-sse extension - Persistent bottom navbar - white-space: nowrap
This commit is contained in:
parent
8d3e31ef56
commit
0dbf9c90a6
@ -20,11 +20,12 @@
|
||||
;; Start the chat server
|
||||
(defun start-chat-server (&rest args &key (address "localhost") (port 8080) &allow-other-keys)
|
||||
"Start the Clack chat server."
|
||||
(slynk:create-server :port 4005 :dont-close t)
|
||||
(setf *handler*
|
||||
(apply #'clack:clackup *app*
|
||||
:port port :host address args))
|
||||
(when *handler*
|
||||
(format t "Chat server started on ~A port ~A~%" address port)))
|
||||
(format t ";; Chat server started on ~A port ~A~%" address port)))
|
||||
|
||||
(defun stop-chat-server (&rest args)
|
||||
(declare (ignore args))
|
||||
@ -33,20 +34,22 @@
|
||||
(clack:stop *handler*)
|
||||
(setf *handler* nil)))
|
||||
(unless *handler*
|
||||
(format t "Chat server stopped.~%")))
|
||||
(format t ";; Chat server stopped.~%")))
|
||||
|
||||
(defun restart-chat-server (&rest args)
|
||||
(apply #'stop-chat-server args)
|
||||
(apply #'start-chat-server args))
|
||||
|
||||
(defun main (&rest args &key (foreground nil) &allow-other-keys)
|
||||
(progn
|
||||
(setf (cl-who:html-mode) :html5)
|
||||
(create-messages-table)
|
||||
(apply #'restart-chat-server args)))
|
||||
(defun main (&rest args &key (foreground t) &allow-other-keys)
|
||||
(setf (cl-who:html-mode) :html5)
|
||||
(create-messages-table)
|
||||
(apply #'start-chat-server args)
|
||||
(print (bt:all-threads))
|
||||
(if foreground
|
||||
(wait-for-clack-handler "clack-handler-")))
|
||||
|
||||
;; https://stackoverflow.com/a/30424968
|
||||
(defun wait-for-hunchentoot-listener (name)
|
||||
(defun wait-for-clack-handler (name)
|
||||
(bt:join-thread
|
||||
(find-if
|
||||
(lambda (th)
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
(defun subpath-prefix (path)
|
||||
(format nil "~a~a" *default-prefix* path))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Render chat messages
|
||||
(defun render-chat-messages ()
|
||||
@ -20,7 +20,7 @@
|
||||
(loop for msg in messages
|
||||
do (cl-who:htm
|
||||
(:div :class "box"
|
||||
:style "overflow:auto;"
|
||||
:style "overflow: auto;"
|
||||
(cl-who:str msg)))))
|
||||
"")))
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"Render the main chat page with HTMX integration."
|
||||
(cl-who:with-html-output-to-string (*standard-output* nil :prologue t :indent t)
|
||||
(:html :lang "en"
|
||||
:class "has-navbar-fixed-bottom"
|
||||
(:head
|
||||
(:meta :charset "utf-8")
|
||||
(:meta :name "viewport" :content "width=device-width, initial-scale=1")
|
||||
@ -42,51 +43,63 @@
|
||||
:integrity "sha512-dQu3OKLMpRu85mW24LA1CUZG67BgLPR8Px3mcxmpdyijgl1UpCM1RtJoQP6h8UkufSnaHVRTUx98EQT9fcKohw=="
|
||||
:crossorigin "anonymous"
|
||||
:referrerpolicy "no-referrer")
|
||||
(:script :src "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.3/ext/ws.min.js"
|
||||
:integrity "sha512-1OIiXEswZd/etj60BUwFmyoi0OhrWdoYlzayJpSBivoMV0pQPIQr+vtAn3W3htsbWtLRU8DrBl0epdK4DQbj/w=="
|
||||
:crossorigin "anonymous"
|
||||
:referrerpolicy "no-referrer"))
|
||||
;; (:script :src "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.3/ext/ws.min.js"
|
||||
;; :integrity "sha512-1OIiXEswZd/etj60BUwFmyoi0OhrWdoYlzayJpSBivoMV0pQPIQr+vtAn3W3htsbWtLRU8DrBl0epdK4DQbj/w=="
|
||||
;; :crossorigin "anonymous"
|
||||
;; :referrerpolicy "no-referrer")
|
||||
)
|
||||
(:body
|
||||
(:section :class "section"
|
||||
(:div :class "container"
|
||||
(:h1 :class "title" "Live Chat")
|
||||
(:div :class "field has-addons"
|
||||
(:div :class "control is-expanded"
|
||||
(:input :class "input is-expanded"
|
||||
:id "chat-input"
|
||||
:autocomplete "off"
|
||||
:placeholder "Enter your message..."
|
||||
:type "text" :name "message"))
|
||||
(:div :class "control"
|
||||
(:button :class "button is-link is-light"
|
||||
:type "submit" "Send")))))
|
||||
(:section :class "section"
|
||||
(:div :class "container"
|
||||
:hx-ext "ws"
|
||||
:ws-connect (subpath-prefix "/ws-chat-messages")
|
||||
(:h3 :class "title is-3" "Chat Messages")
|
||||
;; :hx-ext "ws"
|
||||
;; :ws-connect (subpath-prefix "/ws-chat-messages")
|
||||
(:div :class "container"
|
||||
:id "chat-messages"
|
||||
:hx-get "/chat-messages"
|
||||
;; :hx-get "/chat-messages"
|
||||
(cl-who:str (render-chat-messages)))))
|
||||
(:script "
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
(:nav :role "navigation"
|
||||
:class "navbar is-fixed-bottom is-spaced has-shadow"
|
||||
(:div :class "navbar-menu is-active"
|
||||
:id "navbarBasicExample"
|
||||
(:div :class "navbar-start")
|
||||
(:div :class "container"
|
||||
(:div :class "field has-addons"
|
||||
(:div :class "control is-expanded"
|
||||
(:input :class "input is-expanded"
|
||||
:id "chat-input"
|
||||
:autocomplete "off"
|
||||
:placeholder "Enter your message..."
|
||||
:type "text" :name "message"))
|
||||
(:div :class "control"
|
||||
(:button :class "button is-link is-light"
|
||||
:type "submit" "Send"))))
|
||||
(:div :class "navbar-end")))
|
||||
(:script (cl-who:str (js-snippet (subpath-prefix "/ws-chat-messages"))))))))
|
||||
|
||||
function receivedMessage(msg) {
|
||||
document.querySelector('#chat-messages')
|
||||
.insertAdjacentHTML('afterbegin', msg.data);
|
||||
(defun js-snippet (path)
|
||||
(format nil
|
||||
"function receivedMessage(msg) {
|
||||
document.querySelector('#chat-messages').insertAdjacentHTML('afterbegin', msg.data);
|
||||
}
|
||||
|
||||
// TODO {{ server-name }}:{{ server-port }}
|
||||
const ws = new WebSocket('ws://' + window.location.host + '/ws-chat-messages');
|
||||
const ws = new WebSocket('wss://' + window.location.host + '~a');
|
||||
ws.addEventListener('message', receivedMessage);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const inputField = document.getElementById('chat-input');
|
||||
inputField.addEventListener('keyup', (evt) => {
|
||||
if (evt.key === 'Enter') {
|
||||
ws.send(evt.target.value);
|
||||
evt.target.value = '';
|
||||
}
|
||||
});")))))
|
||||
});
|
||||
// https://bulma.io/documentation/components/navbar/#navbar-menu
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach( el => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
});" path))
|
||||
|
@ -17,6 +17,7 @@
|
||||
(let ((message
|
||||
(cl-who:with-html-output-to-string (*standard-output*)
|
||||
(:div :class "box"
|
||||
:style "overflow: auto; white-space: nowrap;"
|
||||
(format t "~a: ~a" (gethash connection *connections*) message)))))
|
||||
(loop :for con :being :the :hash-key :of *connections* :do
|
||||
(send con message))))
|
||||
@ -25,6 +26,7 @@
|
||||
(let ((message
|
||||
(cl-who:with-html-output-to-string (*standard-output*)
|
||||
(:div :class "box"
|
||||
:style "overflow: auto; white-space: nowrap;"
|
||||
(format t "... ~a disconnected."
|
||||
(gethash connection *connections*))))))
|
||||
(loop :for con :being :the :hash-key :of *connections* :do
|
||||
|
@ -15,6 +15,7 @@
|
||||
#:clack
|
||||
#:lack
|
||||
#:ningle
|
||||
#:slynk
|
||||
;; WebSocket chat server backend
|
||||
#:websocket-driver)
|
||||
:components ((:file "package")
|
||||
|
@ -63,4 +63,3 @@
|
||||
#:live-chat-routes))
|
||||
|
||||
(in-package #:live-chat)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user