Skip to content

Redesign the teal modules #1548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
575201a
feat: implement a dropdown buttons navigation for modules
vedhav Jun 12, 2025
6834958
fix: make the nested modules work
vedhav Jun 12, 2025
db9f7f5
feat: do not allow deep nesting of modules
vedhav Jun 12, 2025
48b3e12
test: add unit test to check for deep nested modules
vedhav Jun 12, 2025
cece6c6
chore: simplify the test
vedhav Jun 12, 2025
ec3ab27
feat: use the bootstrap navigation to simplify the widget
vedhav Jun 19, 2025
45f71b7
chore: rename file + add function to extract reporter (both extractor…
vedhav Jun 19, 2025
885dd0e
chore: reorganize the code
vedhav Jun 19, 2025
499ce40
fix: restore input for bookmarking
vedhav Jun 20, 2025
9f285da
revert: use the older modules structure and only flatten when needed
vedhav Jun 20, 2025
e0e1679
chore: rebuild docs
vedhav Jun 20, 2025
41af8ae
feat: show module groups before the module buttons
vedhav Jun 23, 2025
f032ec1
chore: remove unused function
vedhav Jun 23, 2025
dabfd02
feat: mvp of data load sidebar
vedhav Jun 23, 2025
71dfd2c
feat: display teal_data_module ui before everything
vedhav Jun 23, 2025
886c787
Merge branch 'main' into 1525-redesign-modules@main
vedhav Jun 24, 2025
c91508d
chore: fix tests
vedhav Jun 24, 2025
63caebf
chore: fix ci and update some docs
vedhav Jun 24, 2025
1df8009
chore: fix spellcheck
vedhav Jun 24, 2025
cb92a68
docs: add docs for the sidebar overlay widget
vedhav Jun 24, 2025
dd7c130
chore: remove duplicate operation
vedhav Jun 24, 2025
af997b8
feat: use modals for teal_data_module
vedhav Jun 26, 2025
f57bd8a
fix: click the button using runjs
vedhav Jun 26, 2025
953249c
feat: make the enable/disable state persist
vedhav Jun 26, 2025
f2a22a1
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
github-actions[bot] Jun 26, 2025
eb4bded
feat: enable/disable the close button based on validation
vedhav Jun 26, 2025
60b37ab
chore: simplify the enable/disable logic
vedhav Jun 26, 2025
dab2d14
chore: remove extra enable
vedhav Jun 26, 2025
5ac435b
fix: disable when not resolved
vedhav Jun 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ Collate:
'module_filter_data.R'
'module_filter_manager.R'
'module_init_data.R'
'module_nested_tabs.R'
'module_session_info.R'
'module_snapshot_manager.R'
'module_teal.R'
'module_teal_data.R'
'module_teal_lockfile.R'
'module_teal_navigation.R'
'module_teal_with_splash.R'
'module_transform_data.R'
'reporter_previewer_module.R'
Expand Down
47 changes: 1 addition & 46 deletions R/include_css_js.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,6 @@ include_css_files <- function(pattern = "*") {
)
}

#' Include `JS` files from `/inst/js/` package directory to application header
#'
#' `system.file` should not be used to access files in other packages, it does
#' not work with `devtools`. Therefore, we redefine this method in each package
#' as needed. Thus, we do not export this method
#'
#' @param pattern (`character`) pattern of files to be included, passed to `system.file`
#' @param except (`character`) vector of basename filenames to be excluded
#'
#' @return HTML code that includes `JS` files.
#' @keywords internal
include_js_files <- function(pattern = NULL, except = NULL) {
checkmate::assert_character(except, min.len = 1, any.missing = FALSE, null.ok = TRUE)
js_files <- list.files(system.file("js", package = "teal", mustWork = TRUE), pattern = pattern, full.names = TRUE)
js_files <- js_files[!(basename(js_files) %in% except)] # no-op if except is NULL

singleton(lapply(js_files, includeScript))
}

