Skip to content

Instantly share code, notes, and snippets.

@andimiller
Created February 21, 2024 15:12
Show Gist options
  • Save andimiller/59084d0bb2ba1d705e1cc81917a6a10d to your computer and use it in GitHub Desktop.
Save andimiller/59084d0bb2ba1d705e1cc81917a6a10d to your computer and use it in GitHub Desktop.
package net.andimiller.examples.htmx
package chat
import cats.implicits._
import cats.effect._
import org.http4s.{FormDataDecoder, HttpRoutes}
import org.http4s.dsl.Http4sDsl
import HTML._
import org.http4s.FormDataDecoder.{field, formEntityDecoder}
import java.time.LocalDateTime
import scala.xml.Elem
case class Message(time: LocalDateTime, name: String, message: String)
class Routes[F[_]: Async](history: Ref[F, Vector[Message]]) extends Http4sDsl[F] {
def header(title: String) =
<head>
<title>{title}</title>
<script src="https://unpkg.com/[email protected]"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css" />
</head>
case class NameForm(name: String)
implicit val mapper: FormDataDecoder[NameForm] = field[String]("name").map(NameForm)
case class MessageForm(name: String, message: String)
implicit val msgMapper: FormDataDecoder[MessageForm] =
(
field[String]("name"),
field[String]("message")
).mapN(MessageForm)
def chatLog: F[Elem] = history.get.map { msgs =>
<table>
<tbody>
{
msgs.map { msg =>
<tr>
<td>{msg.time.toString}</td>
<td>{msg.name}</td>
<td>{msg.message}</td>
</tr>
}
}
</tbody>
</table>
}
def routes: HttpRoutes[F] = HttpRoutes.of[F] {
case GET -> Root =>
Ok(
<html>
{header("Chat Demo")}
<body>
<header>
<h1>Chat Demo</h1>
</header>
<main>
<div id="chatlog" hx-get="/chatlog" hx-trigger="every 1s">
</div>
<div id="input">
<input
name="name"
placeholder="Enter your name to chat"
hx-post="/name"
hx-target="#input"
></input>
</div>
</main>
</body>
</html>
)
case req @ POST -> Root / "name" =>
req.as[NameForm].flatMap { nameForm =>
Ok(
<form
hx-post="/message"
hx-target="#chatlog"
>
<fieldset role="group">
<input name="name" value={nameForm.name} readonly="">
</input>
<input
name="message"
type="text"
placeholder="Enter your message to chat"
hx-sync="closest"
hx-trigger="submit"
width="20%"
></input>
<button>Send</button>
</fieldset>
</form>
)
}
case req @ POST -> Root / "message" =>
req.as[MessageForm].flatMap { msgForm =>
history.update { prev =>
prev.appended(
Message(LocalDateTime.now(), msgForm.name, msgForm.message)
)
} *> chatLog.flatMap(Ok(_))
}
case GET -> Root / "chatlog" =>
chatLog.flatMap(Ok(_))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment