import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable data class Pt(val x: Int, val y: Int) Json.encodeToString(Pt(2, 3)) var pt: Pt? = null notebook.commManager.registerCommTarget("t_clicker") { comm, openData -> comm.onData { msgPt -> // Thread.sleep(3000) pt = msgPt } } HTML( """
""".trimIndent() ) pt 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 { HTML(""" ${it.value} """.trimIndent()) } } val cnt1 = Counter1() cnt1 cnt1.value 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() private val idToComm = mutableMapOf() 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(""" $value """.trimIndent()) } } } notebook.commManager.registerCommTarget("t_counter") { comm, openData -> val counterId = Json.decodeFromJsonElement(openData).id CounterFactory.addComm(counterId, comm) val counter = CounterFactory.getCounter(counterId) comm.onData { d -> val command = d.command when(command) { "inc" -> counter.inc() "dec" -> counter.dec() } } } val ctr2 = CounterFactory.create() ctr2 ctr2.value 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 { 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>( private val targetName: String, widgetClass: kotlin.reflect.KClass, notebook: Notebook ) { protected abstract fun createImpl(args: A): W protected abstract fun deserializeCommand(json: JsonObject): C private val idToWidget = mutableMapOf() 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( """
${w.renderState(targetName)}
""".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, 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, private var shownElementsLimit: Int, ): Widget { 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 """ ${state.d} """ } override fun setStateJs(): String { return """ const contentElem = elem.getElementsByClassName('listContent')[0]; contentElem.innerHTML = state.d; """ } } class ListWidgetFactory(notebook: Notebook): WidgetFactory("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(json) } } val listFactory = ListWidgetFactory(notebook) val listWidget = listFactory.create(ListWidgetArgs((1..1000).toList(), 5)) listWidget listWidget.showMore() listWidget class ListWidgetArgs( val list: List, 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, private var shownElementsLimit: Int, ): Widget { 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 """ ${state.d} """ } override fun setStateJs(): String { return """ const contentElem = elem.getElementsByClassName('listContent')[0]; contentElem.innerHTML = state.d; """ } } class ListWidgetFactory(notebook: Notebook): WidgetFactory("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(json) } } val listFactory = ListWidgetFactory(notebook) val listWidget = listFactory.create(ListWidgetArgs((1..1000).toList(), 5)) listWidget listWidget[10] = 42