From 0dbf9c90a6707f016a545a05a1c11fbfd679f045 Mon Sep 17 00:00:00 2001 From: Bubblegumdrop Date: Sun, 13 Oct 2024 00:31:11 -0400 Subject: [PATCH] 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 --- live-chat-main.lisp | 19 ++++++------ live-chat-ui.lisp | 83 +++++++++++++++++++++++++++++++---------------------- live-chat-ws.lisp | 2 ++ live-chat.asd | 1 + package.lisp | 1 - 5 files changed, 62 insertions(+), 44 deletions(-) diff --git a/live-chat-main.lisp b/live-chat-main.lisp index f60fde4..55ac1ce 100644 --- a/live-chat-main.lisp +++ b/live-chat-main.lisp @@ -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) diff --git a/live-chat-ui.lisp b/live-chat-ui.lisp index 9c8dc81..7ef4ef4 100644 --- a/live-chat-ui.lisp +++ b/live-chat-ui.lisp @@ -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)) diff --git a/live-chat-ws.lisp b/live-chat-ws.lisp index 1a45a67..08cdb6a 100644 --- a/live-chat-ws.lisp +++ b/live-chat-ws.lisp @@ -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 diff --git a/live-chat.asd b/live-chat.asd index bfeee04..df83ef6 100644 --- a/live-chat.asd +++ b/live-chat.asd @@ -15,6 +15,7 @@ #:clack #:lack #:ningle + #:slynk ;; WebSocket chat server backend #:websocket-driver) :components ((:file "package") diff --git a/package.lisp b/package.lisp index b5e651e..01057dc 100644 --- a/package.lisp +++ b/package.lisp @@ -63,4 +63,3 @@ #:live-chat-routes)) (in-package #:live-chat) -