import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Pt(val x: Int, val y: Int)
Json.encodeToString(Pt(2, 3))
{"x":2,"y":3}
var pt: Pt? = null
notebook.commManager.registerCommTarget("t_clicker") { comm, openData ->
comm.onData<Pt> { msgPt ->
// Thread.sleep(3000)
pt = msgPt
}
}
HTML(
"""
<div style="background-color:#AAAA00; width:200px; height: 200px;" id="clicker"></div>
<div style="font-size:30px; margin-top:5px;" id="clickerOut"></div>
<script>
(function() {
const dOut = document.getElementById("clickerOut");
const d = document.getElementById("clicker");
const comm = Jupyter.notebook.kernel.comm_manager.new_comm("t_clicker", {});
d.addEventListener("click", (event) => {
dOut.innerHTML = "x = " + event.offsetX + ", y = " + event.offsetY;
comm.send({x: event.offsetX, y: event.offsetY});
});
})();
</script>
""".trimIndent()
)
pt
Pt(x=144, y=101)
interface Counter {
fun inc()
fun dec()
val value: Int
}
class Counter1(private var cnt: Int = 0): Counter {
override fun inc() { ++cnt }
override fun dec() { --cnt }
override val value: Int get() = cnt
}
USE {
render<Counter1> {
HTML("""
<button id="inc">+</button>
<span id="cntVal">${it.value}</span>
<button id="dec">-</button>
<script>
(function(){
function getVal() {
return parseInt(document.getElementById("cntVal").innerHTML);
}
function setVal(v) {
document.getElementById("cntVal").innerHTML = v + "";
}
const incButton = document.getElementById("inc");
const decButton = document.getElementById("dec");
incButton.addEventListener("click", (e) => {
setVal(getVal() + 1);
});
decButton.addEventListener("click", (e) => {
setVal(getVal() - 1);
});
})();
</script>
""".trimIndent())
}
}
val cnt1 = Counter1()
cnt1
cnt1.value
0
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
class CounterOpen(val id: String)
@Serializable
class CounterCommand(val command: String)
@Serializable
class CounterValueUpdate(val value: Int)
object CounterFactory {
private val idToCtr = mutableMapOf<String, CounterImpl>()
private val idToComm = mutableMapOf<String, Comm>()
fun create(): Counter {
val ctr = CounterImpl()
idToCtr[ctr.id] = ctr
return ctr
}
fun addComm(id: String, comm: Comm) {
idToComm[id] = comm
}
fun getCounter(id: String): Counter = idToCtr[id]!!
fun updateValue(id: String, value: Int) {
val comm = idToComm[id] ?: return
comm.sendData(CounterValueUpdate(value))
}
private class CounterImpl(
private var cnt: Int = 0,
val id: String = java.util.UUID.randomUUID().toString()
): Counter, Renderable {
override fun inc() {
++cnt
CounterFactory.updateValue(id, value)
}
override fun dec() {
--cnt
CounterFactory.updateValue(id, value)
}
override val value: Int get() = cnt
override fun render(notebook: Notebook): DisplayResult {
return HTML("""
<button id="inc" onclick="incSend('$id')">+</button>
<span class="cntVal$id">$value</span>
<button id="dec" onclick="decSend('$id')">-</button>
<script>
window.incSend = function(id) {
cntComms[id].send({command : "inc"});
};
window.decSend = function(id) {
cntComms[id].send({command : "dec"});
};
(function(){
const cntId = "$id";
const comm = Jupyter.notebook.kernel.comm_manager.new_comm("t_counter", { id: cntId });
window.cntComms = window.cntComms || {};
window.cntComms[cntId] = comm;
function setVal(id, v) {
const outs = document.getElementsByClassName("cntVal" + id);
const contents = v + "";
for (out of outs) {
out.innerHTML = contents;
}
}
comm.on_msg((msg) => {
const value = msg.content.data.value;
setVal(cntId, value);
});
})();
</script>
""".trimIndent())
}
}
}
notebook.commManager.registerCommTarget("t_counter") { comm, openData ->
val counterId = Json.decodeFromJsonElement<CounterOpen>(openData).id
CounterFactory.addComm(counterId, comm)
val counter = CounterFactory.getCounter(counterId)
comm.onData<CounterCommand> { d ->
val command = d.command
when(command) {
"inc" -> counter.inc()
"dec" -> counter.dec()
}
}
}
val ctr2 = CounterFactory.create()
ctr2
ctr2.value
6
ctr2.inc()
for (i in 1..10) {
ctr2.inc()
Thread.sleep(500)
}
val ctr3 = CounterFactory.create()
ctr3
ctr3
ctr3.inc()
import kotlinx.serialization.*
import kotlinx.serialization.json.*
interface WidgetConstrictorArg
interface WidgetState {
val json: JsonObject
}
interface WidgetCommand
interface Widget<S: WidgetState, C: WidgetCommand> {
val id: String
val state: S
var comm: Comm?
// call it after every kernel-side update
fun syncState() {
comm?.send(state.json)
}
fun updateState(command: C)
// Generally HTML code. May call JS function kotlinCommSend(target, id, command)
// `command` will be deserialized to type C
fun renderState(targetName: String): String
// arguments are `elem` and `state`. `elem` is a div container where all generated with renderState() is held.
fun setStateJs(): String
}
abstract class WidgetFactory<A: WidgetConstrictorArg, S: WidgetState, C: WidgetCommand, W: Widget<S, C>>(
private val targetName: String,
widgetClass: kotlin.reflect.KClass<W>,
notebook: Notebook
) {
protected abstract fun createImpl(args: A): W
protected abstract fun deserializeCommand(json: JsonObject): C
private val idToWidget = mutableMapOf<String, W>()
init {
notebook.commManager.registerCommTarget(targetName) { comm, openData ->
val widgetId = openData["id"]!!.jsonPrimitive.content
addComm(widgetId, comm)
val widget = getWidget(widgetId)
comm.onMessage { d ->
val command = deserializeCommand(d)
widget.updateState(command)
}
widget.syncState()
}
notebook.renderersProcessor.registerWithoutOptimizing(createRenderer(widgetClass) { w ->
HTML(
"""
<div class="widget_${targetName}_${w.id}">${w.renderState(targetName)}</div>
<script>
window.kotlinCommSend = function(target, wId, command) {
window.kotlinComms[target][wId].send(command);
};
window.setElementState = function(elem, state) {
${w.setStateJs()}
};
window.setState = function(target, id, data) {
const outs = document.getElementsByClassName("widget_" + target + "_" + id);
for (out of outs) {
setElementState(out, data);
}
};
(function(){
const initComm = function() {
const wId = "${w.id}";
const target = "$targetName";
const comm = Jupyter.notebook.kernel.comm_manager.new_comm(target, { id: wId });
window.kotlinComms = window.kotlinComms || {};
window.kotlinComms[target] = window.kotlinComms[target] || {};
window.kotlinComms[target][wId] = comm;
comm.on_msg((msg) => {
const d = msg.content.data;
setState(target, wId, d);
});
};
if (Jupyter.notebook.kernel != null) initComm();
else window.onload = initComm;
})();
</script>
""".trimIndent()
)
})
}
fun create(args: A): W {
return createImpl(args).also { w -> idToWidget[w.id] = w }
}
fun addComm(id: String, comm: Comm) {
val widget = idToWidget[id] ?: return
widget.comm = comm
}
fun getWidget(id: String): W = idToWidget[id]!!
}
class ListWidgetArgs(
val list: List<Int>,
val shownElementsLimit: Int,
): WidgetConstrictorArg
@Serializable
class ListWidgetState(
val d: String
) : WidgetState {
override val json get() = Json.encodeToJsonElement(this).jsonObject
}
@Serializable
class ListWidgetCommand : WidgetCommand
class ListWidget(
override val id: String,
val data: List<Int>,
private var shownElementsLimit: Int,
): Widget<ListWidgetState, ListWidgetCommand> {
override val state get() = ListWidgetState(data.take(minOf(shownElementsLimit, data.size)).toString())
override var comm: Comm? = null
override fun updateState(command: ListWidgetCommand) {
showMore()
}
fun showMore() {
shownElementsLimit += 10
syncState()
}
override fun renderState(targetName: String): String {
return """
<button onclick="kotlinCommSend('${targetName}', '${id}', {})">Show more</button>
<span class="listContent">${state.d}</span>
"""
}
override fun setStateJs(): String {
return """
const contentElem = elem.getElementsByClassName('listContent')[0];
contentElem.innerHTML = state.d;
"""
}
}
class ListWidgetFactory(notebook: Notebook): WidgetFactory<ListWidgetArgs, ListWidgetState, ListWidgetCommand, ListWidget>("t_list", ListWidget::class, notebook) {
override fun createImpl(args: ListWidgetArgs): ListWidget {
return ListWidget(java.util.UUID.randomUUID().toString(), args.list, args.shownElementsLimit)
}
override fun deserializeCommand(json: JsonObject): ListWidgetCommand {
return Json.decodeFromJsonElement<ListWidgetCommand>(json)
}
}
val listFactory = ListWidgetFactory(notebook)
val listWidget = listFactory.create(ListWidgetArgs((1..1000).toList(), 5))
listWidget
listWidget.showMore()
listWidget
class ListWidgetArgs(
val list: List<Int>,
val shownElementsLimit: Int,
): WidgetConstrictorArg
@Serializable
class ListWidgetState(
val d: String
) : WidgetState {
override val json get() = Json.encodeToJsonElement(this).jsonObject
}
@Serializable
class ListWidgetCommand : WidgetCommand
class ListWidget(
override val id: String,
val data: MutableList<Int>,
private var shownElementsLimit: Int,
): Widget<ListWidgetState, ListWidgetCommand> {
override val state get() = ListWidgetState(data.take(minOf(shownElementsLimit, data.size)).toString())
override var comm: Comm? = null
override fun updateState(command: ListWidgetCommand) {
showMore()
}
fun showMore() {
shownElementsLimit += 10
syncState()
}
operator fun set(i: Int, v: Int) {
data[i] = v
syncState()
}
override fun renderState(targetName: String): String {
return """
<button onclick="kotlinCommSend('${targetName}', '${id}', {})">Show more</button>
<span class="listContent">${state.d}</span>
"""
}
override fun setStateJs(): String {
return """
const contentElem = elem.getElementsByClassName('listContent')[0];
contentElem.innerHTML = state.d;
"""
}
}
class ListWidgetFactory(notebook: Notebook): WidgetFactory<ListWidgetArgs, ListWidgetState, ListWidgetCommand, ListWidget>("t_list2", ListWidget::class, notebook) {
override fun createImpl(args: ListWidgetArgs): ListWidget {
return ListWidget(java.util.UUID.randomUUID().toString(), args.list.toMutableList(), args.shownElementsLimit)
}
override fun deserializeCommand(json: JsonObject): ListWidgetCommand {
return Json.decodeFromJsonElement<ListWidgetCommand>(json)
}
}
val listFactory = ListWidgetFactory(notebook)
val listWidget = listFactory.create(ListWidgetArgs((1..1000).toList(), 5))
listWidget
listWidget[10] = 42