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
+ )