diff --git a/DESCRIPTION b/DESCRIPTION index af6527b..5bc40b1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -27,6 +27,7 @@ Imports: DT, dygraphs, ggplot2 (>= 3.0.0), + htmltools, magrittr, plotly, stats, @@ -36,4 +37,4 @@ Suggests: Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.3.3 diff --git a/NAMESPACE b/NAMESPACE index 69040ae..06f4b57 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,12 +7,14 @@ export(random_dygraph) export(random_ggplot) export(random_ggplotly) export(random_image) +export(random_image_ext) export(random_lm) export(random_print) export(random_table) export(random_text) importFrom(DT,datatable) importFrom(attempt,stop_if_all) +importFrom(attempt,stop_if_not) importFrom(dygraphs,dygraph) importFrom(ggplot2,aes) importFrom(ggplot2,coord_flip) @@ -42,6 +44,7 @@ importFrom(ggplot2,stat) importFrom(ggplot2,theme_minimal) importFrom(ggplot2,xlim) importFrom(ggplot2,ylim) +importFrom(htmltools,img) importFrom(magrittr,"%>%") importFrom(plotly,ggplotly) importFrom(stats,HoltWinters) @@ -50,3 +53,4 @@ importFrom(stats,lm) importFrom(stats,predict) importFrom(stats,rnorm) importFrom(stats,shapiro.test) +importFrom(utils,URLencode) diff --git a/NEWS.md b/NEWS.md index 2f5d6e2..830081e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,6 @@ # shinipsum 0.0.0.9000 +* New `random_image_ext()`: returns an `` tag pointing at the + [Lorem Picsum](https://picsum.photos/) API, with `width` / `height` / `seed` + arguments, for quick Shiny UI prototyping (#8, thanks @feddelegrand7). * Added a `NEWS.md` file to track changes to the package. diff --git a/R/random_image_ext.R b/R/random_image_ext.R new file mode 100644 index 0000000..6afca73 --- /dev/null +++ b/R/random_image_ext.R @@ -0,0 +1,49 @@ +#' A Random External Image from the Lorem Picsum API +#' +#' Returns an `` tag pointing at a random image served by the +#' [Lorem Picsum](https://picsum.photos/) API, ready to be dropped inside a +#' Shiny UI for quick prototyping. No network request is performed by this +#' function: only the URL is built, the image is fetched by the browser. +#' +#' @param width,height image dimensions, in pixels. Single positive +#' (finite, whole) numbers. +#' @param seed optional seed making the picked image stable across calls. Any +#' atomic scalar; it is coerced to a string and URL-encoded. When `NULL` +#' (default), a fresh random image is served on every request. +#' +#' @importFrom htmltools img +#' @importFrom attempt stop_if_not +#' @importFrom utils URLencode +#' +#' @return an `` [htmltools::tag][htmltools::tags] +#' +#' @export +#' +#' @examples +#' random_image_ext() +#' random_image_ext(width = 400, height = 600, seed = "caramba") +random_image_ext <- function(width = 400, height = 400, seed = NULL) { + valid_dim <- function(.x) { + is.numeric(.x) && length(.x) == 1L && is.finite(.x) && + .x >= 1 && .x == round(.x) + } + stop_if_not(width, valid_dim, "`width` must be a single positive integer") + stop_if_not(height, valid_dim, "`height` must be a single positive integer") + + if (is.null(seed)) { + src <- sprintf("https://picsum.photos/%.0f/%.0f", width, height) + } else { + stop_if_not( + seed, + ~ is.atomic(.x) && length(.x) == 1L && !is.na(.x), + "`seed` must be a single non-missing atomic value (or NULL)" + ) + src <- sprintf( + "https://picsum.photos/seed/%s/%.0f/%.0f", + URLencode(as.character(seed), reserved = TRUE), + width, height + ) + } + + img(src = src) +} diff --git a/man/random_image_ext.Rd b/man/random_image_ext.Rd new file mode 100644 index 0000000..4a0602c --- /dev/null +++ b/man/random_image_ext.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/random_image_ext.R +\name{random_image_ext} +\alias{random_image_ext} +\title{A Random External Image from the Lorem Picsum API} +\usage{ +random_image_ext(width = 400, height = 400, seed = NULL) +} +\arguments{ +\item{width, height}{image dimensions, in pixels. Single positive +(finite, whole) numbers.} + +\item{seed}{optional seed making the picked image stable across calls. Any +atomic scalar; it is coerced to a string and URL-encoded. When \code{NULL} +(default), a fresh random image is served on every request.} +} +\value{ +an \verb{} \link[htmltools:builder]{htmltools::tag} +} +\description{ +Returns an \verb{} tag pointing at a random image served by the +\href{https://picsum.photos/}{Lorem Picsum} API, ready to be dropped inside a +Shiny UI for quick prototyping. No network request is performed by this +function: only the URL is built, the image is fetched by the browser. +} +\examples{ +random_image_ext() +random_image_ext(width = 400, height = 600, seed = "caramba") +} diff --git a/man/shinipsum-package.Rd b/man/shinipsum-package.Rd index b0b0ca3..0fa84ce 100644 --- a/man/shinipsum-package.Rd +++ b/man/shinipsum-package.Rd @@ -6,8 +6,7 @@ \alias{shinipsum-package} \title{shinipsum: Lorem-Ipsum-like Helpers for fast Shiny Prototyping} \description{ -Prototype your shiny apps quickly with these - Lorem-Ipsum-like Helpers. +Prototype your shiny apps quickly with these Lorem-Ipsum-like Helpers. } \seealso{ Useful links: diff --git a/tests/testthat/test-image-ext.R b/tests/testthat/test-image-ext.R new file mode 100644 index 0000000..f900954 --- /dev/null +++ b/tests/testthat/test-image-ext.R @@ -0,0 +1,59 @@ +context("test-image-ext.R") + +test_that("random_image_ext returns an tag pointing at picsum", { + i <- random_image_ext() + expect_is(i, "shiny.tag") + expect_equal(i$name, "img") + expect_equal(i$attribs$src, "https://picsum.photos/400/400") +}) + +test_that("width and height go into the URL", { + expect_equal( + random_image_ext(width = 200, height = 300)$attribs$src, + "https://picsum.photos/200/300" + ) +}) + +test_that("seed is included and URL-encoded", { + expect_equal( + random_image_ext(seed = "caramba")$attribs$src, + "https://picsum.photos/seed/caramba/400/400" + ) + expect_equal( + random_image_ext(width = 100, height = 150, seed = "a b/c")$attribs$src, + "https://picsum.photos/seed/a%20b%2Fc/100/150" + ) +}) + +test_that("width / height are validated", { + expect_error(random_image_ext(width = 0), "width") + expect_error(random_image_ext(width = -10), "width") + expect_error(random_image_ext(width = 1.5), "width") + expect_error(random_image_ext(width = c(100, 200)), "width") + expect_error(random_image_ext(width = NA), "width") + expect_error(random_image_ext(width = "100"), "width") + expect_error(random_image_ext(height = 0), "height") + expect_error(random_image_ext(height = "x"), "height") + # tricky numerics fail with the friendly message, not a low-level error + expect_error(random_image_ext(width = Inf), "width") + expect_error(random_image_ext(width = NaN), "width") + expect_error(random_image_ext(width = 1e400), "width") + expect_error(random_image_ext(width = 1 + 0i), "width") + expect_error(random_image_ext(width = TRUE), "width") +}) + +test_that("seed is validated", { + expect_error(random_image_ext(seed = c("a", "b")), "seed") + expect_error(random_image_ext(seed = NA), "seed") + expect_error(random_image_ext(seed = character(0)), "seed") + expect_error(random_image_ext(seed = list("a")), "seed") + # numeric / other atomic scalars are coerced to character + expect_equal( + random_image_ext(seed = 42)$attribs$src, + "https://picsum.photos/seed/42/400/400" + ) + expect_equal( + random_image_ext(seed = TRUE)$attribs$src, + "https://picsum.photos/seed/TRUE/400/400" + ) +})