diff --git a/DESCRIPTION b/DESCRIPTION
index e0df144..4785036 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -23,13 +23,17 @@ Depends:
R (>= 3.0.2)
Imports:
jsonlite (>= 0.9.16),
+ rlang,
rmarkdown
Suggests:
fontawesome (>= 0.3.0),
htmltools,
+ htmlwidgets,
knitr,
shiny (>= 1.5.0),
- testthat
+ shinytest2,
+ testthat,
+ withr
VignetteBuilder:
knitr
Config/roxygen2/version: 8.1.1
diff --git a/NAMESPACE b/NAMESPACE
index c4f8636..73cb3d6 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -6,4 +6,9 @@ export(reactlog_module_server)
export(reactlog_module_ui)
export(reactlog_render)
export(reactlog_show)
+export(reactlog_widget)
+export(reactlog_widget_module_server)
+export(reactlog_widget_module_ui)
+export(reactlog_widget_output)
export(reactlog_write)
+export(render_reactlog_widget)
diff --git a/NEWS.md b/NEWS.md
index 8996ec4..05f4605 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,7 @@
# reactlog (development)
+- Added `reactlog_widget()`, which shows the reactlog as an htmlwidget so it can be embedded in R Markdown or Quarto documents. Like `reactlog_show()`, `log` defaults to `shiny::reactlog()`. To show it in a Shiny app, use `reactlog_widget_module_ui()` and `reactlog_widget_module_server()`, or `reactlog_widget_output()` and `render_reactlog_widget()`. Requires the htmlwidgets package (#160)
+
- `reactlog_show()` now uses `shiny::reactlog()` as its default `log`, so `reactlog_show()` works with no arguments after running an app (#91)
- The graph legend now shows that a solid line is a dependency, a dashed line is an `isolate()`d dependency, and a dotted line is an inactive dependency (one that existed earlier but is not active at the current step) (#76)
diff --git a/R/renderReactLog.R b/R/renderReactLog.R
index 2502bf2..f19c043 100644
--- a/R/renderReactLog.R
+++ b/R/renderReactLog.R
@@ -11,31 +11,7 @@ inst_reactlog_assets <- function() {
#' @param session_token token to be used to subset which session is displayed.
#' Defaults to all sessions.
reactlog_render <- function(log, session_token = NULL, time = TRUE) {
- log <- reactlog_upgrade(log)
-
- template_file <- inst_reactlog_file("reactlog.html")
- # does not need utf8 support
- html <- paste(readLines(template_file, warn = FALSE), collapse = "\n")
-
- # get the json form of the lgo without writing to disk
- json_blob <- as.character(
- reactlog_write(log, file = NULL, session_token = session_token)
- )
-
- fixed_sub <- function(...) {
- sub(..., fixed = TRUE)
- }
-
- html <- fixed_sub(
- "__DATA__", paste(json_blob, collapse = "\n"),
- fixed_sub(
- "__TIME__", paste0("\"", time, "\""),
- fixed_sub(
- "", "",
- html
- )
- )
- )
+ html <- reactlog_page_html(log, session_token = session_token, time = time)
# check if shiny is even available
if (requireNamespace("shiny", quietly = TRUE)) {
@@ -60,3 +36,32 @@ reactlog_render <- function(log, session_token = NULL, time = TRUE) {
file
}
+
+fixed_sub <- function(...) {
+ sub(..., fixed = TRUE)
+}
+
+# The reactlog page as one string. Assets are still linked from `reactlogAsset/`.
+reactlog_page_html <- function(log, session_token = NULL, time = TRUE) {
+ log <- reactlog_upgrade(log)
+
+ template_file <- inst_reactlog_file("reactlog.html")
+ # does not need utf8 support
+ html <- paste(readLines(template_file, warn = FALSE), collapse = "\n")
+
+ # get the json form of the lgo without writing to disk
+ json_blob <- as.character(
+ reactlog_write(log, file = NULL, session_token = session_token)
+ )
+
+ fixed_sub(
+ "__DATA__", paste(json_blob, collapse = "\n"),
+ fixed_sub(
+ "__TIME__", paste0("\"", time, "\""),
+ fixed_sub(
+ "", "",
+ html
+ )
+ )
+ )
+}
diff --git a/R/shinyModule.R b/R/shinyModule.R
index bd58411..42b57fa 100644
--- a/R/shinyModule.R
+++ b/R/shinyModule.R
@@ -55,17 +55,20 @@
reactlog_module_ui <- function(include_refresh = TRUE, id = "reactlog_module") {
ns <- shiny::NS(id)
shiny::tagList(
- if (isTRUE(include_refresh))
- shiny::actionButton(
- ns("refresh"),
- "",
- icon = shiny::icon("arrows-rotate", lib = "font-awesome"),
- class = "btn-sm btn-warning"
- ),
+ if (isTRUE(include_refresh)) refresh_button(ns),
shiny::uiOutput(ns("iframe"))
)
}
+refresh_button <- function(ns) {
+ shiny::actionButton(
+ ns("refresh"),
+ "",
+ icon = shiny::icon("arrows-rotate", lib = "font-awesome"),
+ class = "btn-sm btn-warning"
+ )
+}
+
#' @rdname reactlog_module
#' @export
reactlog_module_server <- function(
@@ -146,3 +149,63 @@ assert_shiny_version <- function() {
stop("`shiny` v", shiny_version_required(), " or greater must be installed")
}
}
+
+
+#' Reactlog widget Shiny module
+#'
+#' Displays a [reactlog_widget()] in the given application, filtered to the
+#' current session. Unlike [reactlog_module_ui()], the log is sent through
+#' Shiny's websocket, so it does not need the `reactlog` URL that Shiny serves.
+#'
+#' The log is read when the widget renders. Click the refresh button to show
+#' newer entries. The first render runs before Shiny is idle for the first
+#' time, so it may not show a complete step until refreshed.
+#'
+#' @inheritParams reactlog_module_ui
+#' @param width,height Size of the widget. Must be a valid CSS unit or a number.
+#' @seealso [reactlog_widget_output()]
+#' @rdname reactlog_widget_module
+#' @export
+#' @examples
+#' if (interactive()) {
+#' library(shiny)
+#' reactlog_enable()
+#'
+#' ui <- fluidPage(
+#' numericInput("n", "n", 1),
+#' reactlog_widget_module_ui()
+#' )
+#' server <- function(input, output, session) {
+#' reactlog_widget_module_server()
+#' }
+#' shinyApp(ui, server)
+#' }
+reactlog_widget_module_ui <- function(
+ include_refresh = TRUE,
+ id = "reactlog_widget_module",
+ width = "100%",
+ height = "600px"
+) {
+ ns <- shiny::NS(id)
+ shiny::tagList(
+ if (isTRUE(include_refresh)) refresh_button(ns),
+ reactlog_widget_output(ns("widget"), width = width, height = height)
+ )
+}
+
+#' @rdname reactlog_widget_module
+#' @export
+reactlog_widget_module_server <- function(id = "reactlog_widget_module") {
+ assert_shiny_version()
+
+ shiny::moduleServer(
+ id,
+ function(input, output, session) {
+ output$widget <- render_reactlog_widget({
+ # trigger render refresh
+ input$refresh
+ reactlog_widget(session_token = session$token)
+ })
+ }
+ )
+}
diff --git a/R/widget.R b/R/widget.R
new file mode 100644
index 0000000..d8b9033
--- /dev/null
+++ b/R/widget.R
@@ -0,0 +1,143 @@
+#' Reactlog htmlwidget
+#'
+#' Displays the reactlog as an \pkg{htmlwidgets} widget, so it can be embedded
+#' in R Markdown or Quarto documents, or shown in the RStudio viewer.
+#'
+#' The widget is a self-contained page shown in an `