mirror of
https://github.com/phfroidmont/scalive.git
synced 2025-12-25 05:26:59 +01:00
Support live navigation
This commit is contained in:
parent
0b067aa7e1
commit
d42472061b
5 changed files with 63 additions and 30 deletions
|
|
@ -8,6 +8,10 @@ package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys:
|
|||
|
||||
lazy val defer = htmlAttr("defer", codecs.BooleanAsAttrPresenceCodec)
|
||||
|
||||
object link:
|
||||
def navigate(path: String, mods: Mod*): HtmlElement =
|
||||
a(href := path, phx.link := "redirect", phx.linkState := "push", mods)
|
||||
|
||||
object phx:
|
||||
private def phxAttr(suffix: String): HtmlAttr[String] =
|
||||
new HtmlAttr(s"phx-$suffix", StringAsIsCodec)
|
||||
|
|
@ -18,6 +22,8 @@ package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys:
|
|||
|
||||
private[scalive] lazy val session = dataPhxAttr("session")
|
||||
private[scalive] lazy val main = htmlAttr("data-phx-main", BooleanAsAttrPresenceCodec)
|
||||
private[scalive] lazy val link = dataPhxAttr("link")
|
||||
private[scalive] lazy val linkState = dataPhxAttr("link-state")
|
||||
lazy val click = phxAttrJson("click")
|
||||
def value(key: String) = phxAttr(s"value-$key")
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ final case class WebSocketMessage(
|
|||
eventType: String,
|
||||
payload: WebSocketMessage.Payload):
|
||||
val meta = WebSocketMessage.Meta(joinRef, messageRef, topic, eventType)
|
||||
def okReply =
|
||||
WebSocketMessage(
|
||||
joinRef,
|
||||
messageRef,
|
||||
topic,
|
||||
"phx_reply",
|
||||
Payload.Reply("ok", LiveResponse.Empty)
|
||||
)
|
||||
object WebSocketMessage:
|
||||
|
||||
final case class Meta(
|
||||
|
|
@ -33,6 +41,8 @@ object WebSocketMessage:
|
|||
val payloadParsed = eventType match
|
||||
case "heartbeat" => Right(Payload.Heartbeat)
|
||||
case "phx_join" => payload.as[Payload.Join]
|
||||
case "phx_leave" => Right(Payload.Leave)
|
||||
case "phx_close" => Right(Payload.Close)
|
||||
case "event" => payload.as[Payload.Event]
|
||||
case s => Left(s"Unknown event type : $s")
|
||||
|
||||
|
|
@ -56,6 +66,8 @@ object WebSocketMessage:
|
|||
m.payload match
|
||||
case Payload.Heartbeat => Json.Obj.empty
|
||||
case p: Payload.Join => p.toJsonAST.getOrElse(throw new IllegalArgumentException())
|
||||
case Payload.Leave => Json.Obj.empty
|
||||
case Payload.Close => Json.Obj.empty
|
||||
case p: Payload.Reply => p.toJsonAST.getOrElse(throw new IllegalArgumentException())
|
||||
case p: Payload.Event => p.toJsonAST.getOrElse(throw new IllegalArgumentException())
|
||||
case p: Payload.Diff => p.toJsonAST.getOrElse(throw new IllegalArgumentException())
|
||||
|
|
@ -65,11 +77,14 @@ object WebSocketMessage:
|
|||
enum Payload:
|
||||
case Heartbeat
|
||||
case Join(
|
||||
url: String,
|
||||
url: Option[String],
|
||||
redirect: Option[String],
|
||||
// params: Map[String, String],
|
||||
session: String,
|
||||
static: Option[String],
|
||||
sticky: Boolean)
|
||||
case Leave
|
||||
case Close
|
||||
case Reply(status: String, response: LiveResponse)
|
||||
case Diff(diff: scalive.Diff)
|
||||
case Event(`type`: Payload.EventType, event: String, value: Map[String, String])
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ class HomeLiveView() extends LiveView[String, Unit]:
|
|||
cls := "space-y-2",
|
||||
links.map((path, name) =>
|
||||
li(
|
||||
a(
|
||||
href := path,
|
||||
link.navigate(
|
||||
path,
|
||||
cls := "block px-4 py-2 rounded-lg text-gray-700 hover:bg-gray-100 hover:text-gray-900 font-medium transition",
|
||||
name
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package scalive
|
||||
|
||||
import scalive.WebSocketMessage.LiveResponse
|
||||
import scalive.WebSocketMessage.Meta
|
||||
import scalive.WebSocketMessage.Payload
|
||||
import zio.*
|
||||
|
|
@ -75,6 +74,18 @@ class LiveChannel(private val sockets: SubscriptionRef[Map[String, Socket[?, ?]]
|
|||
.map(m.updated(id, _))
|
||||
}.flatMap(_ => ZIO.logDebug(s"LiveView joined $id"))
|
||||
|
||||
def leave(id: String): UIO[Unit] =
|
||||
sockets.updateZIO { m =>
|
||||
m.get(id) match
|
||||
case Some(socket) =>
|
||||
for
|
||||
_ <- socket.shutdown
|
||||
_ <- ZIO.logDebug(s"Left LiveView $id")
|
||||
yield m.removed(id)
|
||||
case None =>
|
||||
ZIO.logWarning(s"Tried to leave LiveView $id which doesn't exist").as(m)
|
||||
}
|
||||
|
||||
def event(id: String, value: String, meta: WebSocketMessage.Meta): UIO[Unit] =
|
||||
sockets.get.flatMap { m =>
|
||||
m.get(id) match
|
||||
|
|
@ -109,13 +120,16 @@ class LiveRouter(rootLayout: HtmlElement => HtmlElement, liveRoutes: List[LiveRo
|
|||
Read(
|
||||
WebSocketFrame.text(
|
||||
WebSocketMessage(
|
||||
meta.joinRef,
|
||||
meta.messageRef,
|
||||
meta.topic,
|
||||
payload match
|
||||
joinRef = meta.joinRef,
|
||||
messageRef = payload match
|
||||
case Payload.Close => meta.joinRef
|
||||
case _ => meta.messageRef,
|
||||
topic = meta.topic,
|
||||
eventType = payload match
|
||||
case Payload.Diff(_) => "diff"
|
||||
case Payload.Close => "phx_close"
|
||||
case _ => "phx_reply",
|
||||
payload
|
||||
payload = payload
|
||||
).toJson
|
||||
)
|
||||
)
|
||||
|
|
@ -146,21 +160,10 @@ class LiveRouter(rootLayout: HtmlElement => HtmlElement, liveRoutes: List[LiveRo
|
|||
private def handleMessage(message: WebSocketMessage, liveChannel: LiveChannel)
|
||||
: RIO[Scope, Option[WebSocketMessage]] =
|
||||
message.payload match
|
||||
case Payload.Heartbeat =>
|
||||
ZIO.succeed(
|
||||
Some(
|
||||
WebSocketMessage(
|
||||
message.joinRef,
|
||||
message.messageRef,
|
||||
message.topic,
|
||||
"phx_reply",
|
||||
Payload.Reply("ok", LiveResponse.Empty)
|
||||
)
|
||||
)
|
||||
)
|
||||
case Payload.Join(url, session, static, sticky) =>
|
||||
case Payload.Heartbeat => ZIO.succeed(Some(message.okReply))
|
||||
case Payload.Join(url, redirect, session, static, sticky) =>
|
||||
ZIO
|
||||
.fromEither(URL.decode(url)).flatMap(url =>
|
||||
.fromEither(URL.decode(url.orElse(redirect).getOrElse(???))).flatMap(url =>
|
||||
val req = Request(url = url)
|
||||
liveRoutes.iterator
|
||||
.map(route =>
|
||||
|
|
@ -169,7 +172,7 @@ class LiveRouter(rootLayout: HtmlElement => HtmlElement, liveRoutes: List[LiveRo
|
|||
.toOption
|
||||
.map(route.liveviewBuilder(_, req))
|
||||
.map(
|
||||
ZIO.logDebug(s"Joining live view ${route.path.toString} ${message.topic}") *>
|
||||
ZIO.logDebug(s"Joining LiveView ${route.path.toString} ${message.topic}") *>
|
||||
liveChannel.join(message.topic, session, _, message.meta)(
|
||||
using route.messageCodec
|
||||
)
|
||||
|
|
@ -178,12 +181,17 @@ class LiveRouter(rootLayout: HtmlElement => HtmlElement, liveRoutes: List[LiveRo
|
|||
.collectFirst { case Some(join) => join.map(_ => None) }
|
||||
.getOrElse(ZIO.succeed(None))
|
||||
)
|
||||
case Payload.Leave =>
|
||||
liveChannel
|
||||
.leave(message.topic)
|
||||
.as(Some(message.okReply))
|
||||
case Payload.Event(_, event, _) =>
|
||||
liveChannel
|
||||
.event(message.topic, event, message.meta)
|
||||
.map(_ => None)
|
||||
case Payload.Reply(_, _) => ZIO.die(new IllegalArgumentException())
|
||||
case Payload.Diff(_) => ZIO.die(new IllegalArgumentException())
|
||||
case Payload.Close => ZIO.die(new IllegalArgumentException())
|
||||
end match
|
||||
end handleMessage
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ object Socket:
|
|||
yield ()
|
||||
}.fork
|
||||
stop =
|
||||
inbox.shutdown *> outHub.shutdown *> clientFiber.interrupt.unit *> serverFiber.interrupt.unit
|
||||
outHub.publish(Payload.Close -> meta) *>
|
||||
inbox.shutdown *>
|
||||
outHub.shutdown *>
|
||||
clientFiber.interrupt.unit *>
|
||||
serverFiber.interrupt.unit
|
||||
outbox =
|
||||
ZStream.succeed(
|
||||
Payload.okReply(LiveResponse.InitDiff(initDiff)) -> meta
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue