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{