Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -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
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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).

Expand Down
67 changes: 34 additions & 33 deletions R/timer.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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")) {
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions inst/examples/shinymobile_timer_simple.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ ui <- shinyMobile::f7Page(
seconds = 10L,
type = "simple",
frame = "circle",
color = "green",
fill = "black",
style = "font-weight: bold; font-size: 72px;"
)
)
Expand Down
43 changes: 20 additions & 23 deletions inst/www/timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});
6 changes: 6 additions & 0 deletions man/shinyTimer.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 3 additions & 16 deletions man/updateShinyTimer.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading