diff --git a/NAMESPACE b/NAMESPACE index 6daedb64..e5c7d9ec 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -27,4 +27,4 @@ importFrom(routr,RouteStack) importFrom(routr,ressource_route) importFrom(stats,setNames) importFrom(tools,file_ext) -importFrom(utils,getFromNamespace) \ No newline at end of file +importFrom(utils,getFromNamespace) diff --git a/R/dash.R b/R/dash.R index 2519133f..00ae0775 100644 --- a/R/dash.R +++ b/R/dash.R @@ -18,7 +18,8 @@ #' requests_pathname_prefix = NULL, #' external_scripts = NULL, #' external_stylesheets = NULL, -#' suppress_callback_exceptions = FALSE +#' suppress_callback_exceptions = FALSE, +#' show_undo_redo = FALSE #' ) #' #' @section Arguments: @@ -53,7 +54,9 @@ #' `external_stylesheets` \tab \tab List. An optional list of valid URLs from which #' to serve CSS for rendered pages.\cr #' `suppress_callback_exceptions` \tab \tab Logical. Whether to relay warnings about -#' possible layout mis-specifications when registering a callback. +#' possible layout mis-specifications when registering a callback.\cr +#' `show_undo_redo` \tab \tab Logical. Set to `TRUE` to enable undo and redo buttons for +#' stepping through the history of the app state. #' } #' #' @section Fields: @@ -130,6 +133,7 @@ #' \describe{ #' \item{path}{Character. A path string prefixed with a leading `/` which directs at a path or asset directory.} #' \item{requests_pathname_prefix}{Character. The pathname prefix for the app on a deployed application. Defaults to the environment variable set by the server, or `""` if run locally.} +#' } #' } #' \item{`strip_relative_path(path, requests_pathname_prefix)`}{ #' The `strip_relative_path` method simplifies the handling of URLs and pathnames for apps @@ -140,7 +144,8 @@ #' \describe{ #' \item{path}{Character. A path string prefixed with a leading `/` and `requests_pathname_prefix` which directs at a path or asset directory.} #' \item{requests_pathname_prefix}{Character. The pathname prefix for the app on a deployed application. Defaults to the environment variable set by the server, or `""` if run locally.} -#' } +#' } +#' } #' \item{`index_string(string)`}{ #' The `index_string` method allows the specification of a custom index by changing #' the default `HTML` template that is generated by the Dash UI. Meta tags, CSS, Javascript, @@ -271,7 +276,8 @@ Dash <- R6::R6Class( external_scripts = NULL, external_stylesheets = NULL, compress = TRUE, - suppress_callback_exceptions = FALSE) { + suppress_callback_exceptions = FALSE, + show_undo_redo = FALSE) { # argument type checking assertthat::assert_that(inherits(server, "Fire")) @@ -305,6 +311,7 @@ Dash <- R6::R6Class( self$config$requests_pathname_prefix <- resolvePrefix(requests_pathname_prefix, "DASH_REQUESTS_PATHNAME_PREFIX", url_base_pathname) self$config$external_scripts <- external_scripts self$config$external_stylesheets <- external_stylesheets + self$config$show_undo_redo <- show_undo_redo # ------------------------------------------------------------ # Initialize a route stack and register a static resource route @@ -352,8 +359,6 @@ Dash <- R6::R6Class( response$status <- 200L response$type <- 'json' - - TRUE }) @@ -560,27 +565,27 @@ Dash <- R6::R6Class( # if debug mode is not active dep_path <- system.file(dep_pkg$rpkg_path, package = dep_pkg$rpkg_name) - + response$type <- get_mimetype(filename) if (grepl("text|javascript", response$type)) { response$body <- readLines(dep_path, warn = FALSE, encoding = "UTF-8") - + if (private$compress && length(response$body) > 0) { response <- tryCompress(request, response) } } else { file_handle <- file(dep_path, "rb") file_size <- file.size(dep_path) - + response$body <- readBin(dep_path, raw(), file_size) close(file_handle) - } - + } + if (!private$debug && has_fingerprint) { response$status <- 200L response$set_header('Cache-Control', diff --git a/R/utils.R b/R/utils.R index f2f3d8fd..44c23492 100644 --- a/R/utils.R +++ b/R/utils.R @@ -506,7 +506,7 @@ get_package_mapping <- function(script_name, url_package, dependencies) { dep_path <- file.path(x$src$file, x$stylesheet) else if (!is.null(x$other)) dep_path <- file.path(x$src$file, x$other) - + # remove n>1 slashes and replace with / if present; # htmltools seems to permit // in pathnames, but # this complicates string matching unless they're @@ -542,7 +542,7 @@ get_mimetype <- function(filename) { else if (filename_ext %in% c('js.map', 'map')) return('application/json') else - return(mime::guess_type(filename, + return(mime::guess_type(filename, empty = "application/octet-stream")) } diff --git a/man/Dash.Rd b/man/Dash.Rd index f827b75b..e6c0d706 100644 --- a/man/Dash.Rd +++ b/man/Dash.Rd @@ -26,7 +26,8 @@ routes_pathname_prefix = NULL, requests_pathname_prefix = NULL, external_scripts = NULL, external_stylesheets = NULL, -suppress_callback_exceptions = FALSE +suppress_callback_exceptions = FALSE, +show_undo_redo = FALSE ) } @@ -63,7 +64,9 @@ to serve JavaScript source for rendered pages.\cr \code{external_stylesheets} \tab \tab List. An optional list of valid URLs from which to serve CSS for rendered pages.\cr \code{suppress_callback_exceptions} \tab \tab Logical. Whether to relay warnings about -possible layout mis-specifications when registering a callback. +possible layout mis-specifications when registering a callback.\cr +\code{show_undo_redo} \tab \tab Logical. Set to \code{TRUE} to enable undo and redo buttons for +stepping through the history of the app state. } } @@ -202,9 +205,9 @@ by assigning them to variables present in the template. This is similar to the \ but offers the ability to change the default components of the Dash index as seen in the example below: \preformatted{ app$interpolate_index( - template_index, - metas = "", - renderer = renderer, + template_index, + metas = "", + renderer = renderer, config = config) } \describe{ diff --git a/man/clientsideFunction.Rd b/man/clientsideFunction.Rd index 47d6cb80..771be8de 100644 --- a/man/clientsideFunction.Rd +++ b/man/clientsideFunction.Rd @@ -50,5 +50,5 @@ app$callback( "function (value) { return 'Client says \\"' + value + '\\"'; }" -)} +)} } diff --git a/tests/integration/test_render.py b/tests/integration/test_render.py new file mode 100644 index 00000000..07c0293b --- /dev/null +++ b/tests/integration/test_render.py @@ -0,0 +1,62 @@ +def click_undo(self): + undo_selector = "._dash-undo-redo span:first-child div:last-child" + undo = self.wait_for_element_by_css_selector(undo_selector) + self.wait_for_text_to_equal(undo_selector, "undo") + undo.click() + + +def click_redo(self): + redo_selector = "._dash-undo-redo span:last-child div:last-child" + self.wait_for_text_to_equal(redo_selector, "redo") + redo = self.wait_for_element_by_css_selector(redo_selector) + redo.click() + + +app = ''' +library(dash) +library(dashHtmlComponents) +library(dashCoreComponents) +app <- Dash$new(show_undo_redo=TRUE) + +app$layout(htmlDiv(list(dccInput(id="a"), htmlDiv(id="b")))) + +app$callback( + output("b", "children"), + list(input("a", "value")), + function(a) { + return(a) + } +) + +app$run_server() +''' + + +def test_rstr001r_undo_redo(dashr): + dashr.start_server(app) + dashr.wait_for_element_by_css_selector( + "#a" + ) + input1 = dashr.find_element("#a") + input1.send_keys("xyz") + dashr.wait_for_text_to_equal( + "#b", "xyz", timeout=1 + ) + click_undo(dashr) + dashr.wait_for_text_to_equal( + "#b", "xy", timeout=1 + ) + click_undo(dashr) + dashr.wait_for_text_to_equal( + "#b", "x", timeout=1 + ) + click_redo(dashr) + dashr.wait_for_text_to_equal( + "#b", "xy", timeout=1 + ) + dashr.percy_snapshot(name="undo-redo") + click_undo(dashr) + click_undo(dashr) + dashr.wait_for_text_to_equal( + "#b", "", timeout=1 + )