#' Run `JS` file from `/inst/js/` package directory
#'
#' This is triggered from the server to execute on the client
#' rather than triggered directly on the client.
#' Unlike `include_js_files` which includes `JavaScript` functions,
#' the `run_js` actually executes `JavaScript` functions.
#'
#' `system.file` should not be used to access files in other packages, it does
#' not work with `devtools`. Therefore, we redefine this method in each package
#' as needed. Thus, we do not export this method.
#'
#' @param files (`character`) vector of filenames.
#'
#' @return `NULL`, invisibly.
#' @keywords internal
run_js_files <- function(files) {
checkmate::assert_character(files, min.len = 1, any.missing = FALSE)
lapply(files, function(file) {
shinyjs::runjs(paste0(readLines(system.file("js", file, package = "teal", mustWork = TRUE)), collapse = "\n"))
})
invisible(NULL)
}

#' Code to include `teal` `CSS` and `JavaScript` files
#'
#' This is useful when you want to use the same `JavaScript` and `CSS` files that are
Expand All @@ -75,9 +33,6 @@ run_js_files <- function(files) {
include_teal_css_js <- function() {
tagList(
shinyjs::useShinyjs(),
include_css_files(),
# init.js is executed from the server
include_js_files(except = "init.js"),
shinyjs::hidden(icon("fas fa-gear")), # add hidden icon to load font-awesome css for icons
include_css_files()
)
}
136 changes: 73 additions & 63 deletions R/module_teal.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,54 +51,7 @@ ui_teal <- function(id, modules) {
checkmate::assert_class(modules, "teal_modules")
ns <- NS(id)

modules <- append_reporter_module(modules)

# show busy icon when `shiny` session is busy computing stuff
# based on https://stackoverflow.com/questions/17325521/r-shiny-display-loading-message-while-function-is-running/22475216#22475216 # nolint: line_length.
shiny_busy_message_panel <- conditionalPanel(
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint: line_length.
tags$div(
icon("arrows-rotate", class = "fa-spin", prefer_type = "solid"),
"Computing ...",
style = "position: fixed; bottom: 0; right: 0;
width: 140px; margin: 15px; padding: 5px 0 5px 10px;
text-align: left; font-weight: bold; font-size: 100%;
color: #ffffff; background-color: #347ab7; z-index: 105;"
)
)

bslib::page_fluid(
id = id,
theme = get_teal_bs_theme(),
include_teal_css_js(),
shiny_busy_message_panel,
tags$div(
id = ns("tabpanel_wrapper"),
class = "teal-body",
ui_teal_module(id = ns("teal_modules"), modules = modules)
),
tags$div(
id = ns("options_buttons"),
style = "position: absolute; right: 10px;",
ui_bookmark_panel(ns("bookmark_manager"), modules),
ui_snapshot_manager_panel(ns("snapshot_manager_panel")),
ui_filter_manager_panel(ns("filter_manager_panel"))
),
tags$script(
HTML(
sprintf(
"
$(document).ready(function() {
$('#%s').appendTo('#%s');
});
",
ns("options_buttons"),
ns("teal_modules-active_tab")
)
)
),
tags$hr(style = "margin: 1rem 0 0.5rem 0;")
)
uiOutput(ns("main_teal_ui"))
}

#' @rdname module_teal
Expand All @@ -109,7 +62,9 @@ srv_teal <- function(id, data, modules, filter = teal_slices()) {
checkmate::assert_class(modules, "teal_modules")
checkmate::assert_class(filter, "teal_slices")

modules <- drop_module(modules, "teal_module_landing")
modules <- append_reporter_module(modules)
teal_data_resolved_state <- reactiveVal(FALSE)

moduleServer(id, function(input, output, session) {
logger::log_debug("srv_teal initializing.")
Expand All @@ -118,8 +73,40 @@ srv_teal <- function(id, data, modules, filter = teal_slices()) {
shinyjs::showLog()
}

# `JavaScript` code
run_js_files(files = "init.js")
output$main_teal_ui <- renderUI({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that data module is a modal it is not needed so much to keep it in the server. IMO this can be moved to the UI

# show busy icon when `shiny` session is busy computing stuff
# based on https://stackoverflow.com/questions/17325521/r-shiny-display-loading-message-while-function-is-running/22475216#22475216 # nolint: line_length.
shiny_busy_message_panel <- conditionalPanel(
condition = "(($('html').hasClass('shiny-busy')) && (document.getElementById('shiny-notification-panel') == null))", # nolint: line_length.
tags$div(
icon("arrows-rotate", class = "fa-spin", prefer_type = "solid"),
"Computing ...",
style = "position: fixed; bottom: 0; right: 0;
width: 140px; margin: 15px; padding: 5px 0 5px 10px;
text-align: left; font-weight: bold; font-size: 100%;
color: #ffffff; background-color: #347ab7; z-index: 105;"
)
)
bslib::page_fluid(
id = id,
theme = get_teal_bs_theme(),
include_teal_css_js(),
shiny_busy_message_panel,
tags$div(
id = session$ns("options_buttons"),
style = "position: absolute; right: 10px;",
ui_bookmark_panel(session$ns("bookmark_manager"), modules),
ui_snapshot_manager_panel(session$ns("snapshot_manager_panel")),
ui_filter_manager_panel(session$ns("filter_manager_panel"))
),
tags$div(
id = session$ns("tabpanel_wrapper"),
class = "teal-body",
ui_teal_module(id = session$ns("teal_modules"), modules = modules)
),
tags$hr(style = "margin: 1rem 0 0.5rem 0;")
)
})

# set timezone in shiny app
# timezone is set in the early beginning so it will be available also
Expand Down Expand Up @@ -165,8 +152,12 @@ srv_teal <- function(id, data, modules, filter = teal_slices()) {

data_load_status <- reactive({
if (inherits(data_handled(), "teal_data")) {
shinyjs::enable(id = "close_teal_data_module_modal")
teal_data_resolved_state(TRUE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem to be needed as data_load_status holds the same information

Suggested change
teal_data_resolved_state(TRUE)

"ok"
} else if (inherits(data, "teal_data_module")) {
shinyjs::disable(id = "close_teal_data_module_modal")
teal_data_resolved_state(FALSE)
"teal_data_module failed"
} else {
"external failed"
Expand All @@ -182,28 +173,47 @@ srv_teal <- function(id, data, modules, filter = teal_slices()) {
}



if (inherits(data, "teal_data_module")) {
setBookmarkExclude(c("teal_modules-active_tab"))
bslib::nav_insert(
id = "teal_modules-active_tab",
position = "before",
select = TRUE,
bslib::nav_panel(
title = icon("fas fa-database"),
value = "teal_data_module",
tags$div(
ui_init_data(session$ns("data")),
validate_ui
insertUI(
selector = ".teal-modules-wrapper .nav-item-custom",
where = "beforeBegin",
actionButton(session$ns("open_teal_data_module_ui"), NULL, icon = icon("database"))
)
observeEvent(input$open_teal_data_module_ui, ignoreInit = TRUE, ignoreNULL = FALSE, {
showModal(
div(
class = "teal teal-data-module-popup",
modalDialog(
id = session$ns("teal_data_module_ui"),
size = "xl",
tags$div(
ui_init_data(session$ns("data")),
validate_ui
),
easyClose = FALSE,
footer = tags$div(id = session$ns("close_teal_data_module_modal"), modalButton("Dismiss"))
)
)
)
)
if (teal_data_resolved_state()) {
shinyjs::enable(id = "close_teal_data_module_modal")
} else {
shinyjs::disable(id = "close_teal_data_module_modal")
}
})

if (attr(data, "once")) {
observeEvent(data_signatured(), once = TRUE, {
logger::log_debug("srv_teal@2 removing data tab.")
# when once = TRUE we pull data once and then remove data tab
removeTab("teal_modules-active_tab", target = "teal_data_module")
shinyjs::runjs(
sprintf(
"document.querySelector('#%s button').click();",
session$ns("close_teal_data_module_modal")
)
)
removeUI(sprintf("#%s", session$ns("open_teal_data_module_ui")))
})
}
} else {
Expand Down
Loading
Loading