This repository has been archived by the owner on Mar 6, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Live.kt
104 lines (78 loc) 路 2.92 KB
/
Live.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package io.mnhrdt.plugins.live
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.*
import kotlinx.html.*
import kotlinx.html.stream.*
import kotlinx.serialization.*
class LiveViewScope private constructor(options: Options) {
internal var installed = false
val endpoint: String
init {
endpoint = options.endpoint
}
constructor(configure: Options.() -> Unit) : this(Options().apply(configure))
class Options {
var endpoint: String = "/live"
}
}
class LiveViewContext(val connected: Boolean, val parameters: Parameters)
@Serializable
data class LiveHello(val path: String, val parameters: Map<String, String>)
class LiveRouting(private val route: Route, private val scope: LiveViewScope) {
private val handlers = mutableMapOf<String, LiveViewContext.() -> LiveView>()
init {
if (!scope.installed) {
route.webSocket(scope.endpoint) {
val hello = receiveDeserialized<LiveHello>()
val init = handlers[hello.path]
checkNotNull(init) { "" }
val parameters = parametersOf(hello.parameters.mapValues { listOf(it.value) })
val context = LiveViewContext(true, parameters)
val view = init(context)
send(Frame.Text(view.render()))
suspend fun refresh() {
send(Frame.Text(view.render()))
}
val job = launch {
while (true) {
delay(1000L)
refresh()
}
}
// TODO: Send server-side events/react to server view state updates
try {
for (msg in incoming) {
val content = view.render()
send(Frame.Text(content))
}
} finally {
job.cancel("done")
}
}
scope.installed = true
}
}
fun view(path: String, init: LiveViewContext.() -> LiveView) {
route.get(path) {
val context = LiveViewContext(false, call.parameters)
val view = init(context)
val content = view.render()
val type = ContentType.Text.Html.withCharset(Charsets.UTF_8)
val ok = HttpStatusCode.OK
call.respond(TextContent(content, type, ok))
}
handlers[path] = init
}
}
fun Route.live(scope: LiveViewScope, block: LiveRouting.() -> Unit) = LiveRouting(this, scope).apply(block)
abstract class LiveView {
abstract fun render(): String
}
fun LiveView.html(block: HTML.() -> Unit): String =
buildString { append("<!DOCTYPE html>\n").appendHTML().html(block = block) }