diff --git a/DESCRIPTION b/DESCRIPTION index 347be82..e79c1e7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: shinyTimer Title: Customizable Timer for 'shiny' Applications -Version: 0.1.0.9002 +Version: 0.1.0.9003 Authors@R: person("Maciej", "Banas", email = "banasmaciek@gmail.com", role = c("aut", "cre")) Description: Provides a customizable timer widget for 'shiny' applications. Key diff --git a/NEWS.md b/NEWS.md index ce2d871..afaf8d8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # shinyTimer (development version) +* Added `color` parameter to `shinyTimer()` and `updateShinyTimer()` functions (#57). * Added new feedback value (text content of the timer) returned after pausing `shinyTimer` (#63). It can be accessed via `input$shinytimer_content`. * Renamed `background` parameter to `frame` to better reflect its purpose. Added `fill` parameter to pass colors to the background of the `shinyTimer` (#60). diff --git a/R/timer.R b/R/timer.R index 8840f9e..2c2b249 100644 --- a/R/timer.R +++ b/R/timer.R @@ -7,6 +7,8 @@ #' @param seconds An integer, the starting time in seconds for the countdown. #' @param type The type of the countdown timer display ("simple", "mm:ss", "hh:mm:ss", "mm:ss.cs"). #' @param frame The shape of the timer's container ("none", "circle", "rectangle"). +#' @param color A CSS color string for text and border. +#' @param fill A CSS color string for the background. #' @param ... Any additional parameters you want to pass to the placeholder for the timer (`htmltools::tags$div`). #' #' @return A shiny UI component for the countdown timer. @@ -25,7 +27,9 @@ #' ) #' } #' @export -shinyTimer <- function(inputId, label = NULL, hours = 0, minutes = 0, seconds = 0, type = "simple", frame = "circle", ...) { +shinyTimer <- function(inputId, label = NULL, hours = 0, minutes = 0, seconds = 0, + type = "simple", frame = "circle", color = "#000", fill = "#111", + ...) { shiny::addResourcePath("shinyTimer", system.file("www", package = "shinyTimer")) if (!type %in% c("simple", "mm:ss", "hh:mm:ss", "mm:ss.cs")) { @@ -36,37 +40,47 @@ shinyTimer <- function(inputId, label = NULL, hours = 0, minutes = 0, seconds = stop("Invalid frame. Choose 'none', 'circle', or 'rectangle'.") } - totalseconds <- (hours * 3600) + (minutes * 60) + seconds + totalseconds <- hours * 3600 + minutes * 60 + seconds initial_display <- switch( type, - "simple" = as.character(totalseconds), - "mm:ss" = sprintf("%02d:%02d", floor(totalseconds / 60), totalseconds %% 60), - "hh:mm:ss" = sprintf("%02d:%02d:%02d", floor(totalseconds / 3600), floor((totalseconds %% 3600) / 60), totalseconds %% 60), - "mm:ss.cs" = sprintf("%02d:%02d.%02d", floor(totalseconds / 60), floor(totalseconds %% 60), 0) + "simple" = as.character(totalseconds), + "mm:ss" = sprintf("%02d:%02d", floor(totalseconds / 60), totalseconds %% 60), + "hh:mm:ss" = sprintf("%02d:%02d:%02d", + floor(totalseconds / 3600), + floor((totalseconds %% 3600) / 60), + totalseconds %% 60), + "mm:ss.cs" = sprintf("%02d:%02d.%02d", + floor(totalseconds / 60), + floor(totalseconds %% 60), + 0) ) frame_class <- switch( frame, - "circle" = "shiny-timer-circle", + "circle" = "shiny-timer-circle", "rectangle" = "shiny-timer-rectangle", - "none" = "" + "none" = "" ) + style_str <- paste0("color:", color, ";border-color:", color, + ";background:", fill, ";") + shiny::tagList( if (!is.null(label)) htmltools::tags$label(label, `for` = inputId), htmltools::tags$div( id = inputId, class = paste("shiny-timer", frame_class), `data-start-time` = totalseconds, - `data-type` = type, + `data-type` = type, + style = style_str, initial_display, ... ), htmltools::tags$script(src = "shinyTimer/timer.js"), htmltools::tags$style(shiny::HTML(" .shiny-timer-circle { - border: 3px solid #ccc; + border: 3px solid; border-radius: 50%; width: 150px; height: 150px; @@ -75,7 +89,7 @@ shinyTimer <- function(inputId, label = NULL, hours = 0, minutes = 0, seconds = justify-content: center; } .shiny-timer-rectangle { - border: 3px solid #ccc; + border: 3px solid; width: 150px; height: 100px; display: flex; @@ -96,43 +110,30 @@ shinyTimer <- function(inputId, label = NULL, hours = 0, minutes = 0, seconds = #' "hh:mm:ss", "mm:ss.cs"). #' @param label The new label to be displayed above the countdown timer. #' @param frame The new shape of the timer's container ("none", "circle", "rectangle"). +#' @param color A new CSS color string for text and border. #' @param session The session object from the shiny server function. #' #' @return No return value, called for side effects. -#' @examples -#' if (interactive()) { -#' library(shiny) -#' shinyApp( -#' ui = fluidPage( -#' shinyTimer("timer", label = "Countdown Timer", seconds = 10, type = "mm:ss"), -#' actionButton("update", "Update Timer") -#' ), -#' server = function(input, output, session) { -#' observeEvent(input$update, { -#' updateShinyTimer("timer", seconds = 20, type = "hh:mm:ss") -#' }) -#' } -#' ) -#' } #' @export -updateShinyTimer <- function(inputId, hours = NULL, minutes = NULL, seconds = NULL, - type = NULL, label = NULL, frame = NULL, +updateShinyTimer <- function(inputId, hours = NULL, minutes = NULL, seconds = NULL, + type = NULL, label = NULL, frame = NULL, color = NULL, session = shiny::getDefaultReactiveDomain()) { message <- list(inputId = inputId) if (!is.null(hours) || !is.null(minutes) || !is.null(seconds)) { total_seconds <- 0 - if (!is.null(hours)) total_seconds <- total_seconds + (hours * 3600) - if (!is.null(minutes)) total_seconds <- total_seconds + (minutes * 60) + if (!is.null(hours)) total_seconds <- total_seconds + hours * 3600 + if (!is.null(minutes)) total_seconds <- total_seconds + minutes * 60 if (!is.null(seconds)) total_seconds <- total_seconds + seconds - message$start = total_seconds + message$start <- total_seconds } - if (!is.null(type)) message$type <- type + if (!is.null(type)) message$type <- type if (!is.null(label)) message$label <- label if (!is.null(frame)) message$frame <- frame + if (!is.null(color)) message$color <- color - session$sendCustomMessage('updateShinyTimer', message) + session$sendCustomMessage("updateShinyTimer", message) } #' Set shinyTimer in motion: count down diff --git a/inst/examples/shinymobile_timer_simple.R b/inst/examples/shinymobile_timer_simple.R index a40f55c..734a7d7 100644 --- a/inst/examples/shinymobile_timer_simple.R +++ b/inst/examples/shinymobile_timer_simple.R @@ -9,6 +9,8 @@ ui <- shinyMobile::f7Page( seconds = 10L, type = "simple", frame = "circle", + color = "green", + fill = "black", style = "font-weight: bold; font-size: 72px;" ) ) diff --git a/inst/www/timer.js b/inst/www/timer.js index b022fe1..5faa9c9 100644 --- a/inst/www/timer.js +++ b/inst/www/timer.js @@ -80,37 +80,34 @@ Shiny.addCustomMessageHandler('resetTimer', function(message) { }); Shiny.addCustomMessageHandler('updateShinyTimer', function(message) { - const { inputId, start, type, label, frame } = message; - const countdownElement = document.getElementById(inputId); - const labelElement = document.querySelector(`label[for=${inputId}]`); - - if (!countdownElement) { - console.error(`Element with ID ${inputId} not found.`); - return; - } - + const { inputId, start, type, label, frame, color } = message; + const el = document.getElementById(inputId); + const lbl = document.querySelector(`label[for=${inputId}]`); + if (!el) return; clearInterval(timerInterval); - if (start !== undefined) { - countdownElement.setAttribute('data-start-time', start); + el.setAttribute('data-start-time', start); pausedTime = start; } else { - pausedTime = parseFloat(countdownElement.getAttribute('data-start-time')); + pausedTime = parseFloat(el.getAttribute('data-start-time')); } - if (type !== undefined) { - countdownElement.setAttribute('data-type', type); + el.setAttribute('data-type', type); } - - countdownElement.textContent = formatTime(pausedTime, countdownElement.getAttribute('data-type')); - + el.textContent = formatTime(pausedTime, el.getAttribute('data-type')); if (frame !== undefined) { - const frameClass = frame === 'circle' ? 'shiny-timer-circle' : - frame === 'rectangle' ? 'shiny-timer-rectangle' : ''; - countdownElement.className = `shiny-timer ${frameClass}`; + const cls = frame === 'circle' + ? 'shiny-timer-circle' + : frame === 'rectangle' + ? 'shiny-timer-rectangle' + : ''; + el.className = `shiny-timer ${cls}`; } - - if (label !== undefined && labelElement) { - labelElement.textContent = label; + if (label !== undefined && lbl) { + lbl.textContent = label; + } + if (color !== undefined) { + el.style.color = color; + el.style.borderColor = color; } }); diff --git a/man/shinyTimer.Rd b/man/shinyTimer.Rd index 851e995..ae86a2d 100644 --- a/man/shinyTimer.Rd +++ b/man/shinyTimer.Rd @@ -12,6 +12,8 @@ shinyTimer( seconds = 0, type = "simple", frame = "circle", + color = "#000", + fill = "#111", ... ) } @@ -30,6 +32,10 @@ shinyTimer( \item{frame}{The shape of the timer's container ("none", "circle", "rectangle").} +\item{color}{A CSS color string for text and border.} + +\item{fill}{A CSS color string for the background.} + \item{...}{Any additional parameters you want to pass to the placeholder for the timer (\code{htmltools::tags$div}).} } \value{ diff --git a/man/updateShinyTimer.Rd b/man/updateShinyTimer.Rd index d99bf07..333088e 100644 --- a/man/updateShinyTimer.Rd +++ b/man/updateShinyTimer.Rd @@ -12,6 +12,7 @@ updateShinyTimer( type = NULL, label = NULL, frame = NULL, + color = NULL, session = shiny::getDefaultReactiveDomain() ) } @@ -31,6 +32,8 @@ updateShinyTimer( \item{frame}{The new shape of the timer's container ("none", "circle", "rectangle").} +\item{color}{A new CSS color string for text and border.} + \item{session}{The session object from the shiny server function.} } \value{ @@ -39,19 +42,3 @@ No return value, called for side effects. \description{ Update shinyTimer widget } -\examples{ -if (interactive()) { - library(shiny) - shinyApp( - ui = fluidPage( - shinyTimer("timer", label = "Countdown Timer", seconds = 10, type = "mm:ss"), - actionButton("update", "Update Timer") - ), - server = function(input, output, session) { - observeEvent(input$update, { - updateShinyTimer("timer", seconds = 20, type = "hh:mm:ss") - }) - } - ) -} -}