From 71faf22a9cfaaa8b2163ba9bb615b17d0152a88e Mon Sep 17 00:00:00 2001 From: Martin Elff Date: Sat, 14 Dec 2024 22:59:25 +0100 Subject: [PATCH] Use AnyWidget for resizeable widgets --- pkg/R/utils.R | 5 +++ pkg/R/widget-resizeable.R | 47 +++++++++++++-------------- pkg/inst/css/resizeable.css | 7 ++++ pkg/inst/js/resizeable.js | 64 +++++++++++++++++++++++++++++++++++++ pkg/man/AnyWidget.Rd | 3 +- pkg/man/ResizeableWidget.Rd | 28 ++++++---------- 6 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 pkg/inst/css/resizeable.css create mode 100644 pkg/inst/js/resizeable.js diff --git a/pkg/R/utils.R b/pkg/R/utils.R index 4a88912..678769b 100644 --- a/pkg/R/utils.R +++ b/pkg/R/utils.R @@ -258,3 +258,8 @@ menu_ <- function(choices,title=NULL,...) { install_menu <- function(){ replace_in_package("utils", "menu", menu_) } + +read_asset <- function(path) { + path <- system.file(path,package="RKernel") + paste(readLines(path),collapse="\n") +} \ No newline at end of file diff --git a/pkg/R/widget-resizeable.R b/pkg/R/widget-resizeable.R index dd4c490..d5d76e9 100644 --- a/pkg/R/widget-resizeable.R +++ b/pkg/R/widget-resizeable.R @@ -1,3 +1,8 @@ +# resizeable_css <- readLines(system.file("css/resizeable.css", +# package="RKernel")) +# resizeable_esm <- readLines(system.file("js/resizeable.js", +# package="RKernel")) + #' Resizeable Widgets #' @description R6 classes of resizeable containers for arbitrary HTML code #' @details Support for resizeable widgets, available from @@ -8,28 +13,24 @@ #' @export ResizeableWidgetClass <- R6Class_( "ResizeableWidget", - inherit = DOMWidgetClass, + inherit = AnyWidgetClass, public = list( - #' @field _view_name Name of the Javascript model view in the frontend - `_view_name` = structure(Unicode("ResizeableView"),sync=TRUE), - #' @field _model_name Name of the Javascript model in the frontend - `_model_name` = structure(Unicode("ResizeableModel"),sync=TRUE), - #' @field _view_module Name of the module where the view is defined - `_view_module` = structure(Unicode("resizeable-widget"),sync=TRUE), - #' @field _model_module Name of the Javascript module with the model - `_model_module` = structure(Unicode("resizeable-widget"),sync=TRUE), - #' @field _view_module_version Version of the module where the view is defined - `_view_module_version` = structure(Unicode("^0.1.0"),sync=TRUE), - #' @field _model_module_version Version of the module where the model is defined - `_model_module_version` = structure(Unicode("^0.1.0"),sync=TRUE), + #' @field _anywidget_id The AnyWidget id + `_anywidget_id` = structure(Unicode("ResizeableWidget"), sync = TRUE), + #' @field _css A Unicode trait with relevant CSS code + `_css` = structure(Unicode(read_asset("css/resizeable.css")), sync = TRUE), + #' @field _esm A Unicode trait with relevant Javascript code + `_esm` = structure(Unicode(read_asset("js/resizeable.js")), sync = TRUE), #' @field width (Current) width of the widget - width = structure(Integer(-1),sync = TRUE), + width = structure(Integer(-1), sync = TRUE), #' @field height (Current) height of the widget - height = structure(Integer(-1),sync = TRUE), + height = structure(Integer(-1), sync = TRUE), + #' @field direction Which direction(s) should the widget be resizeable + direction = structure(Unicode("both"), sync = TRUE), #' @field value (Current) HTML code content of the widget - value = structure(Unicode("Hello World!"),sync = TRUE), + value = structure(Unicode("Hello World!"), sync = TRUE), #' @field debug Whether to show a dashed frame and background color for debugging - debug = structure(Boolean(FALSE),sync = TRUE) + debug = structure(Boolean(FALSE), sync = TRUE) ) ) @@ -38,10 +39,8 @@ VResizeableWidgetClass <- R6Class_( "VResizeableWidget", inherit = ResizeableWidgetClass, public = list( - #' @field _view_name Name of the Javascript model view in the frontend - `_view_name` = structure(Unicode("VResizeableView"),sync=TRUE), - #' @field _model_name Name of the Javascript model in the frontend - `_model_name` = structure(Unicode("VResizeableModel"),sync=TRUE) + #' @field direction Which direction(s) should the widget be resizeable + direction = structure(Unicode("vertical"), sync = TRUE) ) ) @@ -50,10 +49,8 @@ HResizeableWidgetClass <- R6Class_( "HResizeableWidget", inherit = ResizeableWidgetClass, public = list( - #' @field _view_name Name of the Javascript model view in the frontend - `_view_name` = structure(Unicode("HResizeableView"),sync=TRUE), - #' @field _model_name Name of the Javascript model in the frontend - `_model_name` = structure(Unicode("HResizeableModel"),sync=TRUE) + #' @field direction Which direction(s) should the widget be resizeable + direction = structure(Unicode("horizontal"), sync = TRUE) ) ) diff --git a/pkg/inst/css/resizeable.css b/pkg/inst/css/resizeable.css new file mode 100644 index 0000000..ac3e78a --- /dev/null +++ b/pkg/inst/css/resizeable.css @@ -0,0 +1,7 @@ +.resizer { display:flex; margin:0; padding:0; resize:both; overflow:hidden } +.vresizer { display:flex; margin:0; padding:0; resize:vertical; overflow:hidden } +.hresizer { display:flex; margin:0; padding:0; resize:horizontal; overflow:hidden } +.resizer > .resized, +.hresizer > .resized, +.vresizer > .resized { flex-grow:1; margin:0; padding:0; border:0 } +.debug-resizer { background:rgb(115, 210, 170); border:4px dashed black; } diff --git a/pkg/inst/js/resizeable.js b/pkg/inst/js/resizeable.js new file mode 100644 index 0000000..802a909 --- /dev/null +++ b/pkg/inst/js/resizeable.js @@ -0,0 +1,64 @@ +function render({ model, el }) { + let _innerdiv = document.createElement('div'); + if (model.get('debug')) { + el.classList.add('debug-resizer'); + } + _innerdiv.classList.add('resized'); + _innerdiv.innerHTML = model.get('value'); + el.appendChild(_innerdiv); + let rob = new ResizeObserver((entries) => { + const width = parseInt(el.style.width); + const height = parseInt(el.style.height); + model.set('width', width); + model.set('height', height); + model.save_changes(); + }); + rob.observe(el); + model.on('change:value', () => { + _innerdiv.innerHTML = model.get('value'); + }); + model.on('change:debug', () => { + if (model.get('debug')) { + el.classList.add('debug-resizer'); + } else { + el.classList.remove('debug-resizer'); + } + }); + model.on('change:width', () => { + const width = model.get('width'); + const height = model.get('height'); + if (width > 0) { + el.style.width = `${width}px`; + } + if (height > 0) { + el.style.height = `${height}px`; + } + }); + model.on('change:height', () => { + const width = model.get('width'); + const height = model.get('height'); + if (width > 0) { + el.style.width = `${width}px`; + } + if (height > 0) { + el.style.height = `${height}px`; + } + }); + const direction = model.get('direction'); + if (direction == 'both') { + el.classList.add('resizer'); + } + if (direction == 'vertical') { + el.classList.add('vresizer'); + } + if (direction == 'horizontal') { + el.classList.add('hresizer'); + } + width = parseInt(el.style.width); + height = parseInt(el.style.height); + model.set('width', width); + model.set('height', height); + model.save_changes(); +} + +export default { render }; diff --git a/pkg/man/AnyWidget.Rd b/pkg/man/AnyWidget.Rd index b6f26c4..4c1b53d 100644 --- a/pkg/man/AnyWidget.Rd +++ b/pkg/man/AnyWidget.Rd @@ -5,7 +5,7 @@ \title{A constructor function for a class that inherits from 'AnyWidget' (or 'AnyWidgetClass')} \usage{ -AnyWidget(`_esm`, `_css`, ...) +AnyWidget(`_esm`, `_css`, `_anywidget_id`, ...) } \description{ A constructor function for a class that inherits from @@ -43,6 +43,7 @@ A constructor function for a class that inherits from CountWidget <- AnyWidget( `_esm` = esm, `_css` = css, + `_anywidget_id` = "CountWidget", value = Integer(0) ) CountWidget(value=42) diff --git a/pkg/man/ResizeableWidget.Rd b/pkg/man/ResizeableWidget.Rd index 9e6934c..553f37e 100644 --- a/pkg/man/ResizeableWidget.Rd +++ b/pkg/man/ResizeableWidget.Rd @@ -27,27 +27,23 @@ Support for resizeable widgets, available from } \keyword{internal} \section{Super classes}{ -\code{\link[RKernel:HasTraits]{RKernel::HasTraits}} -> \code{\link[RKernel:Widget]{RKernel::Widget}} -> \code{\link[RKernel:DOMWidget]{RKernel::DOMWidget}} -> \code{ResizeableWidget} +\code{\link[RKernel:HasTraits]{RKernel::HasTraits}} -> \code{\link[RKernel:Widget]{RKernel::Widget}} -> \code{\link[RKernel:DOMWidget]{RKernel::DOMWidget}} -> \code{\link[RKernel:AnyWidget]{RKernel::AnyWidget}} -> \code{ResizeableWidget} } \section{Public fields}{ \if{html}{\out{
}} \describe{ -\item{\code{_view_name}}{Name of the Javascript model view in the frontend} +\item{\code{_anywidget_id}}{The AnyWidget id} -\item{\code{_model_name}}{Name of the Javascript model in the frontend} +\item{\code{_css}}{A Unicode trait with relevant CSS code} -\item{\code{_view_module}}{Name of the module where the view is defined} - -\item{\code{_model_module}}{Name of the Javascript module with the model} - -\item{\code{_view_module_version}}{Version of the module where the view is defined} - -\item{\code{_model_module_version}}{Version of the module where the model is defined} +\item{\code{_esm}}{A Unicode trait with relevant Javascript code} \item{\code{width}}{(Current) width of the widget} \item{\code{height}}{(Current) height of the widget} +\item{\code{direction}}{Which direction(s) should the widget be resizeable} + \item{\code{value}}{(Current) HTML code content of the widget} \item{\code{debug}}{Whether to show a dashed frame and background color for debugging} @@ -111,14 +107,12 @@ The objects of this class are cloneable with this method. } } \section{Super classes}{ -\code{\link[RKernel:HasTraits]{RKernel::HasTraits}} -> \code{\link[RKernel:Widget]{RKernel::Widget}} -> \code{\link[RKernel:DOMWidget]{RKernel::DOMWidget}} -> \code{\link[RKernel:ResizeableWidget]{RKernel::ResizeableWidget}} -> \code{VResizeableWidget} +\code{\link[RKernel:HasTraits]{RKernel::HasTraits}} -> \code{\link[RKernel:Widget]{RKernel::Widget}} -> \code{\link[RKernel:DOMWidget]{RKernel::DOMWidget}} -> \code{\link[RKernel:AnyWidget]{RKernel::AnyWidget}} -> \code{\link[RKernel:ResizeableWidget]{RKernel::ResizeableWidget}} -> \code{VResizeableWidget} } \section{Public fields}{ \if{html}{\out{
}} \describe{ -\item{\code{_view_name}}{Name of the Javascript model view in the frontend} - -\item{\code{_model_name}}{Name of the Javascript model in the frontend} +\item{\code{direction}}{Which direction(s) should the widget be resizeable} } \if{html}{\out{
}} } @@ -179,14 +173,12 @@ The objects of this class are cloneable with this method. } } \section{Super classes}{ -\code{\link[RKernel:HasTraits]{RKernel::HasTraits}} -> \code{\link[RKernel:Widget]{RKernel::Widget}} -> \code{\link[RKernel:DOMWidget]{RKernel::DOMWidget}} -> \code{\link[RKernel:ResizeableWidget]{RKernel::ResizeableWidget}} -> \code{HResizeableWidget} +\code{\link[RKernel:HasTraits]{RKernel::HasTraits}} -> \code{\link[RKernel:Widget]{RKernel::Widget}} -> \code{\link[RKernel:DOMWidget]{RKernel::DOMWidget}} -> \code{\link[RKernel:AnyWidget]{RKernel::AnyWidget}} -> \code{\link[RKernel:ResizeableWidget]{RKernel::ResizeableWidget}} -> \code{HResizeableWidget} } \section{Public fields}{ \if{html}{\out{
}} \describe{ -\item{\code{_view_name}}{Name of the Javascript model view in the frontend} - -\item{\code{_model_name}}{Name of the Javascript model in the frontend} +\item{\code{direction}}{Which direction(s) should the widget be resizeable} } \if{html}{\out{
}} }