diff --git a/share/jupyter/kernels/xr/resources/call.R b/share/jupyter/kernels/xr/resources/call.R index 5600131..3769c5a 100644 --- a/share/jupyter/kernels/xr/resources/call.R +++ b/share/jupyter/kernels/xr/resources/call.R @@ -2,6 +2,6 @@ get(fn, envir = .xeus_env)(...) } -.xeus_new <- function(class, xp) { - get(class, envir = .xeus_env)$new(xp) +.xeus_new <- function(class, xp, ...) { + get(class, envir = .xeus_env)$new(xp, ...) } diff --git a/share/jupyter/kernels/xr/resources/comm.R b/share/jupyter/kernels/xr/resources/comm.R index 619bdc4..64eaa5a 100644 --- a/share/jupyter/kernels/xr/resources/comm.R +++ b/share/jupyter/kernels/xr/resources/comm.R @@ -1,76 +1,101 @@ .CommManager__register_target_callback <- function(comm, request) { - target_callback <- comm_target_env[[request$content$target_name]] - target_callback(comm, request) + callback <- CommManager$target_callback(request$content$target_name) + callback(comm, request) } CommManagerClass <- R6::R6Class("CommManagerClass", public = list( initialize = function() { - private$targets <- new.env() - private$comms <- new.env() + private$env_targets <- new.env() + private$env_comms <- new.env() }, - register_comm_target = function(target_name, callback) { - private$targets[[target_name]] <- callback + register_comm_target = function(target_name, callback = function(comm, message){}) { + private$env_targets[[target_name]] <- callback invisible(.Call("CommManager__register_target", target_name, PACKAGE = "(embedding)")) }, unregister_comm_target = function(target_name) { - rm(list = target_name, private$targets) + rm(list = target_name, private$env_targets) invisible(.Call("CommManager__unregister_target", target_name, PACKAGE = "(embedding)")) }, - new_comm = function(target_name) { - xp <- .Call("CommManager__new_comm", target_name, PACKAGE = "(embedding)") - if (is.null(xp)) { - stop(glue::glue("No target '{target_name}' registered")) - } - Comm$new(xp = xp) + new_comm = function(target_name, description = "") { + .Call("CommManager__new_comm", target_name, description, PACKAGE = "(embedding)") + }, + + comms = function() { + as.list(private$env_comms) + }, + + target_callback = function(target_name) { + private$env_targets[[target_name]] + }, + + preserve = function(comm) { + assign(comm$id, comm, envir = private$env_comms) + }, + + release = function(comm) { + rm(list = comm$id, envir = private$env_comms) } ), private = list( - targets = NULL, - comms = NULL + env_targets = NULL, + env_comms = NULL ) ) CommManager <- CommManagerClass$new() Comm <- R6::R6Class("Comm", public = list( - initialize = function(xp) { + initialize = function(xp, description = "") { private$xp <- xp + private$description <- description + CommManager$preserve(self) }, - open = function(metadata = NULL, data = NULL) { - js_metadata <- jsonlite::toJSON(metadata) - js_data <- jsonlite::toJSON(data) + open = function(data = NULL, metadata = NULL) { + js_metadata <- jsonlite::toJSON(metadata, auto_unbox = TRUE, null = if (is.null(metadata)) "list" else "null") + js_data <- jsonlite::toJSON(data, auto_unbox = TRUE, null = "null") invisible(.Call("Comm__open", private$xp, js_metadata, js_data, PACKAGE = "(embedding)")) }, - close = function(metadata = NULL, data = NULL) { - js_metadata <- jsonlite::toJSON(metadata) - js_data <- jsonlite::toJSON(data) + close = function(data = NULL, metadata = NULL) { + js_metadata <- jsonlite::toJSON(metadata, auto_unbox = TRUE, null = if (is.null(metadata)) "list" else "null") + js_data <- jsonlite::toJSON(data, auto_unbox = TRUE, null = "null") invisible(.Call("Comm__close", private$xp, js_metadata, js_data, PACKAGE = "(embedding)")) }, - send = function(metadata = NULL, data = NULL) { - js_metadata <- jsonlite::toJSON(metadata) - js_data <- jsonlite::toJSON(data) + send = function(data = NULL, metadata = NULL) { + js_metadata <- jsonlite::toJSON(metadata, auto_unbox = TRUE, null = if (is.null(metadata)) "list" else "null") + js_data <- jsonlite::toJSON(data, auto_unbox = TRUE, null = "null") invisible(.Call("Comm__send", private$xp, js_metadata, js_data, PACKAGE = "(embedding)")) }, on_close = function(handler) { - private$close_handler <- handler - invisible(.Call("Comm__on_close", private$xp, handler, PACKAGE = "(embedding)")) + private$close_handler <- function(request) { + handler(request) + self$finalize() + } + invisible(.Call("Comm__on_close", private$xp, private$close_handler, PACKAGE = "(embedding)")) }, on_message = function(handler) { private$message_handler <- handler - invisible(.Call("Comm__on_message", private$xp, handler, PACKAGE = "(embedding)")) + invisible(.Call("Comm__on_message", private$xp, private$message_handler, PACKAGE = "(embedding)")) + }, + + print = function() { + writeLines(glue("")) + }, + + finalize = function() { + CommManager$release(self) } ), @@ -86,6 +111,7 @@ Comm <- R6::R6Class("Comm", private = list( xp = NULL, + description = "", close_handler = NULL, message_handler = NULL ) diff --git a/share/jupyter/kernels/xr/resources/configure.R b/share/jupyter/kernels/xr/resources/configure.R index 6e3525c..6018a62 100644 --- a/share/jupyter/kernels/xr/resources/configure.R +++ b/share/jupyter/kernels/xr/resources/configure.R @@ -39,4 +39,5 @@ configure <- function() { # }) init_options() + init_widgets() } diff --git a/share/jupyter/kernels/xr/resources/execute.R b/share/jupyter/kernels/xr/resources/execute.R index b744cad..bcd940d 100644 --- a/share/jupyter/kernels/xr/resources/execute.R +++ b/share/jupyter/kernels/xr/resources/execute.R @@ -174,17 +174,27 @@ execute <- function(code, execution_counter, silent = FALSE) { if (isTRUE(last_visible)) { obj <- .Last.value - # TODO: This probably needs to be generalized mimetypes <- if (inherits(obj, c("htmlwidget", "shiny.tag.list", "shiny.tag"))) { c("text/plain", "text/html") } else { "text/plain" } - bundle <- IRdisplay::prepare_mimebundle(obj, mimetypes = mimetypes) + bundle <- IRdisplay::prepare_mimebundle(obj, mimetypes = c("text/plain", "text/html")) + + bundle <- if (inherits(obj, c("htmlwidget", "shiny.tag.list", "shiny.tag"))) { + IRdisplay::prepare_mimebundle(obj, mimetypes = c("text/plain", "text/html")) + } else if (inherits(obj, "jupyter.widget.Widget")) { + obj$mime_bundle() + } else { + IRdisplay::prepare_mimebundle(obj, mimetypes = c("text/plain")) + } structure(class = "execution_result", - list(toJSON(bundle$data), toJSON(bundle$metadata)) + list( + data = toJSON(bundle$data), + metadata = toJSON(bundle$metadata) + ) ) } diff --git a/share/jupyter/kernels/xr/resources/setup.R b/share/jupyter/kernels/xr/resources/setup.R index 9ca2c80..66c00b3 100644 --- a/share/jupyter/kernels/xr/resources/setup.R +++ b/share/jupyter/kernels/xr/resources/setup.R @@ -9,7 +9,7 @@ local({ "..", "share", "jupyter", "kernels", "xr", "resources" ) - files <- setdiff(list.files(here), "setup.R") + files <- setdiff(list.files(here, recursive = TRUE), "setup.R") for (f in files) { sys.source(file.path(here, f), envir = .xeus_env) diff --git a/share/jupyter/kernels/xr/resources/widgets/00_widgets.R b/share/jupyter/kernels/xr/resources/widgets/00_widgets.R new file mode 100644 index 0000000..19ac1b7 --- /dev/null +++ b/share/jupyter/kernels/xr/resources/widgets/00_widgets.R @@ -0,0 +1,31 @@ + +handler_jupyter.widget.control <- function(comm, message) { + + comm$on_message(function(request) { + data <- request$content$data + + switch(data$method, + "request_states" = { + comm$send( + data = list( + method = unbox("update_states"), + state = NULL + ) + ) + } + ) + }) +} + +handler_jupyter.widget <- function(comm, message) { + comm$on_message(function(request) { + + }) +} + +init_widgets <- function() { + # CommManager$register_comm_target("jupyter.widget.control", handler_jupyter.widget.control) + CommManager$register_comm_target("jupyter.widget", handler_jupyter.widget) +} + +Widget <- R6::R6Class("jupyter.widget.Widget") diff --git a/share/jupyter/kernels/xr/resources/widgets/01_layout.R b/share/jupyter/kernels/xr/resources/widgets/01_layout.R new file mode 100644 index 0000000..efb51ce --- /dev/null +++ b/share/jupyter/kernels/xr/resources/widgets/01_layout.R @@ -0,0 +1,82 @@ +Layout <- R6::R6Class("jupyter.widget.Layout", + public = list( + comm = NULL, + + initialize = function() { + comm <- CommManager$new_comm("jupyter.widget", "slider layout") + comm$on_message(function(request) { + + }) + comm$on_close(function(request) { + + }) + + comm$open( + data = list(state = private$state_, buffer_paths = list()), + metadata = list(version = "2.1.0") + ) + + self$comm <- comm + }, + + state = function(what) { + if (missing(what)) { + private$state_ + } else { + private$state_[[what]] + } + } + ), + + private = list( + + state_ = list( + "_model_module" = "@jupyter-widgets/base", + "_model_module_version" = "2.0.0", + "_model_name" = "LayoutModel", + "_view_count" = NULL, + "_view_module"= "@jupyter-widgets/base", + "_view_module_version" = "2.0.0", + "_view_name" = "LayoutView", + "align_content" = NULL, + "align_items" = NULL, + "align_self" = NULL, + "border_bottom" = NULL, + "border_left" = NULL, + "border_right" = NULL, + "border_top" = NULL, + "bottom" = NULL, + "display" = NULL, + "flex" = NULL, + "flex_flow" = NULL, + "grid_area" = NULL, + "grid_auto_columns" = NULL, + "grid_auto_flow" = NULL, + "grid_auto_rows" = NULL, + "grid_column" = NULL, + "grid_gap" = NULL, + "grid_row" = NULL, + "grid_template_areas" = NULL, + "grid_template_columns" = NULL, + "grid_template_rows" = NULL, + "height" = NULL, + "justify_content" = NULL, + "justify_items" = NULL, + "left" = NULL, + "margin" = NULL, + "max_height" = NULL, + "max_width" = NULL, + "min_height" = NULL, + "min_width" = NULL, + "object_fit" = NULL, + "object_position" = NULL, + "order" = NULL, + "overflow" = NULL, + "padding" = NULL, + "right" = NULL, + "top" = NULL, + "visibility" = NULL, + "width" = NULL + ) + ) +) diff --git a/share/jupyter/kernels/xr/resources/widgets/02_style.R b/share/jupyter/kernels/xr/resources/widgets/02_style.R new file mode 100644 index 0000000..b46e748 --- /dev/null +++ b/share/jupyter/kernels/xr/resources/widgets/02_style.R @@ -0,0 +1,24 @@ +Style <- R6::R6Class("jupyter.widget.Style", + public = list( + comm = NULL, + + initialize = function(description) { + comm <- CommManager$new_comm("jupyter.widget", description) + + comm$open( + data = list(state = private$state_, buffer_paths = list()), + metadata = list(version = "2.1.0") + ) + + self$comm <- comm + }, + + state = function(what) { + if (missing(what)) { + private$state_ + } else { + private$state_[[what]] + } + } + ) +) diff --git a/share/jupyter/kernels/xr/resources/widgets/03_model.R b/share/jupyter/kernels/xr/resources/widgets/03_model.R new file mode 100644 index 0000000..6d86c5f --- /dev/null +++ b/share/jupyter/kernels/xr/resources/widgets/03_model.R @@ -0,0 +1,82 @@ +Model <- R6::R6Class("jupyter.widget.Model", + public = list( + comm = NULL, + + initialize = function(layout, style, description = "model") { + comm <- CommManager$new_comm("jupyter.widget", description) + comm$on_message(function(request) { + data <- request$content$data + method <- data$method + + switch( + method, + update = { + state <- data$state + private$state_ <- replace(private$states_, names(state), state) + + if (!is.null(handler <- private$handlers[["update"]])) { + handler(state) + } + + comm$send( + data = list( + method = "echo_update", state = state, buffer_paths = list() + ) + ) + }, + + custom = { + content <- data$content + + if (!is.null(handler <- private$handlers[["custom"]])) { + handler(content) + } + } + ) + + }) + + comm$on_close(function(request) {}) + + private$state_$layout <- glue("IPY_MODEL_{layout$comm$id}") + private$state_$style <- glue("IPY_MODEL_{style$comm$id}") + + comm$open( + data = list(state = private$state_, buffer_paths = list()), + metadata = list(version = "2.1.0") + ) + self$comm <- comm + + private$handlers <- new.env() + }, + + state = function(what) { + if (missing(what)) { + private$state_ + } else { + private$state_[[what]] + } + }, + + on_update = function(handler = NULL) { + private$handlers[["update"]] <- handler + }, + + on_custom = function(handler = NULL) { + private$handlers[["custom"]] <- handler + }, + + update = function(...) { + state <- list(...) + self$comm$send( + data = list(method = "update", state = state, buffer_paths = list()) + ) + } + ), + + + private = list( + handlers = NULL + ) + +) \ No newline at end of file diff --git a/share/jupyter/kernels/xr/resources/widgets/Button.R b/share/jupyter/kernels/xr/resources/widgets/Button.R new file mode 100644 index 0000000..30289d4 --- /dev/null +++ b/share/jupyter/kernels/xr/resources/widgets/Button.R @@ -0,0 +1,108 @@ +ButtonStyle <- R6::R6Class("jupyter.widget.ButtonStyle", inherit = Style, + + public = list( + initialize = function() { + super$initialize("button style") + } + ), + + private = list( + state_ = list( + "_model_module" = "@jupyter-widgets/controls", + "_model_module_version" = "2.0.0", + "_model_name" = "ButtonStyleModel", + "_view_count" = NULL, + "_view_module" = "@jupyter-widgets/base", + "_view_module_version" = "2.0.0", + "_view_name" = "StyleView", + "button_color" = NULL, + "font_family" = NULL, + "font_size" = NULL, + "font_style" = NULL, + "font_variant" = NULL, + "font_weight" = NULL, + "text_color" = NULL, + "text_decoration" = NULL + ) + ) +) + +ButtonModel <- R6::R6Class("jupyter.widget.ButtonModel", inherit = Model, + public = list( + comm = NULL, + + initialize = function(layout, style) { + super$initialize(layout, style, "button model") + + self$on_custom(function(content) { + if (content$event == "click") { + click_handler <- private$handlers[["custom/click"]] + if (!is.null(click_handler)) { + click_handler() + } + } + }) + }, + + on_click = function(handler = NULL) { + private$handlers[["custom/click"]] <- handler + } + ), + + private = list( + state_ = list( + "_dom_classes" = list(), + "_model_module" = "@jupyter-widgets/controls", + "_model_module_version" = "2.0.0", + "_model_name" = "ButtonModel", + "_view_count" = NULL, + "_view_module" = "@jupyter-widgets/controls", + "_view_module_version" = "2.0.0", + "_view_name" = "ButtonView", + "button_style" = "", + "description" = "Click Me", + "disabled" = FALSE, + "icon" = "", + "layout" = "IPY_MODEL_{layout}", + "style" = "IPY_MODEL_{style}", + "tabbable" = NULL, + "tooltip" = NULL + ) + ) +) + +Button <- R6::R6Class("jupyter.widget.Button", inherit = Widget, + public = list( + layout = NULL, + style = NULL, + model = NULL, + + initialize = function() { + self$layout <- Layout$new() + self$style <- ButtonStyle$new() + self$model <- ButtonModel$new(self$layout, self$style) + }, + + mime_bundle = function() { + data <- list( + "text/plain" = unbox( + glue("