diff --git a/DESCRIPTION b/DESCRIPTION index aee91796e..549fc08f8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,6 +22,7 @@ URL: https://github.com/insightsengineering/teal.reporter, https://insightsengineering.github.io/teal.reporter/ BugReports: https://github.com/insightsengineering/teal.reporter/issues Imports: + bsicons, bslib (>= 0.8.0), checkmate (>= 2.1.0), flextable (>= 0.9.2), @@ -35,8 +36,10 @@ Imports: rtables (>= 0.6.11), rtables.officer (>= 0.0.2), shiny (>= 1.6.0), + shinyjs, shinybusy (>= 0.3.2), shinyWidgets (>= 0.5.1), + sortable (>= 0.5.0), yaml (>= 1.1.0), zip (>= 1.1.0) Suggests: diff --git a/NAMESPACE b/NAMESPACE index 23f0e3b5e..067f228cd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,8 @@ export(add_card_button_ui) export(as_yaml_auto) export(download_report_button_srv) export(download_report_button_ui) +export(preview_report_button_srv) +export(preview_report_button_ui) export(report_load_srv) export(report_load_ui) export(reporter_previewer_srv) diff --git a/NEWS.md b/NEWS.md index 1c89b416b..9b9719e7e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,11 +2,18 @@ # teal.reporter 0.4.0 +### Breaking changes + +* The functions `reporter_previewer_ui()` and `reporter_previewer_srv()` are deprecated and will be removed in a future release. Please migrate to using the underlying shiny modules independently: + - **UI**: `report_load_ui()`, `download_report_button_ui()`, `reset_report_button_ui()`, and `preview_report_button_ui()` + - **Server**: `report_load_srv()`, `download_report_button_srv()`, `reset_report_button_srv()`, and `preview_report_button_srv()` + ### Enhancements * Reports can now be reloaded. A zip file containing the report can be uploaded to restore the state of the Previewer. * Report cards are now included in bookmarks. When using the `shiny` bookmarking mechanism, existing report cards will be available in the restored application. * HTML content can now be added to the report. +* Added `preview_report_button_ui` and `preview_report_button_srv` to create a shiny module that creates a button to open the report previewer in a modal. # teal.reporter 0.3.1 diff --git a/R/AddCardModule.R b/R/AddCardModule.R index 362060230..a15e5f04d 100644 --- a/R/AddCardModule.R +++ b/R/AddCardModule.R @@ -34,6 +34,7 @@ #' #' @param id (`character(1)`) this `shiny` module's id. #' @param reporter (`Reporter`) instance. +#' @param label (`character(1)`) label of the button. By default it is empty. #' @param card_fun (`function`) which returns a [`ReportCard`] instance. See `Details`. #' #' @return `NULL`. @@ -41,41 +42,12 @@ NULL #' @rdname add_card_button #' @export -add_card_button_ui <- function(id) { - ns <- shiny::NS(id) - - # Buttons with custom css and - # js code to disable the add card button when clicked to prevent multi-clicks - shiny::tagList( - shiny::singleton( - shiny::tags$head(shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter"))) - ), - shiny::singleton( - shiny::tags$head( - shiny::tags$script( - shiny::HTML( - sprintf( - ' - $(document).ready(function(event) { - $("body").on("click", "#%s", function() { - $(this).addClass("disabled"); - }) - })', - ns("add_card_ok") - ) - ) - ) - ) - ), - shiny::actionButton( - ns("add_report_card_button"), - title = "Add Card", - class = "teal-reporter simple_report_button btn-primary", - `data-val` = shiny::restoreInput(id = ns("add_report_card_button"), default = NULL), - shiny::tags$span( - shiny::icon("plus") - ) - ) +add_card_button_ui <- function(id, label = NULL) { + checkmate::assert_string(label, null.ok = TRUE) + .outline_button( + shiny::NS(id, "add_report_card_button"), + icon = "plus-lg", + label = label ) } @@ -97,7 +69,8 @@ add_card_button_srv <- function(id, reporter, card_fun) { add_modal <- function() { shiny::div( - class = "teal-widgets reporter-modal", + class = "teal-reporter reporter-modal", + .custom_css_dependency(), shiny::modalDialog( easyClose = TRUE, shiny::tags$h3("Add a Card to the Report"), @@ -131,17 +104,15 @@ add_card_button_srv <- function(id, reporter, card_fun) { footer = shiny::div( shiny::tags$button( type = "button", - class = "btn btn-secondary", - `data-dismiss` = "modal", + class = "btn btn-outline-secondary", `data-bs-dismiss` = "modal", NULL, - "Cancel" + "Dismiss" ), shiny::tags$button( id = ns("add_card_ok"), type = "button", class = "btn btn-primary action-button", - `data-val` = shiny::restoreInput(id = ns("add_card_ok"), default = NULL), NULL, "Add Card" ) diff --git a/R/DownloadModule.R b/R/DownloadModule.R index 4b304da40..03708730c 100644 --- a/R/DownloadModule.R +++ b/R/DownloadModule.R @@ -12,6 +12,7 @@ #' #' @param id (`character(1)`) this `shiny` module's id. #' @param reporter (`Reporter`) instance. +#' @param label (`character(1)`) label of the button. By default it is empty. #' @param global_knitr (`list`) of `knitr` parameters (passed to `knitr::opts_chunk$set`) #' for customizing the rendering process. #' @inheritParams reporter_download_inputs @@ -21,21 +22,12 @@ NULL #' @rdname download_report_button #' @export -download_report_button_ui <- function(id) { - ns <- shiny::NS(id) - shiny::tagList( - shiny::singleton( - shiny::tags$head(shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter"))) - ), - shiny::actionButton( - ns("download_button"), - class = "teal-reporter simple_report_button btn-primary", - title = "Download", - `data-val` = shiny::restoreInput(id = ns("download_button"), default = NULL), - shiny::tags$span( - shiny::icon("download") - ) - ) +download_report_button_ui <- function(id, label = NULL) { + checkmate::assert_string(label, null.ok = TRUE) + .outline_button( + shiny::NS(id, "download_button"), + label = label, + icon = "download" ) } @@ -44,15 +36,8 @@ download_report_button_ui <- function(id) { download_report_button_srv <- function(id, reporter, global_knitr = getOption("teal.reporter.global_knitr"), - rmd_output = c( - "html" = "html_document", "pdf" = "pdf_document", - "powerpoint" = "powerpoint_presentation", "word" = "word_document" - ), - rmd_yaml_args = list( - author = "NEST", title = "Report", - date = as.character(Sys.Date()), output = "html_document", - toc = FALSE - )) { + rmd_output = getOption("teal.reporter.rmd_output"), + rmd_yaml_args = getOption("teal.reporter.rmd_yaml_args")) { checkmate::assert_class(reporter, "Reporter") checkmate::assert_subset(names(global_knitr), names(knitr::opts_chunk$get())) checkmate::assert_subset( @@ -78,37 +63,37 @@ download_report_button_srv <- function(id, download_modal <- function() { nr_cards <- length(reporter$get_cards()) - downb <- shiny::tags$a( - id = ns("download_data"), - class = paste("btn btn-primary shiny-download-link", if (nr_cards) NULL else "disabled"), - style = if (nr_cards) NULL else "pointer-events: none;", - href = "", - target = "_blank", - download = NA, - shiny::icon("download"), - "Download" + downb <- shiny::downloadButton( + outputId = ns("download_data"), + label = "Download", + class = c( + "btn", "teal-reporter", "download-ok", "btn-primary", "shiny-download-link", + if (nr_cards == 0) "disabled" + ), + icon = shiny::icon("download") ) shiny::tags$div( - class = "teal-widgets reporter-modal", + class = "teal-reporter reporter-modal", + .custom_css_dependency(), shiny::modalDialog( easyClose = TRUE, shiny::tags$h3("Download the Report"), shiny::tags$hr(), if (length(reporter$get_cards()) == 0) { shiny::tags$div( - class = "mb-4", shiny::tags$p( class = "text-danger", shiny::tags$strong("No Cards Added") - ) + ), + shiny::tags$br() ) } else { shiny::tags$div( - class = "mb-4", shiny::tags$p( class = "text-success", shiny::tags$strong(paste("Number of cards: ", nr_cards)) ), + shiny::tags$br() ) }, reporter_download_inputs( @@ -120,11 +105,10 @@ download_report_button_srv <- function(id, footer = shiny::tagList( shiny::tags$button( type = "button", - class = "btn btn-secondary", - `data-dismiss` = "modal", + class = "btn btn-outline-secondary", `data-bs-dismiss` = "modal", NULL, - "Cancel" + "Dismiss" ), downb ) @@ -136,6 +120,12 @@ download_report_button_srv <- function(id, shiny::showModal(download_modal()) }) + shiny::observeEvent(reporter$get_reactive_add_card(), { + shinyjs::toggleClass( + id = "download_button", condition = reporter$get_reactive_add_card() == 0, class = "disabled" + ) + }) + output$download_data <- shiny::downloadHandler( filename = function() { paste0( diff --git a/R/LoadReporterModule.R b/R/LoadReporterModule.R index c272003e6..3b0b92811 100644 --- a/R/LoadReporterModule.R +++ b/R/LoadReporterModule.R @@ -1,38 +1,32 @@ -#' User Interface to Load `Reporter` +#' Load `Reporter` button module +#' #' @description `r lifecycle::badge("experimental")` -#' Button to upload `ReporterCard`(s) to the `Reporter`. #' -#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' Provides a button to upload `ReporterCard`(s) to the `Reporter`. +#' +#' For more information, refer to the vignette: `vignette("simpleReporter", "teal.reporter")`. +#' +#' @name load_report_button +#' #' @param id `character(1)` this `shiny` module's id. +#' @param label (`character(1)`) label of the button. By default it is empty. +#' @param reporter [`Reporter`] instance. +NULL + +#' @rdname load_report_button #' @return `shiny::tagList` #' @export -report_load_ui <- function(id) { - ns <- shiny::NS(id) - - shiny::tagList( - shiny::singleton( - shiny::tags$head(shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter"))) - ), - shiny::actionButton( - ns("reporter_load"), - class = "teal-reporter simple_report_button btn-primary", - title = "Load", - shiny::tags$span( - shiny::icon("upload") - ) - ) +report_load_ui <- function(id, label = NULL) { + checkmate::assert_string(label, null.ok = TRUE) + .outline_button( + shiny::NS(id, "reporter_load"), + label = label, + icon = "upload" ) } -#' Server to Load `Reporter` -#' @description `r lifecycle::badge("experimental")` -#' Server to load `ReporterCard`(s) to the `Reporter` -#' -#' For more details see the vignette: `vignette("simpleReporter", "teal.reporter")`. -#' -#' @param id `character(1)` this `shiny` module's id. -#' @param reporter [`Reporter`] instance. -#' + +#' @rdname load_report_button #' @return `shiny::moduleServer` #' @export report_load_srv <- function(id, reporter) { @@ -47,7 +41,8 @@ report_load_srv <- function(id, reporter) { archiver_modal <- function() { nr_cards <- length(reporter$get_cards()) shiny::div( - class = "teal-widgets reporter-modal", + class = "teal-reporter reporter-modal", + .custom_css_dependency(), shiny::modalDialog( easyClose = TRUE, shiny::tags$h3("Load the Report"), @@ -59,24 +54,29 @@ report_load_srv <- function(id, reporter) { footer = shiny::div( shiny::tags$button( type = "button", - class = "btn btn-danger", - `data-dismiss` = "modal", + class = "btn btn-outline-secondary", `data-bs-dismiss` = "modal", NULL, - "Cancel" + "Dismiss" ), - shiny::tags$button( - id = ns("reporter_load_main"), - type = "button", - class = "btn btn-primary action-button", - NULL, - "Load" + shinyjs::disabled( + shiny::tags$button( + id = ns("reporter_load_main"), + type = "button", + class = "btn btn-primary action-button", + NULL, + "Load" + ) ) ) ) ) } + shiny::observeEvent(input$archiver_zip, { + shinyjs::enable(id = "reporter_load_main") + }) + shiny::observeEvent(input$reporter_load, { shiny::showModal(archiver_modal()) }) diff --git a/R/Previewer.R b/R/Previewer.R index e03d212d3..ec2fa2633 100644 --- a/R/Previewer.R +++ b/R/Previewer.R @@ -1,7 +1,24 @@ -#' Report previewer module +#' Show report previewer button module #' #' @description `r lifecycle::badge("experimental")` #' +#' Provides a button that triggers showing the report preview in a modal. +#' +#' For more details see the vignette: `vignette("previewerReporter", "teal.reporter")`. +#' +#' @name reporter_previewer +#' +#' @param id (`character(1)`) `shiny` module instance id. +#' @param label (`character(1)`) label of the button. By default it is "Preview Report". +#' @param reporter (`Reporter`) instance. +#' +#' @return `NULL`. +NULL + +#' Report previewer module +#' +#' @description `r lifecycle::badge("deprecated")` +#' #' Module offers functionalities to visualize, manipulate, #' and interact with report cards that have been added to a report. #' It includes a previewer interface to see the cards and options to modify the report before downloading. @@ -10,9 +27,13 @@ #' #' For more details see the vignette: `vignette("previewerReporter", "teal.reporter")`. #' +#' This function is deprecated and will be removed in the next release. +#' Please use `preview_report_button_ui()` and `preview_report_button_srv()` +#' to create a preview button that opens a modal with the report preview. +#' #' @details `r global_knitr_details()` #' -#' @name reporter_previewer +#' @name reporter_previewer_deprecated #' #' @param id (`character(1)`) `shiny` module instance id. #' @param reporter (`Reporter`) instance. @@ -28,54 +49,124 @@ NULL #' @rdname reporter_previewer #' @export -reporter_previewer_ui <- function(id) { +preview_report_button_ui <- function(id, label = "Preview Report") { + checkmate::assert_string(label, null.ok = TRUE) ns <- shiny::NS(id) + .outline_button( + ns("preview_button"), + label = shiny::tags$span( + label, + shiny::uiOutput(ns("preview_button_counter")) + ), + icon = "file-earmark-text" + ) +} + +#' @rdname reporter_previewer +#' @export +preview_report_button_srv <- function(id, reporter) { + checkmate::assert_class(reporter, "Reporter") - shiny::fluidRow( - add_previewer_js(ns), - add_previewer_css(), + shiny::moduleServer(id, function(input, output, session) { + shiny::setBookmarkExclude(c("preview_button")) + + shiny::observeEvent(reporter$get_reactive_add_card(), { + shinyjs::toggleClass( + id = "preview_button", condition = reporter$get_reactive_add_card() == 0, class = "disabled" + ) + }) + + output$preview_button_counter <- shiny::renderUI({ + shiny::tags$span( + class = "position-absolute badge rounded-pill bg-primary", + reporter$get_reactive_add_card() + ) + }) + + preview_modal <- function() { + shiny::tags$div( + class = "teal-reporter reporter-previewer-modal", + .custom_css_dependency(), + shiny::modalDialog( + easyClose = TRUE, + size = "xl", + reporter_previewer_content_ui(session$ns("preview_content")), + footer = shiny::tagList( + shiny::tags$button( + type = "button", + class = "btn btn-outline-secondary", + `data-bs-dismiss` = "modal", + NULL, + "Dismiss" + ) + ) + ) + ) + } + + shiny::observeEvent(input$preview_button, { + shiny::showModal(preview_modal()) + }) + reporter_previewer_content_srv(id = "preview_content", reporter = reporter) + }) +} + +#' @rdname reporter_previewer_deprecated +#' @export +reporter_previewer_ui <- function(id) { + ns <- shiny::NS(id) + lifecycle::deprecate_soft( + when = "", + what = "reporter_previewer_ui()", + details = paste( + "Calling `reporter_previewer_ui()` is deprecated and will be removed in the next release.\n", + "Please use `report_load_ui()`, `download_report_button_ui()`, `reset_report_button_ui()`,", + "and `preview_report_button_ui()` instead." + ) + ) + bslib::page_fluid( shiny::tagList( + shinyjs::useShinyjs(), shiny::tags$div( - class = "col-md-3", - shiny::tags$div(class = "well", shiny::uiOutput(ns("encoding"))) + class = "well", + style = "display: inline-flex; flex-direction: row; gap: 10px;", + shiny::tags$span(id = ns("load_span"), report_load_ui(ns("load"), label = "Load Report")), + shiny::tags$span( + id = ns("download_span"), download_report_button_ui(ns("download"), label = "Download Report") + ), + shiny::tags$span(id = ns("reset_span"), reset_report_button_ui(ns("reset"), label = "Reset Report")) ), shiny::tags$div( - class = "col-md-9", - shiny::tags$div( - id = "reporter_previewer", - shiny::uiOutput(ns("pcards")) - ) + reporter_previewer_content_ui(ns("previewer")) ) ) ) } -#' @rdname reporter_previewer +#' @rdname reporter_previewer_deprecated #' @export reporter_previewer_srv <- function(id, reporter, global_knitr = getOption("teal.reporter.global_knitr"), - rmd_output = c( - "html" = "html_document", "pdf" = "pdf_document", - "powerpoint" = "powerpoint_presentation", - "word" = "word_document" - ), - rmd_yaml_args = list( - author = "NEST", title = "Report", - date = as.character(Sys.Date()), output = "html_document", - toc = FALSE - ), + rmd_output = getOption("teal.reporter.rmd_output"), + rmd_yaml_args = getOption("teal.reporter.rmd_yaml_args"), previewer_buttons = c("download", "load", "reset")) { + lifecycle::deprecate_soft( + when = "", + what = "reporter_previewer_srv()", + details = paste( + "Calling `reporter_previewer_srv()` is deprecated and will be removed in the next release.\n", + "Please use `report_load_srv()`, `download_report_button_srv()`, `reset_report_button_srv()`,", + "and `preview_report_button_srv()` instead." + ) + ) checkmate::assert_subset(previewer_buttons, c("download", "load", "reset"), empty.ok = FALSE) checkmate::assert_true("download" %in% previewer_buttons) checkmate::assert_class(reporter, "Reporter") checkmate::assert_subset(names(global_knitr), names(knitr::opts_chunk$get())) checkmate::assert_subset( rmd_output, - c( - "html_document", "pdf_document", - "powerpoint_presentation", "word_document" - ), + c("html_document", "pdf_document", "powerpoint_presentation", "word_document"), empty.ok = FALSE ) checkmate::assert_list(rmd_yaml_args, names = "named") @@ -87,206 +178,117 @@ reporter_previewer_srv <- function(id, checkmate::assert_true(rmd_yaml_args[["output"]] %in% rmd_output) shiny::moduleServer(id, function(input, output, session) { - shiny::setBookmarkExclude(c( - "card_remove_id", "card_down_id", "card_up_id", "remove_card_ok", "showrcode", "download_data_prev", - "load_reporter_previewer", "load_reporter" - )) - - session$onBookmark(function(state) { - reporterdir <- file.path(state$dir, "reporter") - dir.create(reporterdir) - reporter$to_jsondir(reporterdir) - }) - session$onRestored(function(state) { - reporterdir <- file.path(state$dir, "reporter") - reporter$from_jsondir(reporterdir) - }) - - ns <- session$ns - - reset_report_button_srv("resetButtonPreviewer", reporter) - - output$encoding <- shiny::renderUI({ - reporter$get_reactive_add_card() - nr_cards <- length(reporter$get_cards()) + if (!"load" %in% previewer_buttons) { + shinyjs::hide(id = "load_span") + } + if (!"download" %in% previewer_buttons) { + shinyjs::hide(id = "download_span") + } + if (!"reset" %in% previewer_buttons) { + shinyjs::hide(id = "reset_span") + } + report_load_srv("load", reporter = reporter) + download_report_button_srv( + "download", + reporter = reporter, + global_knitr = global_knitr, + rmd_output = rmd_output, + rmd_yaml_args = rmd_yaml_args + ) + reset_report_button_srv("reset", reporter = reporter) + reporter_previewer_content_srv("previewer", reporter = reporter) + }) +} - previewer_buttons_list <- list( - download = htmltools::tagAppendAttributes( - shiny::actionButton( - ns("download_data_prev"), - class = "teal-reporter simple_report_button", - shiny::tags$span("Download Report", shiny::icon("download")) - ), - class = if (nr_cards) "" else "disabled" - ), - load = shiny::actionButton( - ns("load_reporter_previewer"), - class = "teal-reporter simple_report_button", - `data-val` = shiny::restoreInput(id = ns("load_reporter_previewer"), default = NULL), - shiny::tags$span( - "Load Report", shiny::icon("upload") - ) - ), - reset = reset_report_button_ui(ns("resetButtonPreviewer"), label = "Reset Report") - ) +#' @keywords internal +reporter_previewer_content_ui <- function(id) { + shiny::uiOutput(shiny::NS(id, "pcards")) +} - shiny::tags$div( - id = "previewer_reporter_encoding", - shiny::tags$h3("Download the Report"), - shiny::tags$hr(), - reporter_download_inputs( - rmd_yaml_args = rmd_yaml_args, - rmd_output = rmd_output, - showrcode = any_rcode_block(reporter), - session = session - ), - shiny::tags$div( - id = "previewer_reporter_buttons", - class = "previewer_buttons_line", - lapply(previewer_buttons_list[previewer_buttons], shiny::tags$div) - ) - ) +#' @keywords internal +reporter_previewer_content_srv <- function(id, reporter) { + shiny::moduleServer(id, function(input, output, session) { + shiny::setBookmarkExclude("card_remove_id") + report_cards <- shiny::reactive({ + shiny::req(reporter$get_reactive_add_card()) + input$reporter_cards_order + reporter$get_cards() }) - output$pcards <- shiny::renderUI({ - reporter$get_reactive_add_card() - input$card_remove_id - input$card_down_id - input$card_up_id - - cards <- reporter$get_cards() + cards <- report_cards() if (length(cards)) { shiny::tags$div( - class = "panel-group accordion", - id = "reporter_previewer_panel", - lapply(seq_along(cards), function(ic) { - previewer_collapse_item(ic, cards[[ic]]$get_name(), cards[[ic]]$get_content()) - }) + .custom_css_dependency(), + bslib::accordion( + id = session$ns("reporter_cards"), + class = "teal-reporter report-previewer-accordion", + lapply(names(cards), function(card_id) { + htmltools::tagAppendChildren( + tag = shiny::tags$div( + id = card_id, + `data-rank-id` = card_id, + bslib::accordion_panel( + title = cards[[card_id]]$get_name(), + icon = bslib::tooltip( + bsicons::bs_icon("arrows-move"), + "Move card" + ), + shiny::tags$div( + id = paste0("card", card_id), + lapply( + cards[[card_id]]$get_content(), + function(b) { + block_to_html(b) + } + ) + ) + ) + ), + .cssSelector = ".accordion-button", + bslib::tooltip( + shiny::tags$a( + class = "action-button", + role = "button", + style = "text-decoration: none;", + onclick = sprintf( + "Shiny.setInputValue('%s', '%s', {priority: 'event'});", + session$ns("card_remove_id"), + card_id + ), + bsicons::bs_icon("x-circle", class = "text-danger") + ), + "Remove card" + ) + ) + }) + ), + sortable::sortable_js( + css_id = session$ns("reporter_cards"), + options = sortable::sortable_options( + onSort = sortable::sortable_js_capture_input(session$ns("reporter_cards_order")), + handle = ".accordion-icon" + ) + ) ) } else { shiny::tags$div( - id = "reporter_previewer_panel_no_cards", + shiny::tags$br(), shiny::tags$p( - class = "text-danger mt-4", + class = "text-danger", shiny::tags$strong("No Cards added") ) ) } }) - shiny::observeEvent(input$load_reporter_previewer, { - nr_cards <- length(reporter$get_cards()) - shiny::showModal( - shiny::modalDialog( - easyClose = TRUE, - shiny::tags$h3("Load the Reporter"), - shiny::tags$hr(), - shiny::fileInput(ns("archiver_zip"), "Choose Reporter File to Load (a zip file)", - multiple = FALSE, - accept = c(".zip") - ), - footer = shiny::div( - shiny::tags$button( - type = "button", - class = "btn btn-danger", - `data-dismiss` = "modal", - `data-bs-dismiss` = "modal", - NULL, - "Cancel" - ), - shiny::tags$button( - id = ns("load_reporter"), - type = "button", - class = "btn btn-primary action-button", - `data-val` = shiny::restoreInput(id = ns("load_reporter"), default = NULL), - NULL, - "Load" - ) - ) - ) - ) - }) - - shiny::observeEvent(input$load_reporter, { - switch("JSON", - JSON = load_json_report(reporter, input$archiver_zip[["datapath"]], input$archiver_zip[["name"]]), - stop("The provided Reporter file format is not supported") - ) - - shiny::removeModal() - }) - shiny::observeEvent(input$card_remove_id, { - shiny::showModal( - shiny::modalDialog( - title = "Remove the Report Card", - shiny::tags$p( - shiny::HTML( - sprintf( - "Do you really want to remove the card %s from the Report?", - input$card_remove_id - ) - ) - ), - footer = shiny::tagList( - shiny::tags$button( - type = "button", - class = "btn btn-secondary", - `data-dismiss` = "modal", - `data-bs-dismiss` = "modal", - NULL, - "Cancel" - ), - shiny::actionButton(ns("remove_card_ok"), "OK", class = "btn-danger") - ) - ) - ) - }) - - shiny::observeEvent(input$remove_card_ok, { - reporter$remove_cards(input$card_remove_id) - shiny::removeModal() - }) - - shiny::observeEvent(input$card_up_id, { - if (input$card_up_id > 1) { - reporter$swap_cards( - as.integer(input$card_up_id), - as.integer(input$card_up_id - 1) - ) - } + reporter$remove_cards(ids = input$card_remove_id) }) - shiny::observeEvent(input$card_down_id, { - if (input$card_down_id < length(reporter$get_cards())) { - reporter$swap_cards( - as.integer(input$card_down_id), - as.integer(input$card_down_id + 1) - ) - } + shiny::observeEvent(input$reporter_cards_order, { + reporter$reorder_cards(input$reporter_cards_order) }) - - output$download_data_prev <- shiny::downloadHandler( - filename = function() { - paste0( - "report_", - if (reporter$get_id() == "") NULL else paste0(reporter$get_id(), "_"), - format(Sys.time(), "%y%m%d%H%M%S"), - ".zip" - ) - }, - content = function(file) { - shiny::showNotification("Rendering and Downloading the document.") - shinybusy::block(id = ns("download_data_prev"), text = "", type = "dots") - input_list <- lapply(names(rmd_yaml_args), function(x) input[[x]]) - names(input_list) <- names(rmd_yaml_args) - if (is.logical(input$showrcode)) global_knitr[["echo"]] <- input$showrcode - report_render_and_compress(reporter, input_list, global_knitr, file) - shinybusy::unblock(id = ns("download_data_prev")) - }, - contentType = "application/zip" - ) }) } @@ -321,161 +323,28 @@ block_to_html <- function(b) { } } -#' @noRd -#' @keywords internal -add_previewer_css <- function() { - shiny::tagList( - shiny::singleton( - shiny::tags$head(shiny::includeCSS(system.file("css/Previewer.css", package = "teal.reporter"))) - ), - shiny::singleton( - shiny::tags$head(shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter"))) - ) - ) -} - -#' @noRd -#' @keywords internal -add_previewer_js <- function(ns) { - shiny::singleton( - shiny::tags$head(shiny::tags$script( - shiny::HTML(sprintf(' - $(document).ready(function(event) { - $("body").on("click", "span.card_remove_id", function() { - let val = $(this).data("cardid"); - Shiny.setInputValue("%s", val, {priority: "event"}); - }); - - $("body").on("click", "span.card_up_id", function() { - let val = $(this).data("cardid"); - Shiny.setInputValue("%s", val, {priority: "event"}); - }); - - $("body").on("click", "span.card_down_id", function() { - let val = $(this).data("cardid"); - Shiny.setInputValue("%s", val, {priority: "event"}); - }); - }); - ', ns("card_remove_id"), ns("card_up_id"), ns("card_down_id"))) - )) - ) -} - -#' @noRd -#' @keywords internal -nav_previewer_icon <- function(name, icon_name, idx, size = 1L) { - checkmate::assert_string(name) - checkmate::assert_string(icon_name) - checkmate::assert_int(size) - - shiny::tags$span( - class = paste(name, "icon_previewer"), - # data field needed to record clicked card on the js side - `data-cardid` = idx, - shiny::icon(icon_name, sprintf("fa-%sx", size)) - ) -} - -#' @noRd -#' @keywords internal -nav_previewer_icons <- function(idx, size = 1L) { - shiny::tags$span( - class = "preview_card_control", - nav_previewer_icon(name = "card_remove_id", icon_name = "xmark", idx = idx, size = size), - nav_previewer_icon(name = "card_up_id", icon_name = "arrow-up", idx = idx, size = size), - nav_previewer_icon(name = "card_down_id", icon_name = "arrow-down", idx = idx, size = size) - ) -} - #' @noRd #' @keywords internal previewer_collapse_item <- function(idx, card_name, card_blocks) { - shiny::tags$div(.renderHook = function(x) { - # get bs version - version <- get_bs_version() - - if (version == "3") { - shiny::tags$div( - id = paste0("panel_card_", idx), - class = "panel panel-default", - shiny::tags$div( - class = "panel-heading overflow-auto", - shiny::tags$div( - class = "panel-title", - shiny::tags$span( - nav_previewer_icons(idx = idx), - shiny::tags$a( - class = "accordion-toggle block py-3 px-4 -my-3 -mx-4", - `data-toggle` = "collapse", - `data-parent` = "#reporter_previewer_panel", - href = paste0("#collapse", idx), - shiny::tags$h4(paste0("Card ", idx, ": ", card_name), shiny::icon("caret-down")) - ) - ) - ) - ), - shiny::tags$div( - id = paste0("collapse", idx), class = "collapse out", - shiny::tags$div( - class = "panel-body", - shiny::tags$div( - id = paste0("card", idx), - lapply( - card_blocks, - function(b) { - block_to_html(b) - } - ) - ) - ) - ) - ) - } else { + htmltools::tagAppendChildren( + tag = bslib::accordion_panel( + title = paste0("Card ", idx, ": ", card_name), shiny::tags$div( - id = paste0("panel_card_", idx), - class = "card", - shiny::tags$div( - class = "overflow-auto", - shiny::tags$div( - class = "card-header", - shiny::tags$span( - nav_previewer_icons(idx = idx), - shiny::tags$a( - class = "accordion-toggle block py-3 px-4 -my-3 -mx-4", - # bs4 - `data-toggle` = "collapse", - # bs5 - `data-bs-toggle` = "collapse", - href = paste0("#collapse", idx), - shiny::tags$h4( - paste0("Card ", idx, ": ", card_name), - shiny::icon("caret-down") - ) - ) - ) - ) - ), - shiny::tags$div( - id = paste0("collapse", idx), - class = "collapse out", - # bs4 - `data-parent` = "#reporter_previewer_panel", - # bs5 - `data-bs-parent` = "#reporter_previewer_panel", - shiny::tags$div( - class = "card-body", - shiny::tags$div( - id = paste0("card", idx), - lapply( - card_blocks, - function(b) { - block_to_html(b) - } - ) - ) - ) + id = paste0("card", idx), + lapply( + card_blocks, + function(b) { + block_to_html(b) + } ) ) - } - }) + ), + .cssSelector = ".accordion-header", + shiny::actionButton( + inputId = paste0("card_remove_id_", idx), + label = "Remove card", + class = "card_remove_id", + `data-cardid` = idx + ) + ) } diff --git a/R/Reporter.R b/R/Reporter.R index 37716ad99..082500b06 100644 --- a/R/Reporter.R +++ b/R/Reporter.R @@ -49,7 +49,15 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' reporter$append_cards(list(card1, card2)) append_cards = function(cards) { checkmate::assert_list(cards, "ReportCard") - private$cards <- append(private$cards, cards) + # Set up unique id for each card + names(cards) <- vapply(cards, function(card) { + sprintf("card_%s", substr(rlang::hash(list(deparse1(card), Sys.time())), 1, 8)) + }, character(1L)) + + for (card_id in names(cards)) { + private$cards[[card_id]] <- cards[[card_id]] + private$cards_order <- c(private$cards_order, card_id) + } private$reactive_add_card(length(private$cards)) invisible(self) }, @@ -80,7 +88,7 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. #' reporter$append_cards(list(card1, card2)) #' reporter$get_cards() get_cards = function() { - private$cards + private$cards[private$cards_order] }, #' @description Compiles and returns all content blocks from the [`ReportCard`] in the `Reporter`. #' @@ -129,42 +137,26 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. private$cards <- list() private$metadata <- list() private$reactive_add_card(0) + private$cards_order <- c() invisible(self) }, #' @description Removes specific `ReportCard` objects from the `Reporter` by their indices. #' - #' @param ids (`integer(id)`) the indexes of cards + #' @param ids (`character`) the ids of the cards to be removed. #' @return `self`, invisibly. - remove_cards = function(ids = NULL) { - checkmate::assert( - checkmate::check_null(ids), - checkmate::check_integer(ids, min.len = 1, max.len = length(private$cards)) - ) + remove_cards = function(ids) { if (!is.null(ids)) { - private$cards <- private$cards[-ids] + private$cards <- private$cards[!names(private$cards) %in% ids] + private$cards_order <- private$cards_order[!private$cards_order %in% ids] } private$reactive_add_card(length(private$cards)) invisible(self) }, - #' @description Swaps the positions of two `ReportCard` objects within the `Reporter`. - #' - #' @param start (`integer`) the index of the first card - #' @param end (`integer`) the index of the second card + #' @description Reorders `ReportCard` or `ReportDocument` objects in `Reporter`. + #' @param new_order `character` vector with card ids in the desired order. #' @return `self`, invisibly. - swap_cards = function(start, end) { - checkmate::assert( - checkmate::check_integer(start, - min.len = 1, max.len = 1, lower = 1, upper = length(private$cards) - ), - checkmate::check_integer(end, - min.len = 1, max.len = 1, lower = 1, upper = length(private$cards) - ), - combine = "and" - ) - start_val <- private$cards[[start]]$clone() - end_val <- private$cards[[end]]$clone() - private$cards[[start]] <- end_val - private$cards[[end]] <- start_val + reorder_cards = function(new_order) { + private$cards_order <- new_order invisible(self) }, #' @description Gets the current value of the reactive variable for adding cards. @@ -338,6 +330,7 @@ Reporter <- R6::R6Class( # nolint: object_name_linter. cards = list(), metadata = list(), reactive_add_card = NULL, + cards_order = c(), # @description The copy constructor. # # @param name the name of the field diff --git a/R/ResetModule.R b/R/ResetModule.R index ca7c5e06d..1fc4bac17 100644 --- a/R/ResetModule.R +++ b/R/ResetModule.R @@ -9,8 +9,7 @@ #' @name reset_report_button #' #' @param id (`character(1)`) `shiny` module instance id. -#' @param label (`character(1)`) label before the icon. -#' By default `NULL`. +#' @param label (`character(1)`) label of the button. By default it is empty. #' @param reporter (`Reporter`) instance. #' @return `NULL`. NULL @@ -20,21 +19,11 @@ NULL reset_report_button_ui <- function(id, label = NULL) { checkmate::assert_string(label, null.ok = TRUE) - ns <- shiny::NS(id) - shiny::tagList( - shiny::singleton( - shiny::tags$head(shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter"))) - ), - shiny::actionButton( - ns("reset_reporter"), - class = "teal-reporter simple_report_button clear-report btn-warning", - title = "Reset", - `data-val` = shiny::restoreInput(id = ns("reset_reporter"), default = NULL), - shiny::tags$span( - if (!is.null(label)) label, - shiny::icon("xmark") - ) - ) + .outline_button( + shiny::NS(id, "reset_reporter"), + label = label, + icon = "x-lg", + class = "danger" ) } @@ -49,12 +38,19 @@ reset_report_button_srv <- function(id, reporter) { ns <- session$ns nr_cards <- length(reporter$get_cards()) + shiny::observeEvent(reporter$get_reactive_add_card(), { + shinyjs::toggleClass( + id = "reset_reporter", condition = reporter$get_reactive_add_card() == 0, class = "disabled" + ) + }) shiny::observeEvent(input$reset_reporter, { shiny::tags$div( - class = "teal-widgets reporter-modal", + class = "teal-reporter reporter-modal", + .custom_css_dependency(), shiny::showModal( shiny::modalDialog( + easyClose = TRUE, shiny::tags$h3("Reset the Report"), shiny::tags$hr(), shiny::tags$strong( @@ -65,13 +61,12 @@ reset_report_button_srv <- function(id, reporter) { footer = shiny::tagList( shiny::tags$button( type = "button", - class = "btn btn-secondary", - `data-dismiss` = "modal", + class = "btn btn-outline-secondary", `data-bs-dismiss` = "modal", NULL, - "Cancel" + "Dismiss" ), - shiny::actionButton(ns("reset_reporter_ok"), "Reset", class = "btn-danger") + shiny::actionButton(ns("reset_reporter_ok"), "Reset", class = "btn btn-primary") ) ) ) diff --git a/R/SimpleReporter.R b/R/SimpleReporter.R index 3aef137ce..d45a41d08 100644 --- a/R/SimpleReporter.R +++ b/R/SimpleReporter.R @@ -39,19 +39,17 @@ NULL simple_reporter_ui <- function(id) { ns <- shiny::NS(id) shiny::tagList( - shiny::singleton( - shiny::tags$head(shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter"))) - ), + .custom_css_dependency(), shiny::tags$div( - class = "block mb-4 p-1", - shiny::tags$label(class = "text-primary block -ml-1", shiny::tags$strong("Reporter")), + shiny::tags$label(class = "text-primary", shiny::tags$strong("Reporter")), shiny::tags$div( class = "simple_reporter_container", add_card_button_ui(ns("add_report_card_simple")), download_report_button_ui(ns("download_button_simple")), report_load_ui(ns("archive_load_simple")), reset_report_button_ui(ns("reset_button_simple")) - ) + ), + shiny::tags$br() ) ) } diff --git a/R/utils.R b/R/utils.R index c86e4222f..424a6d5bd 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,15 +1,3 @@ -#' Get bootstrap current version -#' @note will work properly mainly inside a tag `.renderHook` -#' @keywords internal -get_bs_version <- function() { - theme <- bslib::bs_current_theme() - if (bslib::is_bs_theme(theme)) { - bslib::theme_version(theme) - } else { - "3" - } -} - #' Panel group widget #' #' `r lifecycle::badge("experimental")` @@ -35,78 +23,36 @@ panel_item <- function(title, ..., collapsed = TRUE, input_id = NULL) { shiny::tags$div(.renderHook = function(res_tag) { - bs_version <- get_bs_version() - - # alter tag structure - if (bs_version == "3") { - res_tag$children <- list( + res_tag$children <- list( + shiny::tags$div( + class = "card", + style = "margin: 0.5rem 0;", shiny::tags$div( - class = "panel panel-default", + class = "card-header", shiny::tags$div( - id = div_id, - class = paste("panel-heading", ifelse(collapsed, "collapsed", "")), - `data-toggle` = "collapse", + class = ifelse(collapsed, "collapsed", ""), + `data-bs-toggle` = "collapse", href = paste0("#", panel_id), `aria-expanded` = ifelse(collapsed, "false", "true"), shiny::icon("angle-down", class = "dropdown-icon"), shiny::tags$label( - class = "panel-title inline", + style = "display: inline;", title, ) - ), - shiny::tags$div( - class = paste("panel-collapse collapse", ifelse(collapsed, "", "in")), - id = panel_id, - shiny::tags$div( - class = "panel-body", - ... - ) ) - ) - ) - } else if (bs_version %in% c("4", "5")) { - res_tag$children <- list( + ), shiny::tags$div( - class = "card my-2", + id = panel_id, + class = paste("collapse", ifelse(collapsed, "", "show")), shiny::tags$div( - class = "card-header", - shiny::tags$div( - class = ifelse(collapsed, "collapsed", ""), - # bs4 - `data-toggle` = "collapse", - # bs5 - `data-bs-toggle` = "collapse", - href = paste0("#", panel_id), - `aria-expanded` = ifelse(collapsed, "false", "true"), - shiny::icon("angle-down", class = "dropdown-icon"), - shiny::tags$label( - class = "card-title inline", - title, - ) - ) - ), - shiny::tags$div( - id = panel_id, - class = paste("collapse", ifelse(collapsed, "", "show")), - shiny::tags$div( - class = "card-body", - ... - ) + class = "card-body", + ... ) ) ) - } else { - stop("Bootstrap 3, 4, and 5 are supported.") - } - - shiny::tagList( - shiny::singleton( - shiny::tags$head( - shiny::includeCSS(system.file("css/custom.css", package = "teal.reporter")) - ) - ), - res_tag ) + + res_tag }) } @@ -166,7 +112,7 @@ get_merge_index_single <- function(span) { } j <- j + span[j] } - return(ret) + ret } #' Divide text block into smaller blocks @@ -214,3 +160,37 @@ global_knitr_details <- function() { collapse = "\n" ) } + + +#' @keywords internal +.outline_button <- function(id, label, icon = NULL, class = "primary") { + shiny::tagList( + shinyjs::useShinyjs(), + .custom_css_dependency(), + shiny::tags$a( + id = id, + class = sprintf("teal-reporter action-button outline-button %s", class), + role = "button", + style = "text-decoration: none;", + if (!is.null(icon)) { + margin_style <- ifelse(is.null(label), "margin: 0 10px 0 10px;", "") + shiny::tags$span( + style = margin_style, + bsicons::bs_icon(icon, class = sprintf("text-%s", class)) + ) + }, + label + ) + ) +} + +#' @keywords internal +.custom_css_dependency <- function() { + htmltools::htmlDependency( + name = "teal-reporter", + version = utils::packageVersion("teal.reporter"), + package = "teal.reporter", + src = "css", + stylesheet = "custom.css" + ) +} diff --git a/R/zzz.R b/R/zzz.R index 8352c1112..48148c361 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,14 +1,25 @@ .onLoad <- function(libname, pkgname) { op <- options() - default_global_knitr <- list(teal.reporter.global_knitr = list( - echo = TRUE, - tidy.opts = list(width.cutoff = 60), - tidy = requireNamespace("formatR", quietly = TRUE) - )) + teal_reporter_default_options <- list( + teal.reporter.global_knitr = list( + echo = TRUE, + tidy.opts = list(width.cutoff = 60), + tidy = requireNamespace("formatR", quietly = TRUE) + ), + teal.reporter.rmd_output = c( + "html" = "html_document", "pdf" = "pdf_document", + "powerpoint" = "powerpoint_presentation", + "word" = "word_document" + ), + teal.reporter.rmd_yaml_args = list( + author = "NEST", title = "Report", + date = as.character(Sys.Date()), output = "html_document", + toc = FALSE + ) + ) - if (!("teal.reporter.global_knitr" %in% names(op))) { - options(default_global_knitr) - } + toset <- !(names(teal_reporter_default_options) %in% names(op)) + if (any(toset)) options(teal_reporter_default_options[toset]) invisible() } diff --git a/_pkgdown.yml b/_pkgdown.yml index 51d0f467d..78c529ee2 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -12,12 +12,12 @@ navbar: reports: text: Reports menu: - - text: Coverage report - href: coverage-report/ - - text: Unit test report - href: unit-test-report/ - - text: Non-CRAN unit test report - href: unit-test-report-non-cran/ + - text: Coverage report + href: coverage-report/ + - text: Unit test report + href: unit-test-report/ + - text: Non-CRAN unit test report + href: unit-test-report-non-cran/ github: icon: fa-github href: https://github.com/insightsengineering/teal.reporter @@ -29,18 +29,15 @@ reference: - add_card_button_ui - title: "`shiny` modules for viewing and downloading reports" contents: - - reporter_previewer_srv - - reporter_previewer_ui - simple_reporter_srv - simple_reporter_ui - title: Supporting `shiny` modules contents: - - download_report_button_srv - - download_report_button_ui - - reset_report_button_srv - - reset_report_button_ui - - report_load_srv - - report_load_ui + - add_card_button + - download_report_button + - reset_report_button + - load_report_button + - reporter_previewer - title: "`yaml` and rmd utility functions" contents: - as_yaml_auto @@ -51,3 +48,6 @@ reference: contents: - ReportCard - Reporter + - title: "Deprecated functions" + contents: + - reporter_previewer_deprecated diff --git a/inst/css/Previewer.css b/inst/css/Previewer.css deleted file mode 100644 index e66e17476..000000000 --- a/inst/css/Previewer.css +++ /dev/null @@ -1,41 +0,0 @@ -/* teal.reporter Previewer css */ - -/* highlight the icon when hover */ -span.preview_card_control i:hover { - color: blue; -} - -.previewer_buttons_line { - display: flex; - flex-wrap: wrap; - margin-right: 10px; -} - -/* Disable any anchor with disabled class */ -a.disabled { - color: grey; - cursor: not-allowed; - pointer-events: none; - text-decoration: none; -} - -a[id$="download_data"].disabled, -a[id$="download_data_prev"].disabled { - border: 0; - color: white; - background-color: darkgrey; -} - -/* icons in the previewer */ -.icon_previewer { - float: right; - margin-left: 10px; - margin-right: 10px; - margin-top: 10px; - color: #337ab7; -} - -/* prevents oversizing elements covered by shinybusy::block */ -.nx-block-temporary-position { - min-height: 0 !important; -} diff --git a/inst/css/custom.css b/inst/css/custom.css index 445383cb2..085eb9ad3 100644 --- a/inst/css/custom.css +++ b/inst/css/custom.css @@ -1,95 +1,85 @@ -/* teal.reporter custom css */ - -.overflow-auto { - overflow: auto; -} - -.block { - display: block; +/* To make sure the shinybusy::block() element does not take too much height */ +.teal-reporter.download-ok.nx-block-temporary-position { + min-height: 10px !important; } -.inline { - display: inline; -} - -.flex { +.simple_reporter_container { display: flex; -} - -.float-right { - float: right; -} - -.justify-start { justify-content: flex-start; + flex-wrap: wrap; + gap: 10px; } -.-ml-1 { - margin-left: -0.25rem; +.teal-reporter.reporter-modal .modal-body, +.modal-footer { + padding: 1rem; } -.-mx-4 { - margin-left: -1rem; - margin-right: -1rem; +.teal-reporter.reporter-modal .modal-footer { + padding-top: 0; } -.-my-3 { - margin-top: -0.75rem; - margin-bottom: -0.75rem; +.teal-reporter.outline-button { + padding: 0.3em; + border-radius: 5px; } -.mb-4 { - margin-bottom: 1rem; +.teal-reporter.outline-button.disabled { + pointer-events: none; + opacity: 0.65; } -.mt-4 { - margin-top: 1rem; +.teal-reporter.outline-button.primary { + color: var(--bs-primary); + border: solid 1px var(--bs-primary); } -.my-4 { - margin: 1rem 0; +.teal-reporter.outline-button.primary:hover { + background: var(--bs-primary-bg-subtle); } -.my-2 { - margin: 0.5rem 0; +.teal-reporter.outline-button.danger { + color: var(--bs-danger); + border: solid 1px var(--bs-danger); } -.px-4 { - padding-left: 1rem; - padding-right: 1rem; +.teal-reporter.outline-button.danger:hover { + background: var(--bs-danger-bg-subtle); } -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; +.teal-reporter.outline-button .badge { + margin-top: -1em; + margin-left: -0.3em; } -.p-1 { - padding: 0.25rem; +.teal-reporter.report-previewer-accordion .accordion-header { + display: flex; + align-items: center; } -/* dynamic labels after icons */ +.teal-reporter.report-previewer-accordion .accordion-button { + background: var(--bs-primary-bg-subtle); +} -.simple_reporter_container { - display: flex; - justify-content: flex-start; - flex-wrap: wrap; +.teal-reporter.report-previewer-accordion .accordion-icon { + cursor: grab; } -.simple_report_button { - min-width: 55px; - margin: 1px 1px; +.teal-reporter.report-previewer-accordion .accordion-icon:active { + cursor: grabbing; } -.teal-reporter.simple_report_button.disabled { - cursor: not-allowed; +.teal-reporter.report-previewer-accordion .accordion-button a.action-button { + margin-left: 8px; + order: 1; } -.teal-widgets.reporter-modal .modal-body, -.modal-footer { - padding: 1rem; +.teal-reporter.reporter-previewer-modal .modal-content { + overflow: auto; + max-height: 90vh; + height: 90vh; } -.teal-widgets.reporter-modal .modal-footer { - padding-top: 0; +.teal-reporter.reporter-previewer-modal .modal-dialog { + --bs-modal-margin: 20px; } diff --git a/man/Reporter.Rd b/man/Reporter.Rd index b13064d10..66b719c56 100644 --- a/man/Reporter.Rd +++ b/man/Reporter.Rd @@ -176,7 +176,7 @@ reporter$from_jsondir(tmp_dir) \item \href{#method-Reporter-get_blocks}{\code{Reporter$get_blocks()}} \item \href{#method-Reporter-reset}{\code{Reporter$reset()}} \item \href{#method-Reporter-remove_cards}{\code{Reporter$remove_cards()}} -\item \href{#method-Reporter-swap_cards}{\code{Reporter$swap_cards()}} +\item \href{#method-Reporter-reorder_cards}{\code{Reporter$reorder_cards()}} \item \href{#method-Reporter-get_reactive_add_card}{\code{Reporter$get_reactive_add_card()}} \item \href{#method-Reporter-get_metadata}{\code{Reporter$get_metadata()}} \item \href{#method-Reporter-append_metadata}{\code{Reporter$append_metadata()}} @@ -285,13 +285,13 @@ Resets the \code{Reporter}, removing all \code{\link{ReportCard}} objects and me \subsection{Method \code{remove_cards()}}{ Removes specific \code{ReportCard} objects from the \code{Reporter} by their indices. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Reporter$remove_cards(ids = NULL)}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{Reporter$remove_cards(ids)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{ids}}{(\code{integer(id)}) the indexes of cards} +\item{\code{ids}}{(\code{character}) the ids of the cards to be removed.} } \if{html}{\out{
}} } @@ -300,20 +300,18 @@ Removes specific \code{ReportCard} objects from the \code{Reporter} by their ind } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Reporter-swap_cards}{}}} -\subsection{Method \code{swap_cards()}}{ -Swaps the positions of two \code{ReportCard} objects within the \code{Reporter}. +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Reporter-reorder_cards}{}}} +\subsection{Method \code{reorder_cards()}}{ +Reorders \code{ReportCard} or \code{ReportDocument} objects in \code{Reporter}. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Reporter$swap_cards(start, end)}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{Reporter$reorder_cards(new_order)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{start}}{(\code{integer}) the index of the first card} - -\item{\code{end}}{(\code{integer}) the index of the second card} +\item{\code{new_order}}{\code{character} vector with card ids in the desired order.} } \if{html}{\out{
}} } diff --git a/man/add_card_button.Rd b/man/add_card_button.Rd index 6e009ceca..a329410a4 100644 --- a/man/add_card_button.Rd +++ b/man/add_card_button.Rd @@ -6,13 +6,15 @@ \alias{add_card_button_srv} \title{Add card button module} \usage{ -add_card_button_ui(id) +add_card_button_ui(id, label = NULL) add_card_button_srv(id, reporter, card_fun) } \arguments{ \item{id}{(\code{character(1)}) this \code{shiny} module's id.} +\item{label}{(\code{character(1)}) label of the button. By default it is empty.} + \item{reporter}{(\code{Reporter}) instance.} \item{card_fun}{(\code{function}) which returns a \code{\link{ReportCard}} instance. See \code{Details}.} diff --git a/man/download_report_button.Rd b/man/download_report_button.Rd index a7091a31a..58cd35346 100644 --- a/man/download_report_button.Rd +++ b/man/download_report_button.Rd @@ -6,21 +6,21 @@ \alias{download_report_button_srv} \title{Download report button module} \usage{ -download_report_button_ui(id) +download_report_button_ui(id, label = NULL) download_report_button_srv( id, reporter, global_knitr = getOption("teal.reporter.global_knitr"), - rmd_output = c(html = "html_document", pdf = "pdf_document", powerpoint = - "powerpoint_presentation", word = "word_document"), - rmd_yaml_args = list(author = "NEST", title = "Report", date = - as.character(Sys.Date()), output = "html_document", toc = FALSE) + rmd_output = getOption("teal.reporter.rmd_output"), + rmd_yaml_args = getOption("teal.reporter.rmd_yaml_args") ) } \arguments{ \item{id}{(\code{character(1)}) this \code{shiny} module's id.} +\item{label}{(\code{character(1)}) label of the button. By default it is empty.} + \item{reporter}{(\code{Reporter}) instance.} \item{global_knitr}{(\code{list}) of \code{knitr} parameters (passed to \code{knitr::opts_chunk$set}) diff --git a/man/get_bs_version.Rd b/man/get_bs_version.Rd deleted file mode 100644 index 6f29bb4ad..000000000 --- a/man/get_bs_version.Rd +++ /dev/null @@ -1,15 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.R -\name{get_bs_version} -\alias{get_bs_version} -\title{Get bootstrap current version} -\usage{ -get_bs_version() -} -\description{ -Get bootstrap current version -} -\note{ -will work properly mainly inside a tag \code{.renderHook} -} -\keyword{internal} diff --git a/man/report_load_srv.Rd b/man/load_report_button.Rd similarity index 54% rename from man/report_load_srv.Rd rename to man/load_report_button.Rd index ae823e9ba..23bf376fb 100644 --- a/man/report_load_srv.Rd +++ b/man/load_report_button.Rd @@ -1,22 +1,31 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/LoadReporterModule.R -\name{report_load_srv} +\name{load_report_button} +\alias{load_report_button} +\alias{report_load_ui} \alias{report_load_srv} -\title{Server to Load \code{Reporter}} +\title{Load \code{Reporter} button module} \usage{ +report_load_ui(id, label = NULL) + report_load_srv(id, reporter) } \arguments{ \item{id}{\code{character(1)} this \code{shiny} module's id.} +\item{label}{(\code{character(1)}) label of the button. By default it is empty.} + \item{reporter}{\code{\link{Reporter}} instance.} } \value{ +\code{shiny::tagList} + \code{shiny::moduleServer} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} -Server to load \code{ReporterCard}(s) to the \code{Reporter} -For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. +Provides a button to upload \code{ReporterCard}(s) to the \code{Reporter}. + +For more information, refer to the vignette: \code{vignette("simpleReporter", "teal.reporter")}. } diff --git a/man/report_load_ui.Rd b/man/report_load_ui.Rd deleted file mode 100644 index f42673060..000000000 --- a/man/report_load_ui.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LoadReporterModule.R -\name{report_load_ui} -\alias{report_load_ui} -\title{User Interface to Load \code{Reporter}} -\usage{ -report_load_ui(id) -} -\arguments{ -\item{id}{\code{character(1)} this \code{shiny} module's id.} -} -\value{ -\code{shiny::tagList} -} -\description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} -Button to upload \code{ReporterCard}(s) to the \code{Reporter}. - -For more details see the vignette: \code{vignette("simpleReporter", "teal.reporter")}. -} diff --git a/man/reporter_previewer.Rd b/man/reporter_previewer.Rd index 409128708..27a63b82f 100644 --- a/man/reporter_previewer.Rd +++ b/man/reporter_previewer.Rd @@ -2,44 +2,20 @@ % Please edit documentation in R/Previewer.R \name{reporter_previewer} \alias{reporter_previewer} -\alias{reporter_previewer_ui} -\alias{reporter_previewer_srv} -\title{Report previewer module} +\alias{preview_report_button_ui} +\alias{preview_report_button_srv} +\title{Show report previewer button module} \usage{ -reporter_previewer_ui(id) +preview_report_button_ui(id, label = "Preview Report") -reporter_previewer_srv( - id, - reporter, - global_knitr = getOption("teal.reporter.global_knitr"), - rmd_output = c(html = "html_document", pdf = "pdf_document", powerpoint = - "powerpoint_presentation", word = "word_document"), - rmd_yaml_args = list(author = "NEST", title = "Report", date = - as.character(Sys.Date()), output = "html_document", toc = FALSE), - previewer_buttons = c("download", "load", "reset") -) +preview_report_button_srv(id, reporter) } \arguments{ \item{id}{(\code{character(1)}) \code{shiny} module instance id.} -\item{reporter}{(\code{Reporter}) instance.} - -\item{global_knitr}{(\code{list}) of \code{knitr} parameters (passed to \code{knitr::opts_chunk$set}) -for customizing the rendering process.} - -\item{rmd_output}{(\code{character}) vector with \code{rmarkdown} output types, -by default all possible \code{pdf_document}, \code{html_document}, \code{powerpoint_presentation}, and \code{word_document}. -If vector is named then those names will appear in the \code{UI}.} - -\item{rmd_yaml_args}{(\verb{named list}) with \code{Rmd} \code{yaml} header fields and their default values. -This \code{list} will result in the custom subset of UI inputs for the download reporter functionality. -Default \code{list(author = "NEST", title = "Report", date = Sys.Date(), output = "html_document", toc = FALSE)}. -The \code{list} must include at least \code{"output"} field. -The default value for \code{"output"} has to be in the \code{rmd_output} argument.} +\item{label}{(\code{character(1)}) label of the button. By default it is "Preview Report".} -\item{previewer_buttons}{(\code{character}) set of modules to include with \code{c("download", "load", "reset")} possible -values and \code{"download"} is required. -Default \code{c("download", "load", "reset")}} +\item{reporter}{(\code{Reporter}) instance.} } \value{ \code{NULL}. @@ -47,20 +23,7 @@ Default \code{c("download", "load", "reset")}} \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} -Module offers functionalities to visualize, manipulate, -and interact with report cards that have been added to a report. -It includes a previewer interface to see the cards and options to modify the report before downloading. - -Cards are saved by the \code{shiny} bookmarking mechanism. +Provides a button that triggers showing the report preview in a modal. For more details see the vignette: \code{vignette("previewerReporter", "teal.reporter")}. } -\details{ -To access the default values for the \code{global_knitr} parameter, -use \code{getOption('teal.reporter.global_knitr')}. These defaults include: -\itemize{ -\item \code{echo = TRUE} -\item \code{tidy.opts = list(width.cutoff = 60)} -\item \code{tidy = TRUE} if \code{formatR} package is installed, \code{FALSE} otherwise -} -} diff --git a/man/reporter_previewer_deprecated.Rd b/man/reporter_previewer_deprecated.Rd new file mode 100644 index 000000000..fd7ef2a71 --- /dev/null +++ b/man/reporter_previewer_deprecated.Rd @@ -0,0 +1,68 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Previewer.R +\name{reporter_previewer_deprecated} +\alias{reporter_previewer_deprecated} +\alias{reporter_previewer_ui} +\alias{reporter_previewer_srv} +\title{Report previewer module} +\usage{ +reporter_previewer_ui(id) + +reporter_previewer_srv( + id, + reporter, + global_knitr = getOption("teal.reporter.global_knitr"), + rmd_output = getOption("teal.reporter.rmd_output"), + rmd_yaml_args = getOption("teal.reporter.rmd_yaml_args"), + previewer_buttons = c("download", "load", "reset") +) +} +\arguments{ +\item{id}{(\code{character(1)}) \code{shiny} module instance id.} + +\item{reporter}{(\code{Reporter}) instance.} + +\item{global_knitr}{(\code{list}) of \code{knitr} parameters (passed to \code{knitr::opts_chunk$set}) +for customizing the rendering process.} + +\item{rmd_output}{(\code{character}) vector with \code{rmarkdown} output types, +by default all possible \code{pdf_document}, \code{html_document}, \code{powerpoint_presentation}, and \code{word_document}. +If vector is named then those names will appear in the \code{UI}.} + +\item{rmd_yaml_args}{(\verb{named list}) with \code{Rmd} \code{yaml} header fields and their default values. +This \code{list} will result in the custom subset of UI inputs for the download reporter functionality. +Default \code{list(author = "NEST", title = "Report", date = Sys.Date(), output = "html_document", toc = FALSE)}. +The \code{list} must include at least \code{"output"} field. +The default value for \code{"output"} has to be in the \code{rmd_output} argument.} + +\item{previewer_buttons}{(\code{character}) set of modules to include with \code{c("download", "load", "reset")} possible +values and \code{"download"} is required. +Default \code{c("download", "load", "reset")}} +} +\value{ +\code{NULL}. +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +Module offers functionalities to visualize, manipulate, +and interact with report cards that have been added to a report. +It includes a previewer interface to see the cards and options to modify the report before downloading. + +Cards are saved by the \code{shiny} bookmarking mechanism. + +For more details see the vignette: \code{vignette("previewerReporter", "teal.reporter")}. + +This function is deprecated and will be removed in the next release. +Please use \code{preview_report_button_ui()} and \code{preview_report_button_srv()} +to create a preview button that opens a modal with the report preview. +} +\details{ +To access the default values for the \code{global_knitr} parameter, +use \code{getOption('teal.reporter.global_knitr')}. These defaults include: +\itemize{ +\item \code{echo = TRUE} +\item \code{tidy.opts = list(width.cutoff = 60)} +\item \code{tidy = TRUE} if \code{formatR} package is installed, \code{FALSE} otherwise +} +} diff --git a/man/reset_report_button.Rd b/man/reset_report_button.Rd index 94965888a..d9b01860d 100644 --- a/man/reset_report_button.Rd +++ b/man/reset_report_button.Rd @@ -13,8 +13,7 @@ reset_report_button_srv(id, reporter) \arguments{ \item{id}{(\code{character(1)}) \code{shiny} module instance id.} -\item{label}{(\code{character(1)}) label before the icon. -By default \code{NULL}.} +\item{label}{(\code{character(1)}) label of the button. By default it is empty.} \item{reporter}{(\code{Reporter}) instance.} } diff --git a/tests/testthat/test-DownloadModule.R b/tests/testthat/test-DownloadModule.R new file mode 100644 index 000000000..8213ab5a4 --- /dev/null +++ b/tests/testthat/test-DownloadModule.R @@ -0,0 +1,35 @@ +testthat::skip_if_not_installed("ggplot2") +card1 <- ReportCard$new() +card1$append_text("Header 2 text", "header2") +card1$append_text("A paragraph of default text", "header2") +card1$append_plot( + ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Length)) + + ggplot2::geom_histogram() +) + +reporter <- Reporter$new() +reporter$append_cards(list(card1)) + +testthat::test_that("download_report_button_srv - download a document", { + shiny::testServer( + download_report_button_srv, + args = list( + reporter = reporter, + global_knitr = getOption("teal.reporter.global_knitr"), + rmd_output = getOption("teal.reporter.rmd_output"), + rmd_yaml_args = getOption("teal.reporter.rmd_yaml_args") + ), + expr = { + f <- output$download_data + testthat::expect_true(file.exists(f)) + tmp_dir <- tempdir() + output_dir <- file.path(tmp_dir, sprintf("report_test_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4")))) + dir.create(path = output_dir) + zip::unzip(f, exdir = output_dir) + files <- list.files(output_dir, recursive = TRUE) + testthat::expect_true(any(grepl("[.]Rmd", files))) + testthat::expect_true(any(grepl("[.]html", files))) + unlink(output_dir, recursive = TRUE) + } + ) +}) diff --git a/tests/testthat/test-PreviewerReportModule.R b/tests/testthat/test-PreviewerReportModule.R index 3e9e09aaa..3aedd2e5c 100644 --- a/tests/testthat/test-PreviewerReportModule.R +++ b/tests/testthat/test-PreviewerReportModule.R @@ -10,32 +10,6 @@ card1$append_plot( reporter <- Reporter$new() reporter$append_cards(list(card1)) -testthat::test_that("reporter_previewer_srv - render and downlaod a document", { - shiny::testServer( - reporter_previewer_srv, - args = list(reporter = reporter), - expr = { - session$setInputs(`output` = "html_document") - session$setInputs(`title` = "TITLE") - session$setInputs(`author` = "AUTHOR") - session$setInputs(`toc` = FALSE) - session$setInputs(`download_data_prev` = 0) - - f <- output$download_data_prev - testthat::expect_true(file.exists(f)) - tmp_dir <- tempdir() - output_dir <- file.path(tmp_dir, sprintf("report_test_%s", gsub("[.]", "", format(Sys.time(), "%Y%m%d%H%M%OS4")))) - dir.create(path = output_dir) - zip::unzip(f, exdir = output_dir) - files <- list.files(output_dir, recursive = TRUE) - testthat::expect_true(any(grepl("[.]Rmd", files))) - testthat::expect_true(any(grepl("[.]html", files))) - unlink(output_dir, recursive = TRUE) - } - ) -}) - - testthat::test_that("reporter_previewer_srv - subset of rmd_yaml_args", { rmd_yaml_args_correct <- list( correct1 = list( @@ -80,22 +54,6 @@ testthat::test_that("reporter_previewer_srv - subset of rmd_yaml_args", { } }) -reporter <- Reporter$new() -reporter$append_cards(list(card1)) -testthat::test_that("reporter_previewer_srv - remove a card", { - shiny::testServer( - reporter_previewer_srv, - args = list(reporter = reporter), - expr = { - len_prior <- length(reporter$get_cards()) - session$setInputs(`card_remove_id` = 1L) - session$setInputs(`remove_card_ok` = TRUE) - len_post <- length(reporter$get_cards()) - - testthat::expect_identical(len_prior, len_post + 1L) - } - ) -}) card2 <- ReportCard$new() card2$append_text("Header 2 text 2", "header2") @@ -140,34 +98,8 @@ testthat::test_that("reporter_previewer_srv - card up and down compensate", { ) }) -testthat::test_that("reporter_previewer_srv - card down", { - shiny::testServer( - reporter_previewer_srv, - args = list(reporter = reporter), - expr = { - cards_pre <- reporter$get_cards() - session$setInputs(`card_down_id` = 1L) - cards_post <- reporter$get_cards() - testthat::expect_equivalent(cards_pre, cards_post[2:1]) - } - ) -}) - -testthat::test_that("reporter_previewer_srv - card up", { - shiny::testServer( - reporter_previewer_srv, - args = list(reporter = reporter), - expr = { - cards_pre <- reporter$get_cards() - session$setInputs(`card_up_id` = 2L) - cards_post <- reporter$get_cards() - testthat::expect_equivalent(cards_pre, cards_post[2:1]) - } - ) -}) - testthat::test_that("reporter_previewer_ui - returns a tagList", { testthat::expect_true( - inherits(reporter_previewer_ui("sth"), c("shiny.tag")) + inherits(reporter_previewer_ui("sth"), c("shiny.tag.list")) ) }) diff --git a/tests/testthat/test-Reporter.R b/tests/testthat/test-Reporter.R index b26e7bdeb..0640d056e 100644 --- a/tests/testthat/test-Reporter.R +++ b/tests/testthat/test-Reporter.R @@ -45,7 +45,7 @@ testthat::test_that("set_id sets the reporter id and returns reporter", { }) testthat::test_that("get_cards returns the same cards which was added to reporter", { - testthat::expect_identical(reporter$get_cards(), list(card1, card2)) + testthat::expect_identical(unname(reporter$get_cards()), list(card1, card2)) }) testthat::test_that("get_blocks returns the same blocks which was added to reporter, sep = NULL", { @@ -78,12 +78,6 @@ testthat::test_that("The deep copy constructor copies the content files to new f }) -testthat::test_that("swap_cards", { - reporter1a <- reporter$clone() - reporter1b <- reporter$clone() - testthat::expect_equal(reporter1a$swap_cards(1L, 2L), reporter1b$swap_cards(2L, 1L)) -}) - testthat::test_that("reactive_add_card", { reporter <- Reporter$new() testthat::expect_error(reporter$get_reactive_add_card()) @@ -125,7 +119,7 @@ testthat::test_that("from_reporter does not return identical/equal object form o }) testthat::test_that("from_reporter persists the cards structure", { - testthat::expect_identical(reporter1$get_cards(), reporter2$from_reporter(reporter1)$get_cards()) + testthat::expect_identical(unname(reporter1$get_cards()), unname(reporter2$from_reporter(reporter1)$get_cards())) }) testthat::test_that("from_reporter persists the reactive_add_card count", { @@ -176,7 +170,7 @@ testthat::test_that("from_reporter does not return identical/equal object form o }) testthat::test_that("from_reporter persists the cards structure", { - testthat::expect_identical(reporter1$get_cards(), reporter2$from_reporter(reporter1)$get_cards()) + testthat::expect_identical(unname(reporter1$get_cards()), unname(reporter2$from_reporter(reporter1)$get_cards())) }) testthat::test_that("from_reporter persists the reactive_add_card count", { diff --git a/tests/testthat/test-ResetModule.R b/tests/testthat/test-ResetModule.R index a0e28f17c..7c9ba794d 100644 --- a/tests/testthat/test-ResetModule.R +++ b/tests/testthat/test-ResetModule.R @@ -14,14 +14,14 @@ card1 <- card_fun() reporter <- Reporter$new() reporter$append_cards(list(card1)) -testthat::test_that("simple_reporter_srv - reset a reporter", { +testthat::test_that("reset_report_button_srv - reset a reporter", { shiny::testServer( - simple_reporter_srv, - args = list(reporter = reporter, card_fun = card_fun), + reset_report_button_srv, + args = list(reporter = reporter), expr = { - testthat::expect_identical(reporter$get_cards(), list(card1)) - session$setInputs(`reset_button_simple-reset_reporter` = 0) - session$setInputs(`reset_button_simple-reset_reporter_ok` = 0) + testthat::expect_identical(unname(reporter$get_cards()), list(card1)) + session$setInputs(`reset_reporter` = 0) + session$setInputs(`reset_reporter_ok` = 0) testthat::expect_identical(reporter$get_blocks(), list()) } ) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 7be3a86ca..a74f3787c 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1,7 +1,3 @@ -testthat::test_that("get_bs_version", { - testthat::expect_identical(get_bs_version(), "3") -}) - testthat::test_that("panel_item", { testthat::expect_s3_class(panel_item("LABEL", shiny::tags$div()), "shiny.tag") }) diff --git a/vignettes/previewerReporter.Rmd b/vignettes/previewerReporter.Rmd index acc3f586e..a6946f130 100644 --- a/vignettes/previewerReporter.Rmd +++ b/vignettes/previewerReporter.Rmd @@ -10,10 +10,11 @@ vignette: > The Report previewer is an advanced shiny module designed for visualization, editing, and downloading of report cards. It extended the base modules introduced in the `simpleReporter` vignette, enhancing interactivity and user engagement with report content. +The report previewer is now implemented as a modal dialog that opens when a preview button is clicked, providing a streamlined user experience without requiring separate tabs. The five essential steps for implementing the report previewer include integrating it within a shiny application. Key code segments are highlighted in `### REPORTER` code blocks. -1. Create a `tabsetPanel` within main app with the previewer. +1. Add the preview button UI component to your app's interface. 2. Integrate the UI components of the modules into the app's UI. 3. Initialize reporter instance. 4. Create the report card function with two optional arguments: `card` and `comment`. @@ -42,42 +43,36 @@ library(bslib) A basic `shiny` app with the previewer module: ```{r, eval=requireNamespace("ggplot2")} -ui <- fluidPage( +ui <- bslib::page_fluid( # please, specify specific bootstrap version and theme - theme = bs_theme(version = "4"), - titlePanel(""), - tabsetPanel( - tabPanel( - "main App", - tags$br(), - sidebarLayout( - sidebarPanel( - uiOutput("encoding") - ), - mainPanel( - tabsetPanel( - id = "tabs", - tabPanel("Plot", plotOutput("dist_plot")), - tabPanel("Table", verbatimTextOutput("table")), - tabPanel("Table DataFrame", verbatimTextOutput("table2")), - tabPanel("Table DataTable", dataTableOutput("table3")) - ) - ) - ) - ), + tags$br(), + tags$span( ### REPORTER - tabPanel( - "Previewer", - reporter_previewer_ui("prev") - ) + teal.reporter::preview_report_button_ui("previewer") ### + ), + tags$br(), + tags$br(), + sidebarLayout( + sidebarPanel( + uiOutput("encoding") + ), + mainPanel( + tabsetPanel( + id = "tabs", + tabPanel("Plot", plotOutput("dist_plot")), + tabPanel("Table", verbatimTextOutput("table")), + tabPanel("Table DataFrame", verbatimTextOutput("table2")), + tabPanel("Table DataTable", dataTableOutput("table3")) + ) + ) ) ) server <- function(input, output, session) { output$encoding <- renderUI({ - tagList( + shiny::tagList( ### REPORTER - teal.reporter::simple_reporter_ui("simple_reporter"), + teal.reporter::add_card_button_ui("add_reporter", label = "Add Report Card"), ### if (input$tabs == "Plot") { sliderInput( @@ -193,8 +188,8 @@ server <- function(input, output, session) { } card } - teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) - teal.reporter::reporter_previewer_srv("prev", reporter) + teal.reporter::add_card_button_srv("add_reporter", reporter = reporter, card_fun = card_fun) + teal.reporter::preview_report_button_srv("previewer", reporter) ### } diff --git a/vignettes/simpleReporter.Rmd b/vignettes/simpleReporter.Rmd index 18cf18e9d..5c20802cf 100644 --- a/vignettes/simpleReporter.Rmd +++ b/vignettes/simpleReporter.Rmd @@ -43,6 +43,7 @@ First, load the required packages: ```{r message = FALSE, eval=requireNamespace("ggplot2")} library(shiny) +library(bslib) library(teal.reporter) library(ggplot2) library(rtables) @@ -51,7 +52,7 @@ library(rtables) Simple reporter `shiny` app with separate modules for each button: ```{r, eval=requireNamespace("ggplot2")} -ui <- fluidPage( +ui <- bslib::page_fluid( titlePanel(""), sidebarLayout( sidebarPanel( @@ -148,7 +149,7 @@ if (interactive()) shinyApp(ui = ui, server = server) Simple reporter `shiny` app with combined buttons modules: ```{r, eval=requireNamespace("ggplot2")} -ui <- fluidPage( +ui <- bslib::page_fluid( titlePanel(""), sidebarLayout( sidebarPanel(