Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79f42f5846 | ||
|
|
9a35b01e23 | ||
|
|
5bf0701939 | ||
|
|
e5083f4938 | ||
|
|
ce6a562a3c | ||
|
|
b6bcfc8683 | ||
|
|
d37beeece7 | ||
|
|
79ee25620f | ||
|
|
82c678a1eb | ||
|
|
458924569a | ||
|
|
501b012b2b | ||
|
|
ee1aac847a | ||
|
|
7785a76a67 | ||
|
|
79af1d6c92 | ||
|
|
a145add5d4 | ||
|
|
abf71389be | ||
|
|
2e2114f99d | ||
|
|
09d415502f | ||
|
|
c489fef4ff | ||
|
|
9d12b0fca7 | ||
|
|
cc9b9d4e6a | ||
|
|
34f9e4484d | ||
|
|
03a3f8f886 | ||
|
|
b900db0c74 | ||
|
|
5fb3ebc2d9 | ||
|
|
fbc6b2df57 | ||
|
|
6208225354 | ||
|
|
e22b693418 | ||
|
|
c7ca49c634 | ||
|
|
d84aa94762 | ||
|
|
89e2c18531 | ||
|
|
43d36c08dc | ||
|
|
4bc330e5dd | ||
|
|
56ab530d87 | ||
|
|
599209a036 | ||
|
|
15b5fa6c01 | ||
|
|
3f4676d9a6 | ||
|
|
bb89cf9235 | ||
|
|
25c40967da | ||
|
|
068b232e75 | ||
|
|
0b7fda707e | ||
|
|
9fd4ba199e | ||
|
|
43e40c7969 | ||
|
|
248f19333c | ||
|
|
306c4f847b | ||
|
|
e689cdc522 | ||
|
|
3e0efd8484 | ||
|
|
4a8400d2a5 | ||
|
|
e432bb0592 | ||
|
|
d002734afe | ||
|
|
54e7377f24 | ||
|
|
a49d24108f | ||
|
|
733a4e8983 | ||
|
|
6309a6fca3 | ||
|
|
3d66940402 | ||
|
|
2872c87e32 | ||
|
|
ecb591f2e1 | ||
|
|
8e37d45948 | ||
|
|
c11f120bb9 |
@@ -35,10 +35,6 @@ rules:
|
||||
|
||||
default-case:
|
||||
- error
|
||||
indent:
|
||||
- error
|
||||
- 2
|
||||
- SwitchCase: 1
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name : Ask a Question
|
||||
about : The issue tracker is not for questions -- please ask questions at https://community.rstudio.com/c/shiny.
|
||||
about : The issue tracker is not for questions -- please ask questions at https://forum.posit.co/tags/shiny.
|
||||
---
|
||||
|
||||
The issue tracker is not for questions. If you have a question, please feel free to ask it on our community site, at https://community.rstudio.com/c/shiny.
|
||||
The issue tracker is not for questions. If you have a question, please feel free to ask it on our community site, at https://forum.posit.co/c/shiny.
|
||||
|
||||
|
||||
1
.github/shiny-workflows/routine.sh
vendored
@@ -5,6 +5,7 @@ echo "Updating package.json version to match DESCRIPTION Version"
|
||||
Rscript ./tools/updatePackageJsonVersion.R
|
||||
if [ -n "$(git status --porcelain package.json)" ]
|
||||
then
|
||||
echo "package.json has changed after running ./tools/updatePackageJsonVersion.R. Re-running 'yarn build'"
|
||||
yarn build
|
||||
git add ./inst package.json && git commit -m 'Sync package version (GitHub Actions)' || echo "No package version to commit"
|
||||
else
|
||||
|
||||
1
.vscode/extensions.json
vendored
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
|
||||
6
.vscode/settings.json
vendored
@@ -15,4 +15,10 @@
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
},
|
||||
"[json]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
},
|
||||
}
|
||||
|
||||
13
DESCRIPTION
@@ -1,7 +1,7 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.8.1.9000
|
||||
Version: 1.10.0
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@posit.co", comment = c(ORCID = "0000-0002-1576-2126")),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@posit.co"),
|
||||
@@ -83,7 +83,7 @@ Imports:
|
||||
R6 (>= 2.0),
|
||||
sourcetools,
|
||||
later (>= 1.0.0),
|
||||
promises (>= 1.1.0),
|
||||
promises (>= 1.3.2),
|
||||
tools,
|
||||
crayon,
|
||||
rlang (>= 0.4.10),
|
||||
@@ -91,10 +91,11 @@ Imports:
|
||||
withr,
|
||||
commonmark (>= 1.7),
|
||||
glue (>= 1.3.2),
|
||||
bslib (>= 0.3.0),
|
||||
cachem,
|
||||
bslib (>= 0.6.0),
|
||||
cachem (>= 1.1.0),
|
||||
lifecycle (>= 0.2.0)
|
||||
Suggests:
|
||||
coro (>= 1.1.0),
|
||||
datasets,
|
||||
DT,
|
||||
Cairo (>= 1.5-5),
|
||||
@@ -128,6 +129,8 @@ Collate:
|
||||
'map.R'
|
||||
'utils.R'
|
||||
'bootstrap.R'
|
||||
'busy-indicators-spinners.R'
|
||||
'busy-indicators.R'
|
||||
'cache-utils.R'
|
||||
'deprecated.R'
|
||||
'devmode.R'
|
||||
@@ -204,7 +207,7 @@ Collate:
|
||||
'version_selectize.R'
|
||||
'version_strftime.R'
|
||||
'viewer.R'
|
||||
RoxygenNote: 7.3.1
|
||||
RoxygenNote: 7.3.2
|
||||
Encoding: UTF-8
|
||||
Roxygen: list(markdown = TRUE)
|
||||
RdMacros: lifecycle
|
||||
|
||||
@@ -78,6 +78,7 @@ export(br)
|
||||
export(browserViewer)
|
||||
export(brushOpts)
|
||||
export(brushedPoints)
|
||||
export(busyIndicatorOptions)
|
||||
export(callModule)
|
||||
export(captureStackTraces)
|
||||
export(checkboxGroupInput)
|
||||
@@ -215,6 +216,7 @@ export(reactiveVal)
|
||||
export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(reactlog)
|
||||
export(reactlogAddMark)
|
||||
export(reactlogReset)
|
||||
export(reactlogShow)
|
||||
export(registerInputHandler)
|
||||
@@ -317,6 +319,7 @@ export(updateTextInput)
|
||||
export(updateVarSelectInput)
|
||||
export(updateVarSelectizeInput)
|
||||
export(urlModal)
|
||||
export(useBusyIndicators)
|
||||
export(validate)
|
||||
export(validateCssUnit)
|
||||
export(varSelectInput)
|
||||
|
||||
78
NEWS.md
@@ -1,9 +1,73 @@
|
||||
# shiny (development version)
|
||||
# shiny 1.10.0
|
||||
|
||||
## New features and improvements
|
||||
|
||||
* When busy indicators are enabled (i.e., `useBusyIndicators()` is in the UI), Shiny now:
|
||||
* Shows the pulse indicator when dynamic UI elements are recalculating and no other spinners are visible in the app. (#4137)
|
||||
* Makes the pulse indicator slightly smaller by default and improves its appearance to better blend with any background. (#4122)
|
||||
|
||||
* Improve collection of deep stack traces (stack traces that are tracked across steps in an async promise chain) with `{coro}` async generators such as `{elmer}` chat streams. Previously, Shiny treated each iteration of an async generator as a distinct deep stack, leading to pathologically long stack traces; now, Shiny only keeps/prints unique deep stack trace, discarding duplicates. (#4156)
|
||||
|
||||
* Added an example to the `ExtendedTask` documentation. (@daattali #4087)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixed a bug in `conditionalPanel()` that would cause the panel to repeatedly show/hide itself when the provided condition was not boolean. (@kamilzyla, #4127)
|
||||
|
||||
* Fixed a bug with `sliderInput()` when used as a range slider that made it impossible to change the slider value when both handles were at the maximum value. (#4131)
|
||||
|
||||
* `dateInput()` and `dateRangeInput()` no longer send immediate updates to the server when the user is typing a date input. Instead, it waits until the user presses Enter or clicks out of the field to send the update, avoiding spurious and incorrect date values. Note that an update is still sent immediately when the field is cleared. (#3664)
|
||||
|
||||
* Fixed a bug in `onBookmark()` hook that caused elements to not be excluded from URL bookmarking. (#3762)
|
||||
|
||||
* Fixed a bug with stack trace capturing that caused reactives with very long async promise chains (hundreds/thousands of steps) to become extremely slow. Chains this long are unlikely to be written by hand, but `{coro}` async generators and `{elmer}` async streaming were easily creating problematically long chains. (#4155)
|
||||
|
||||
* Duplicate input and output IDs -- e.g. using `"debug"` for two inputs or two outputs -- or shared IDs -- e.g. using `"debug"` as the `inputId` for an input and an output -- now result in a console warning message, but not an error. When `devmode()` is enabled, an informative message is shown in the Shiny Client Console. We recommend all Shiny devs enable `devmode()` when developing Shiny apps locally. (#4101)
|
||||
|
||||
* Updating the choices of a `selectizeInput()` via `updateSelectizeInput()` with `server = TRUE` no longer retains the selected choice as a deselected option if the current value is not part of the new choices. (@dvg-p4 #4142)
|
||||
|
||||
* Fixed a bug where stack traces from `observeEvent()` were being stripped of stack frames too aggressively. (#4163)
|
||||
|
||||
# shiny 1.9.1
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixed a bug introduced in v1.9.0 where the boundaries of hover/click/brush regions on plots were being incorrectly scaled when browser zoom was used. (#4111)
|
||||
|
||||
# shiny 1.9.0
|
||||
|
||||
## New busy indication feature
|
||||
|
||||
Add the new `useBusyIndicators()` function to any UI definition to:
|
||||
1. Add a spinner overlay on calculating/recalculating outputs.
|
||||
2. Show a page-level pulsing banner when Shiny is busy calculating something (e.g., a download, side-effect, etc), but no calculating/recalculating outputs are visible.
|
||||
|
||||
In a future version of Shiny, busy indication will be enabled by default, so we encourage you to try it out now, provide feedback, and report any issues.
|
||||
|
||||
In addition, various properties of the spinners and pulse can be customized with `busyIndicatorOptions()`. For more details, see `?busyIndicatorOptions`. (#4040, #4104)
|
||||
|
||||
## New features and improvements
|
||||
|
||||
* The client-side TypeScript code for Shiny has been refactored so that the `Shiny` object is now an instance of class `ShinyClass`. (#4063)
|
||||
|
||||
* In TypeScript, the `Shiny` object has a new property `initializedPromise`, which is a Promise-like object that can be `await`ed or chained with `.then()`. This Promise-like object corresponds to the `shiny:sessioninitialized` JavaScript event, but is easier to use because it can be used both before and after the events have occurred. (#4063)
|
||||
|
||||
* Output bindings now include the `.recalculating` CSS class when they are first bound, up until the first render. This makes it possible/easier to show progress indication when the output is calculating for the first time. (#4039)
|
||||
|
||||
* A new `shiny.client_devmode` option controls client-side devmode features, in particular the client-side error console introduced in shiny 1.8.1, independently of the R-side features of `shiny::devmode()`. This usage is primarily intended for automatic use in Shinylive. (#4073)
|
||||
|
||||
* Added function `reactlogAddMark()` to programmatically add _mark_ed locations in the reactlog log without the requirement of keyboard bindings during an idle reactive moment. (#4103)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* `downloadButton()` and `downloadLink()` are now disabled up until they are fully initialized. This prevents the user from clicking the button/link before the download is ready. (#4041)
|
||||
|
||||
* Output bindings that are removed, invalidated, then inserted again (while invalidated) now correctly include the `.recalculating` CSS class. (#4039)
|
||||
|
||||
* Fixed a recent issue with `uiOutput()` and `conditionalPanel()` not properly lower opacity when recalculation (in a Bootstrap 5 context). (#4027)
|
||||
|
||||
* Image outputs that were scaled by CSS had certain regions that were unresponsive to hover/click/brush handlers. (#3234)
|
||||
|
||||
# shiny 1.8.1.1
|
||||
|
||||
* In v1.8.1, shiny.js starting throwing an error when input/output bindings have duplicate IDs. This error is now only thrown when `shiny::devmode(TRUE)` is enabled, so the issue is still made discoverable through the JS error console, but avoids unnecessarily breaking apps that happen to work with duplicate IDs. (#4019)
|
||||
@@ -16,7 +80,7 @@
|
||||
|
||||
* Added a JavaScript error dialog, reporting errors that previously were only discoverable by opening the browser's devtools open. Since this dialog is mainly useful for debugging and development, it must be enabled with `shiny::devmode()`. (#3931)
|
||||
|
||||
* `runExamples()` now uses the `{bslib}` package to generate a better looking result. It also gains a `package` argument so that other packages can leverage this same function to run Shiny app examples. For more, see `?runExamples`. (#3963, #4005)
|
||||
* `runExample()` now uses the `{bslib}` package to generate a better looking result. It also gains a `package` argument so that other packages can leverage this same function to run Shiny app examples. For more, see `?runExample`. (#3963, #4005)
|
||||
|
||||
* Added `onUnhandledError()` to register a function that will be called when an unhandled error occurs in a Shiny app. Note that this handler doesn't stop the error or prevent the session from closing, but it can be used to log the error or to clean up session-specific resources. (thanks @JohnCoene, #3993)
|
||||
|
||||
@@ -986,7 +1050,7 @@ Shiny can now display notifications on the client browser by using the `showNoti
|
||||
<img src="http://shiny.rstudio.com/images/notification.png" alt="notification" width="50%"/>
|
||||
</p>
|
||||
|
||||
[Here](https://shiny.rstudio.com/articles/notifications.html)'s our article about it, and the [reference documentation](https://shiny.rstudio.com/reference/shiny/latest/showNotification.html).
|
||||
[Here](https://shiny.rstudio.com/articles/notifications.html)'s our article about it, and the [reference documentation](https://shiny.posit.co/r/reference/shiny/latest/shownotification.html).
|
||||
|
||||
## Progress indicators
|
||||
|
||||
@@ -995,7 +1059,7 @@ If your Shiny app contains computations that take a long time to complete, a pro
|
||||
**_Important note_:**
|
||||
> If you were already using progress bars and had customized them with your own CSS, you can add the `style = "old"` argument to your `withProgress()` call (or `Progress$new()`). This will result in the same appearance as before. You can also call `shinyOptions(progress.style = "old")` in your app's server function to make all progress indicators use the old styling.
|
||||
|
||||
To see new progress bars in action, see [this app](https://gallery.shinyapps.io/085-progress/) in the gallery. You can also learn more about this in [our article](https://shiny.rstudio.com/articles/progress.html) and in the reference documentation (either for the easier [`withProgress` functional API](https://shiny.rstudio.com/reference/shiny/latest/withProgress.html) or the more complicated, but more powerful, [`Progress` object-oriented API](https://shiny.rstudio.com/reference/shiny/latest/Progress.html).
|
||||
To see new progress bars in action, see [this app](https://gallery.shinyapps.io/085-progress/) in the gallery. You can also learn more about this in [our article](https://shiny.rstudio.com/articles/progress.html) and in the reference documentation (either for the easier [`withProgress` functional API](https://shiny.posit.co/r/reference/shiny/latest/withprogress.html) or the more complicated, but more powerful, [`Progress` object-oriented API](https://shiny.posit.co/r/reference/shiny/latest/progress.html).
|
||||
|
||||
## Reconnection
|
||||
|
||||
@@ -1009,7 +1073,7 @@ Shiny has now built-in support for displaying modal dialogs like the one below (
|
||||
<img src="http://shiny.rstudio.com/images/modal-dialog.png" alt="modal-dialog" width="50%"/>
|
||||
</p>
|
||||
|
||||
To learn more about this, read [our article](https://shiny.rstudio.com/articles/modal-dialogs.html) and the [reference documentation](https://shiny.rstudio.com/reference/shiny/latest/modalDialog.html).
|
||||
To learn more about this, read [our article](https://shiny.rstudio.com/articles/modal-dialogs.html) and the [reference documentation](https://shiny.posit.co/r/reference/shiny/latest/modaldialog.html).
|
||||
|
||||
## `insertUI` and `removeUI`
|
||||
|
||||
@@ -1017,7 +1081,7 @@ Sometimes in a Shiny app, arbitrary HTML UI may need to be created on-the-fly in
|
||||
|
||||
See [this simple demo app](https://gallery.shinyapps.io/111-insert-ui/) of how one could use `insertUI` and `removeUI` to insert and remove text elements using a queue. Also see [this other app](https://gallery.shinyapps.io/insertUI/) that demonstrates how to insert and remove a few common Shiny input objects. Finally, [this app](https://gallery.shinyapps.io/insertUI-modules/) shows how to dynamically insert modules using `insertUI`.
|
||||
|
||||
For more, read [our article](https://shiny.rstudio.com/articles/dynamic-ui.html) about dynamic UI generation and the reference documentation about [`insertUI`](https://shiny.rstudio.com/reference/shiny/latest/insertUI.html) and [`removeUI`](https://shiny.rstudio.com/reference/shiny/latest/insertUI.html).
|
||||
For more, read [our article](https://shiny.rstudio.com/articles/dynamic-ui.html) about dynamic UI generation and the reference documentation about [`insertUI`](https://shiny.posit.co/r/reference/shiny/latest/insertui.html) and [`removeUI`](https://shiny.posit.co/r/reference/shiny/latest/insertui.html).
|
||||
|
||||
## Documentation for connecting to an external database
|
||||
|
||||
@@ -1051,7 +1115,7 @@ There are many more minor features, small improvements, and bug fixes than we ca
|
||||
<img src="http://shiny.rstudio.com/images/render-table.png" alt="render-table" width="75%"/>
|
||||
</p>
|
||||
|
||||
For more, read our [short article](https://shiny.rstudio.com/articles/render-table.html) about this update, experiment with all the new features in this [demo app](https://gallery.shinyapps.io/109-render-table/), or check out the [reference documentation](https://shiny.rstudio.com/reference/shiny/latest/renderTable.html).
|
||||
For more, read our [short article](https://shiny.rstudio.com/articles/render-table.html) about this update, experiment with all the new features in this [demo app](https://gallery.shinyapps.io/109-render-table/), or check out the [reference documentation](https://shiny.posit.co/r/reference/shiny/latest/rendertable.html).
|
||||
|
||||
## Full changelog
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
|
||||
#' cache by putting this at the top of your app.R, server.R, or global.R:
|
||||
#'
|
||||
#' ```
|
||||
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
|
||||
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
|
||||
#' ```
|
||||
#'
|
||||
#' This will create a subdirectory in your system temp directory named
|
||||
|
||||
@@ -99,13 +99,13 @@ saveShinySaveState <- function(state) {
|
||||
|
||||
# Encode the state to a URL. This does not save to disk.
|
||||
encodeShinySaveState <- function(state) {
|
||||
exclude <- c(state$exclude, "._bookmark_")
|
||||
inputVals <- serializeReactiveValues(state$input, exclude, stateDir = NULL)
|
||||
|
||||
# Allow user-supplied onSave function to do things like add state$values.
|
||||
if (!is.null(state$onSave))
|
||||
isolate(state$onSave(state))
|
||||
|
||||
exclude <- c(state$exclude, "._bookmark_")
|
||||
inputVals <- serializeReactiveValues(state$input, exclude, stateDir = NULL)
|
||||
|
||||
inputVals <- vapply(inputVals,
|
||||
function(x) toJSON(x, strict_atomic = FALSE),
|
||||
character(1),
|
||||
|
||||
@@ -172,9 +172,10 @@ setCurrentTheme <- function(theme) {
|
||||
|
||||
#' Register a theme dependency
|
||||
#'
|
||||
#' This function registers a function that returns an [htmlDependency()] or list
|
||||
#' of such objects. If `session$setCurrentTheme()` is called, the function will
|
||||
#' be re-executed, and the resulting html dependency will be sent to the client.
|
||||
#' This function registers a function that returns an
|
||||
#' [htmltools::htmlDependency()] or list of such objects. If
|
||||
#' `session$setCurrentTheme()` is called, the function will be re-executed, and
|
||||
#' the resulting html dependency will be sent to the client.
|
||||
#'
|
||||
#' Note that `func` should **not** be an anonymous function, or a function which
|
||||
#' is defined within the calling function. This is so that,
|
||||
@@ -1276,10 +1277,13 @@ downloadButton <- function(outputId,
|
||||
...,
|
||||
icon = shiny::icon("download")) {
|
||||
tags$a(id=outputId,
|
||||
class=paste('btn btn-default shiny-download-link', class),
|
||||
class='btn btn-default shiny-download-link disabled',
|
||||
class=class,
|
||||
href='',
|
||||
target='_blank',
|
||||
download=NA,
|
||||
"aria-disabled"="true",
|
||||
tabindex="-1",
|
||||
validateIcon(icon),
|
||||
label, ...)
|
||||
}
|
||||
@@ -1288,10 +1292,13 @@ downloadButton <- function(outputId,
|
||||
#' @export
|
||||
downloadLink <- function(outputId, label="Download", class=NULL, ...) {
|
||||
tags$a(id=outputId,
|
||||
class=paste(c('shiny-download-link', class), collapse=" "),
|
||||
class='shiny-download-link disabled',
|
||||
class=class,
|
||||
href='',
|
||||
target='_blank',
|
||||
download=NA,
|
||||
"aria-disabled"="true",
|
||||
tabindex="-1",
|
||||
label, ...)
|
||||
}
|
||||
|
||||
|
||||
4
R/busy-indicators-spinners.R
Normal file
@@ -0,0 +1,4 @@
|
||||
# Generated by tools/updateSpinnerTypes.R: do not edit by hand
|
||||
.busySpinnerTypes <-
|
||||
c("ring", "ring2", "ring3", "bars", "bars2", "bars3", "pulse",
|
||||
"pulse2", "pulse3", "dots", "dots2", "dots3")
|
||||
294
R/busy-indicators.R
Normal file
@@ -0,0 +1,294 @@
|
||||
#' Enable/disable busy indication
|
||||
#'
|
||||
#' Busy indicators provide a visual cue to users when the server is busy
|
||||
#' calculating outputs or otherwise performing tasks (e.g., producing
|
||||
#' downloads). When enabled, a spinner is shown on each
|
||||
#' calculating/recalculating output, and a pulsing banner is shown at the top of
|
||||
#' the page when the app is otherwise busy. Busy indication is enabled by
|
||||
#' default for UI created with \pkg{bslib}, but must be enabled otherwise. To
|
||||
#' enable/disable, include the result of this function in anywhere in the app's
|
||||
#' UI.
|
||||
#'
|
||||
#' When both `spinners` and `pulse` are set to `TRUE`, the pulse is
|
||||
#' automatically disabled when spinner(s) are active. When both `spinners` and
|
||||
#' `pulse` are set to `FALSE`, no busy indication is shown (other than the
|
||||
#' graying out of recalculating outputs).
|
||||
#'
|
||||
#' @param ... Currently ignored.
|
||||
#' @param spinners Whether to show a spinner on each calculating/recalculating
|
||||
#' output.
|
||||
#' @param pulse Whether to show a pulsing banner at the top of the page when the
|
||||
#' app is busy.
|
||||
#' @param fade Whether to fade recalculating outputs. A value of `FALSE` is
|
||||
#' equivalent to `busyIndicatorOptions(fade_opacity=1)`.
|
||||
#'
|
||||
#' @export
|
||||
#' @seealso [busyIndicatorOptions()] for customizing the appearance of the busy
|
||||
#' indicators.
|
||||
#' @examplesIf rlang::is_interactive()
|
||||
#'
|
||||
#' library(bslib)
|
||||
#'
|
||||
#' ui <- page_fillable(
|
||||
#' useBusyIndicators(),
|
||||
#' card(
|
||||
#' card_header(
|
||||
#' "A plot",
|
||||
#' input_task_button("simulate", "Simulate"),
|
||||
#' class = "d-flex justify-content-between align-items-center"
|
||||
#' ),
|
||||
#' plotOutput("p"),
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$p <- renderPlot({
|
||||
#' input$simulate
|
||||
#' Sys.sleep(4)
|
||||
#' plot(x = rnorm(100), y = rnorm(100))
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
useBusyIndicators <- function(..., spinners = TRUE, pulse = TRUE, fade = TRUE) {
|
||||
|
||||
rlang::check_dots_empty()
|
||||
|
||||
attrs <- list("shinyBusySpinners" = spinners, "shinyBusyPulse" = pulse)
|
||||
|
||||
js <- vapply(names(attrs), character(1), FUN = function(key) {
|
||||
if (attrs[[key]]) {
|
||||
sprintf("document.documentElement.dataset.%s = 'true';", key)
|
||||
} else {
|
||||
sprintf("delete document.documentElement.dataset.%s;", key)
|
||||
}
|
||||
})
|
||||
|
||||
# TODO: it'd be nice if htmltools had something like a page_attrs() that allowed us
|
||||
# to do this without needing to inject JS into the head.
|
||||
res <- tags$script(HTML(paste(js, collapse = "\n")))
|
||||
|
||||
if (!fade) {
|
||||
res <- tagList(res, fadeOptions(opacity = 1))
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#' Customize busy indicator options
|
||||
#'
|
||||
#' @description
|
||||
#' Shiny automatically includes busy indicators, which more specifically means:
|
||||
#' 1. Calculating/recalculating outputs have a spinner overlay.
|
||||
#' 2. Outputs fade out/in when recalculating.
|
||||
#' 3. When no outputs are calculating/recalculating, but Shiny is busy
|
||||
#' doing something else (e.g., a download, side-effect, etc), a page-level
|
||||
#' pulsing banner is shown.
|
||||
#'
|
||||
#' This function allows you to customize the appearance of these busy indicators
|
||||
#' by including the result of this function inside the app's UI. Note that,
|
||||
#' unless `spinner_selector` (or `fade_selector`) is specified, the spinner/fade
|
||||
#' customization applies to the parent element. If the customization should
|
||||
#' instead apply to the entire page, set `spinner_selector = 'html'` and
|
||||
#' `fade_selector = 'html'`.
|
||||
#'
|
||||
#' @param ... Currently ignored.
|
||||
#' @param spinner_type The type of spinner. Pre-bundled types include:
|
||||
#' '`r paste0(.busySpinnerTypes, collapse = "', '")`'.
|
||||
#'
|
||||
#' A path to a local SVG file can also be provided. The SVG should adhere to
|
||||
#' the following rules:
|
||||
#' * The SVG itself should contain the animation.
|
||||
#' * It should avoid absolute sizes (the spinner's containing DOM element
|
||||
#' size is set in CSS by `spinner_size`, so it should fill that container).
|
||||
#' * It should avoid setting absolute colors (the spinner's containing DOM element
|
||||
#' color is set in CSS by `spinner_color`, so it should inherit that color).
|
||||
#' @param spinner_color The color of the spinner. This can be any valid CSS
|
||||
#' color. Defaults to the app's "primary" color if Bootstrap is on the page.
|
||||
#' @param spinner_size The size of the spinner. This can be any valid CSS size.
|
||||
#' @param spinner_delay The amount of time to wait before showing the spinner.
|
||||
#' This can be any valid CSS time and can be useful for not showing the spinner
|
||||
#' if the computation finishes quickly.
|
||||
#' @param spinner_selector A character string containing a CSS selector for
|
||||
#' scoping the spinner customization. The default (`NULL`) will apply the
|
||||
#' spinner customization to the parent element of the spinner.
|
||||
#' @param fade_opacity The opacity (a number between 0 and 1) for recalculating
|
||||
#' output. Set to 1 to "disable" the fade.
|
||||
#' @param fade_selector A character string containing a CSS selector for
|
||||
#' scoping the spinner customization. The default (`NULL`) will apply the
|
||||
#' spinner customization to the parent element of the spinner.
|
||||
#' @param pulse_background A CSS background definition for the pulse. The
|
||||
#' default uses a
|
||||
#' [linear-gradient](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient)
|
||||
#' of the theme's indigo, purple, and pink colors.
|
||||
#' @param pulse_height The height of the pulsing banner. This can be any valid
|
||||
#' CSS size.
|
||||
#' @param pulse_speed The speed of the pulsing banner. This can be any valid CSS
|
||||
#' time.
|
||||
#'
|
||||
#' @export
|
||||
#' @seealso [useBusyIndicators()] to disable/enable busy indicators.
|
||||
#' @examplesIf rlang::is_interactive()
|
||||
#'
|
||||
#' library(bslib)
|
||||
#'
|
||||
#' card_ui <- function(id, spinner_type = id) {
|
||||
#' card(
|
||||
#' busyIndicatorOptions(spinner_type = spinner_type),
|
||||
#' card_header(paste("Spinner:", spinner_type)),
|
||||
#' plotOutput(shiny::NS(id, "plot"))
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' card_server <- function(id, simulate = reactive()) {
|
||||
#' moduleServer(
|
||||
#' id = id,
|
||||
#' function(input, output, session) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' Sys.sleep(1)
|
||||
#' simulate()
|
||||
#' plot(x = rnorm(100), y = rnorm(100))
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#'
|
||||
#' ui <- page_fillable(
|
||||
#' useBusyIndicators(),
|
||||
#' input_task_button("simulate", "Simulate", icon = icon("refresh")),
|
||||
#' layout_columns(
|
||||
#' card_ui("ring"),
|
||||
#' card_ui("bars"),
|
||||
#' card_ui("dots"),
|
||||
#' card_ui("pulse"),
|
||||
#' col_widths = 6
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' simulate <- reactive(input$simulate)
|
||||
#' card_server("ring", simulate)
|
||||
#' card_server("bars", simulate)
|
||||
#' card_server("dots", simulate)
|
||||
#' card_server("pulse", simulate)
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
busyIndicatorOptions <- function(
|
||||
...,
|
||||
spinner_type = NULL,
|
||||
spinner_color = NULL,
|
||||
spinner_size = NULL,
|
||||
spinner_delay = NULL,
|
||||
spinner_selector = NULL,
|
||||
fade_opacity = NULL,
|
||||
fade_selector = NULL,
|
||||
pulse_background = NULL,
|
||||
pulse_height = NULL,
|
||||
pulse_speed = NULL
|
||||
) {
|
||||
|
||||
rlang::check_dots_empty()
|
||||
|
||||
res <- tagList(
|
||||
spinnerOptions(
|
||||
type = spinner_type,
|
||||
color = spinner_color,
|
||||
size = spinner_size,
|
||||
delay = spinner_delay,
|
||||
selector = spinner_selector
|
||||
),
|
||||
fadeOptions(opacity = fade_opacity, selector = fade_selector),
|
||||
pulseOptions(
|
||||
background = pulse_background,
|
||||
height = pulse_height,
|
||||
speed = pulse_speed
|
||||
)
|
||||
)
|
||||
|
||||
bslib::as.card_item(dropNulls(res))
|
||||
}
|
||||
|
||||
|
||||
spinnerOptions <- function(type = NULL, color = NULL, size = NULL, delay = NULL, selector = NULL) {
|
||||
if (is.null(type) && is.null(color) && is.null(size) && is.null(delay) && is.null(selector)) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
url <- NULL
|
||||
if (!is.null(type)) {
|
||||
stopifnot(is.character(type) && length(type) == 1)
|
||||
if (file.exists(type) && grepl("\\.svg$", type)) {
|
||||
typeRaw <- readBin(type, "raw", n = file.info(type)$size)
|
||||
url <- sprintf("url('data:image/svg+xml;base64,%s')", rawToBase64(typeRaw))
|
||||
} else {
|
||||
type <- rlang::arg_match(type, .busySpinnerTypes)
|
||||
url <- sprintf("url('spinners/%s.svg')", type)
|
||||
}
|
||||
}
|
||||
|
||||
# Options controlled via CSS variables.
|
||||
css_vars <- htmltools::css(
|
||||
"--shiny-spinner-url" = url,
|
||||
"--shiny-spinner-color" = htmltools::parseCssColors(color),
|
||||
"--shiny-spinner-size" = htmltools::validateCssUnit(size),
|
||||
"--shiny-spinner-delay" = delay
|
||||
)
|
||||
|
||||
id <- NULL
|
||||
if (is.null(selector)) {
|
||||
id <- paste0("spinner-options-", p_randomInt(100, 1000000))
|
||||
selector <- sprintf(":has(> #%s)", id)
|
||||
}
|
||||
|
||||
css <- HTML(paste0(selector, " {", css_vars, "}"))
|
||||
|
||||
tags$style(css, id = id)
|
||||
}
|
||||
|
||||
fadeOptions <- function(opacity = NULL, selector = NULL) {
|
||||
if (is.null(opacity) && is.null(selector)) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
css_vars <- htmltools::css(
|
||||
"--shiny-fade-opacity" = opacity
|
||||
)
|
||||
|
||||
id <- NULL
|
||||
if (is.null(selector)) {
|
||||
id <- paste0("fade-options-", p_randomInt(100, 1000000))
|
||||
selector <- sprintf(":has(> #%s)", id)
|
||||
}
|
||||
|
||||
css <- HTML(paste0(selector, " {", css_vars, "}"))
|
||||
|
||||
tags$style(css, id = id)
|
||||
}
|
||||
|
||||
pulseOptions <- function(background = NULL, height = NULL, speed = NULL) {
|
||||
if (is.null(background) && is.null(height) && is.null(speed)) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
css_vars <- htmltools::css(
|
||||
"--shiny-pulse-background" = background,
|
||||
"--shiny-pulse-height" = htmltools::validateCssUnit(height),
|
||||
"--shiny-pulse-speed" = speed
|
||||
)
|
||||
|
||||
tags$style(HTML(paste0(":root {", css_vars, "}")))
|
||||
}
|
||||
|
||||
busyIndicatorDependency <- function() {
|
||||
htmlDependency(
|
||||
name = "shiny-busy-indicators",
|
||||
version = get_package_version("shiny"),
|
||||
src = "www/shared/busy-indicators",
|
||||
package = "shiny",
|
||||
stylesheet = "busy-indicators.css",
|
||||
# TODO-future: In next release make spinners and pulse opt-out
|
||||
# head = as.character(useBusyIndicators())
|
||||
)
|
||||
}
|
||||
239
R/conditions.R
@@ -130,6 +130,44 @@ captureStackTraces <- function(expr) {
|
||||
#' @include globals.R
|
||||
.globals$deepStack <- NULL
|
||||
|
||||
getCallStackDigest <- function(callStack, warn = FALSE) {
|
||||
dg <- attr(callStack, "shiny.stack.digest", exact = TRUE)
|
||||
if (!is.null(dg)) {
|
||||
return(dg)
|
||||
}
|
||||
|
||||
if (isTRUE(warn)) {
|
||||
rlang::warn(
|
||||
"Call stack doesn't have a cached digest; expensively computing one now",
|
||||
.frequency = "once",
|
||||
.frequency_id = "deepstack-uncached-digest-warning"
|
||||
)
|
||||
}
|
||||
|
||||
rlang::hash(getCallNames(callStack))
|
||||
}
|
||||
|
||||
saveCallStackDigest <- function(callStack) {
|
||||
attr(callStack, "shiny.stack.digest") <- getCallStackDigest(callStack, warn = FALSE)
|
||||
callStack
|
||||
}
|
||||
|
||||
# Appends a call stack to a list of call stacks, but only if it's not already
|
||||
# in the list. The list is deduplicated by digest; ideally the digests on the
|
||||
# list are cached before calling this function (you will get a warning if not).
|
||||
appendCallStackWithDedupe <- function(lst, x) {
|
||||
digests <- vapply(lst, getCallStackDigest, character(1), warn = TRUE)
|
||||
xdigest <- getCallStackDigest(x, warn = TRUE)
|
||||
stopifnot(all(nzchar(digests)))
|
||||
stopifnot(length(xdigest) == 1)
|
||||
stopifnot(nzchar(xdigest))
|
||||
if (xdigest %in% digests) {
|
||||
return(lst)
|
||||
} else {
|
||||
return(c(lst, list(x)))
|
||||
}
|
||||
}
|
||||
|
||||
createStackTracePromiseDomain <- function() {
|
||||
# These are actually stateless, we wouldn't have to create a new one each time
|
||||
# if we didn't want to. They're pretty cheap though.
|
||||
@@ -142,13 +180,14 @@ createStackTracePromiseDomain <- function() {
|
||||
currentStack <- sys.calls()
|
||||
currentParents <- sys.parents()
|
||||
attr(currentStack, "parents") <- currentParents
|
||||
currentStack <- saveCallStackDigest(currentStack)
|
||||
currentDeepStack <- .globals$deepStack
|
||||
}
|
||||
function(...) {
|
||||
# Fulfill time
|
||||
if (deepStacksEnabled()) {
|
||||
origDeepStack <- .globals$deepStack
|
||||
.globals$deepStack <- c(currentDeepStack, list(currentStack))
|
||||
.globals$deepStack <- appendCallStackWithDedupe(currentDeepStack, currentStack)
|
||||
on.exit(.globals$deepStack <- origDeepStack, add = TRUE)
|
||||
}
|
||||
|
||||
@@ -165,13 +204,14 @@ createStackTracePromiseDomain <- function() {
|
||||
currentStack <- sys.calls()
|
||||
currentParents <- sys.parents()
|
||||
attr(currentStack, "parents") <- currentParents
|
||||
currentStack <- saveCallStackDigest(currentStack)
|
||||
currentDeepStack <- .globals$deepStack
|
||||
}
|
||||
function(...) {
|
||||
# Fulfill time
|
||||
if (deepStacksEnabled()) {
|
||||
origDeepStack <- .globals$deepStack
|
||||
.globals$deepStack <- c(currentDeepStack, list(currentStack))
|
||||
.globals$deepStack <- appendCallStackWithDedupe(currentDeepStack, currentStack)
|
||||
on.exit(.globals$deepStack <- origDeepStack, add = TRUE)
|
||||
}
|
||||
|
||||
@@ -199,6 +239,7 @@ doCaptureStack <- function(e) {
|
||||
calls <- sys.calls()
|
||||
parents <- sys.parents()
|
||||
attr(calls, "parents") <- parents
|
||||
calls <- saveCallStackDigest(calls)
|
||||
attr(e, "stack.trace") <- calls
|
||||
}
|
||||
if (deepStacksEnabled()) {
|
||||
@@ -281,88 +322,115 @@ printStackTrace <- function(cond,
|
||||
full = get_devmode_option("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
should_drop <- !full
|
||||
should_strip <- !full
|
||||
should_prune <- !full
|
||||
|
||||
stackTraceCalls <- c(
|
||||
stackTraces <- c(
|
||||
attr(cond, "deep.stack.trace", exact = TRUE),
|
||||
list(attr(cond, "stack.trace", exact = TRUE))
|
||||
)
|
||||
|
||||
stackTraceParents <- lapply(stackTraceCalls, attr, which = "parents", exact = TRUE)
|
||||
stackTraceCallNames <- lapply(stackTraceCalls, getCallNames)
|
||||
stackTraceCalls <- lapply(stackTraceCalls, offsetSrcrefs, offset = offset)
|
||||
|
||||
# Use dropTrivialFrames logic to remove trailing bits (.handleSimpleError, h)
|
||||
if (should_drop) {
|
||||
# toKeep is a list of logical vectors, of which elements (stack frames) to keep
|
||||
toKeep <- lapply(stackTraceCallNames, dropTrivialFrames)
|
||||
# We apply the list of logical vector indices to each data structure
|
||||
stackTraceCalls <- mapply(stackTraceCalls, FUN = `[`, toKeep, SIMPLIFY = FALSE)
|
||||
stackTraceCallNames <- mapply(stackTraceCallNames, FUN = `[`, toKeep, SIMPLIFY = FALSE)
|
||||
stackTraceParents <- mapply(stackTraceParents, FUN = `[`, toKeep, SIMPLIFY = FALSE)
|
||||
# Stripping of stack traces is the one step where the different stack traces
|
||||
# interact. So we need to do this in one go, instead of individually within
|
||||
# printOneStackTrace.
|
||||
if (!full) {
|
||||
stripResults <- stripStackTraces(lapply(stackTraces, getCallNames))
|
||||
} else {
|
||||
# If full is TRUE, we don't want to strip anything
|
||||
stripResults <- rep_len(list(TRUE), length(stackTraces))
|
||||
}
|
||||
|
||||
delayedAssign("all_true", {
|
||||
# List of logical vectors that are all TRUE, the same shape as
|
||||
# stackTraceCallNames. Delay the evaluation so we don't create it unless
|
||||
# we need it, but if we need it twice then we don't pay to create it twice.
|
||||
lapply(stackTraceCallNames, function(st) {
|
||||
rep_len(TRUE, length(st))
|
||||
})
|
||||
})
|
||||
|
||||
# stripStackTraces and lapply(stackTraceParents, pruneStackTrace) return lists
|
||||
# of logical vectors. Use mapply(FUN = `&`) to boolean-and each pair of the
|
||||
# logical vectors.
|
||||
toShow <- mapply(
|
||||
if (should_strip) stripStackTraces(stackTraceCallNames) else all_true,
|
||||
if (should_prune) lapply(stackTraceParents, pruneStackTrace) else all_true,
|
||||
FUN = `&`,
|
||||
mapply(
|
||||
seq_along(stackTraces),
|
||||
rev(stackTraces),
|
||||
rev(stripResults),
|
||||
FUN = function(i, trace, stripResult) {
|
||||
if (is.integer(trace)) {
|
||||
noun <- if (trace > 1L) "traces" else "trace"
|
||||
message("[ reached getOption(\"shiny.deepstacktrace\") -- omitted ", trace, " more stack ", noun, " ]")
|
||||
} else {
|
||||
if (i != 1) {
|
||||
message("From earlier call:")
|
||||
}
|
||||
printOneStackTrace(
|
||||
stackTrace = trace,
|
||||
stripResult = stripResult,
|
||||
full = full,
|
||||
offset = offset
|
||||
)
|
||||
}
|
||||
# No mapply return value--we're just printing
|
||||
NULL
|
||||
},
|
||||
SIMPLIFY = FALSE
|
||||
)
|
||||
|
||||
dfs <- mapply(seq_along(stackTraceCalls), rev(stackTraceCalls), rev(stackTraceCallNames), rev(toShow), FUN = function(i, calls, nms, index) {
|
||||
st <- data.frame(
|
||||
num = rev(which(index)),
|
||||
call = rev(nms[index]),
|
||||
loc = rev(getLocs(calls[index])),
|
||||
category = rev(getCallCategories(calls[index])),
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
|
||||
if (i != 1) {
|
||||
message("From earlier call:")
|
||||
}
|
||||
|
||||
if (nrow(st) == 0) {
|
||||
message(" [No stack trace available]")
|
||||
} else {
|
||||
width <- floor(log10(max(st$num))) + 1
|
||||
formatted <- paste0(
|
||||
" ",
|
||||
formatC(st$num, width = width),
|
||||
": ",
|
||||
mapply(paste0(st$call, st$loc), st$category, FUN = function(name, category) {
|
||||
if (category == "pkg")
|
||||
crayon::silver(name)
|
||||
else if (category == "user")
|
||||
crayon::blue$bold(name)
|
||||
else
|
||||
crayon::white(name)
|
||||
}),
|
||||
"\n"
|
||||
)
|
||||
cat(file = stderr(), formatted, sep = "")
|
||||
}
|
||||
|
||||
st
|
||||
}, SIMPLIFY = FALSE)
|
||||
|
||||
invisible()
|
||||
}
|
||||
|
||||
printOneStackTrace <- function(stackTrace, stripResult, full, offset) {
|
||||
calls <- offsetSrcrefs(stackTrace, offset = offset)
|
||||
callNames <- getCallNames(stackTrace)
|
||||
parents <- attr(stackTrace, "parents", exact = TRUE)
|
||||
|
||||
should_drop <- !full
|
||||
should_strip <- !full
|
||||
should_prune <- !full
|
||||
|
||||
if (should_drop) {
|
||||
toKeep <- dropTrivialFrames(callNames)
|
||||
calls <- calls[toKeep]
|
||||
callNames <- callNames[toKeep]
|
||||
parents <- parents[toKeep]
|
||||
stripResult <- stripResult[toKeep]
|
||||
}
|
||||
|
||||
toShow <- rep(TRUE, length(callNames))
|
||||
if (should_prune) {
|
||||
toShow <- toShow & pruneStackTrace(parents)
|
||||
}
|
||||
if (should_strip) {
|
||||
toShow <- toShow & stripResult
|
||||
}
|
||||
|
||||
# If we're running in testthat, hide the parts of the stack trace that can
|
||||
# vary based on how testthat was launched. It's critical that this is not
|
||||
# happen at the same time as dropTrivialFrames, which happens before
|
||||
# pruneStackTrace; because dropTrivialTestFrames removes calls from the top
|
||||
# (or bottom? whichever is the oldest?) of the stack, it breaks `parents`
|
||||
# which is based on absolute indices of calls. dropTrivialFrames gets away
|
||||
# with this because it only removes calls from the opposite side of the stack.
|
||||
toShow <- toShow & dropTrivialTestFrames(callNames)
|
||||
|
||||
st <- data.frame(
|
||||
num = rev(which(toShow)),
|
||||
call = rev(callNames[toShow]),
|
||||
loc = rev(getLocs(calls[toShow])),
|
||||
category = rev(getCallCategories(calls[toShow])),
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
|
||||
if (nrow(st) == 0) {
|
||||
message(" [No stack trace available]")
|
||||
} else {
|
||||
width <- floor(log10(max(st$num))) + 1
|
||||
formatted <- paste0(
|
||||
" ",
|
||||
formatC(st$num, width = width),
|
||||
": ",
|
||||
mapply(paste0(st$call, st$loc), st$category, FUN = function(name, category) {
|
||||
if (category == "pkg")
|
||||
crayon::silver(name)
|
||||
else if (category == "user")
|
||||
crayon::blue$bold(name)
|
||||
else
|
||||
crayon::white(name)
|
||||
}),
|
||||
"\n"
|
||||
)
|
||||
cat(file = stderr(), formatted, sep = "")
|
||||
}
|
||||
|
||||
invisible(st)
|
||||
}
|
||||
|
||||
stripStackTraces <- function(stackTraces, values = FALSE) {
|
||||
score <- 1L # >=1: show, <=0: hide
|
||||
lapply(seq_along(stackTraces), function(i) {
|
||||
@@ -458,6 +526,33 @@ dropTrivialFrames <- function(callnames) {
|
||||
)
|
||||
}
|
||||
|
||||
dropTrivialTestFrames <- function(callnames) {
|
||||
if (!identical(Sys.getenv("TESTTHAT_IS_SNAPSHOT"), "true")) {
|
||||
return(rep_len(TRUE, length(callnames)))
|
||||
}
|
||||
|
||||
hideable <- callnames %in% c(
|
||||
"test",
|
||||
"devtools::test",
|
||||
"test_check",
|
||||
"testthat::test_check",
|
||||
"test_dir",
|
||||
"testthat::test_dir",
|
||||
"test_file",
|
||||
"testthat::test_file",
|
||||
"test_local",
|
||||
"testthat::test_local"
|
||||
)
|
||||
|
||||
firstGoodCall <- min(which(!hideable))
|
||||
toRemove <- firstGoodCall - 1L
|
||||
|
||||
c(
|
||||
rep_len(FALSE, toRemove),
|
||||
rep_len(TRUE, length(callnames) - toRemove)
|
||||
)
|
||||
}
|
||||
|
||||
offsetSrcrefs <- function(calls, offset = TRUE) {
|
||||
if (offset) {
|
||||
srcrefs <- getSrcRefs(calls)
|
||||
|
||||
@@ -128,6 +128,12 @@ in_devmode <- function() {
|
||||
!identical(Sys.getenv("TESTTHAT"), "true")
|
||||
}
|
||||
|
||||
in_client_devmode <- function() {
|
||||
# Client-side devmode enables client-side only dev features without local
|
||||
# devmode. Currently, the main feature is the client-side error console.
|
||||
isTRUE(getOption("shiny.client_devmode", FALSE))
|
||||
}
|
||||
|
||||
#' @describeIn devmode Temporarily set Shiny Developer Mode and Developer
|
||||
#' message verbosity
|
||||
#' @param code Code to execute with the temporary Dev Mode options set
|
||||
|
||||
@@ -41,6 +41,54 @@
|
||||
#' is, a function that quickly returns a promise) and allows even that very
|
||||
#' session to immediately unblock and carry on with other user interactions.
|
||||
#'
|
||||
#' @examplesIf rlang::is_interactive() && rlang::is_installed("future")
|
||||
#'
|
||||
#' library(shiny)
|
||||
#' library(bslib)
|
||||
#' library(future)
|
||||
#' plan(multisession)
|
||||
#'
|
||||
#' ui <- page_fluid(
|
||||
#' titlePanel("Extended Task Demo"),
|
||||
#' p(
|
||||
#' 'Click the button below to perform a "calculation"',
|
||||
#' "that takes a while to perform."
|
||||
#' ),
|
||||
#' input_task_button("recalculate", "Recalculate"),
|
||||
#' p(textOutput("result"))
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' rand_task <- ExtendedTask$new(function() {
|
||||
#' future(
|
||||
#' {
|
||||
#' # Slow operation goes here
|
||||
#' Sys.sleep(2)
|
||||
#' sample(1:100, 1)
|
||||
#' },
|
||||
#' seed = TRUE
|
||||
#' )
|
||||
#' })
|
||||
#'
|
||||
#' # Make button state reflect task.
|
||||
#' # If using R >=4.1, you can do this instead:
|
||||
#' # rand_task <- ExtendedTask$new(...) |> bind_task_button("recalculate")
|
||||
#' bind_task_button(rand_task, "recalculate")
|
||||
#'
|
||||
#' observeEvent(input$recalculate, {
|
||||
#' # Invoke the extended in an observer
|
||||
#' rand_task$invoke()
|
||||
#' })
|
||||
#'
|
||||
#' output$result <- renderText({
|
||||
#' # React to updated results when the task completes
|
||||
#' number <- rand_task$result()
|
||||
#' paste0("Your number is ", number, ".")
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' @export
|
||||
ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
public = list(
|
||||
|
||||
122
R/graph.R
@@ -1,4 +1,3 @@
|
||||
|
||||
# domain is like session
|
||||
|
||||
|
||||
@@ -20,7 +19,7 @@ reactIdStr <- function(num) {
|
||||
#' dependencies and execution in your application.
|
||||
#'
|
||||
#' To use the reactive log visualizer, start with a fresh R session and
|
||||
#' run the command `options(shiny.reactlog=TRUE)`; then launch your
|
||||
#' run the command `reactlog::reactlog_enable()`; then launch your
|
||||
#' application in the usual way (e.g. using [runApp()]). At
|
||||
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
||||
#' web browser to launch the reactive log visualization.
|
||||
@@ -43,16 +42,20 @@ reactIdStr <- function(num) {
|
||||
#' call `reactlogShow()` explicitly.
|
||||
#'
|
||||
#' For security and performance reasons, do not enable
|
||||
#' `shiny.reactlog` in production environments. When the option is
|
||||
#' enabled, it's possible for any user of your app to see at least some
|
||||
#' of the source code of your reactive expressions and observers.
|
||||
#' `options(shiny.reactlog=TRUE)` (or `reactlog::reactlog_enable()`) in
|
||||
#' production environments. When the option is enabled, it's possible
|
||||
#' for any user of your app to see at least some of the source code of
|
||||
#' your reactive expressions and observers. In addition, reactlog
|
||||
#' should be considered a memory leak as it will constantly grow and
|
||||
#' will never reset until the R session is restarted.
|
||||
#'
|
||||
#' @name reactlog
|
||||
NULL
|
||||
|
||||
|
||||
#' @describeIn reactlog Return a list of reactive information. Can be used in conjunction with
|
||||
#' [reactlog::reactlog_show] to later display the reactlog graph.
|
||||
#' @describeIn reactlog Return a list of reactive information. Can be used in
|
||||
#' conjunction with [reactlog::reactlog_show] to later display the reactlog
|
||||
#' graph.
|
||||
#' @export
|
||||
reactlog <- function() {
|
||||
rLog$asList()
|
||||
@@ -67,12 +70,34 @@ reactlogShow <- function(time = TRUE) {
|
||||
reactlog::reactlog_show(reactlog(), time = time)
|
||||
}
|
||||
|
||||
#' @describeIn reactlog Resets the entire reactlog stack. Useful for debugging and removing all prior reactive history.
|
||||
#' @describeIn reactlog Resets the entire reactlog stack. Useful for debugging
|
||||
#' and removing all prior reactive history.
|
||||
#' @export
|
||||
reactlogReset <- function() {
|
||||
rLog$reset()
|
||||
}
|
||||
|
||||
#' @describeIn reactlog Adds "mark" entry into the reactlog stack. This is
|
||||
#' useful for programmatically adding a marked entry in the reactlog, rather
|
||||
#' than using your keyboard's key combination.
|
||||
#'
|
||||
#' For example, we can _mark_ the reactlog at the beginning of an
|
||||
#' `observeEvent`'s calculation:
|
||||
#' ```r
|
||||
#' observeEvent(input$my_event_trigger, {
|
||||
#' # Add a mark in the reactlog
|
||||
#' reactlogAddMark()
|
||||
#' # Run your regular event reaction code here...
|
||||
#' ....
|
||||
#' })
|
||||
#' ```
|
||||
#' @param session The Shiny session to assign the mark to. Defaults to the
|
||||
#' current session.
|
||||
#' @export
|
||||
reactlogAddMark <- function(session = getDefaultReactiveDomain()) {
|
||||
rLog$userMark(session)
|
||||
}
|
||||
|
||||
# called in "/reactlog" middleware
|
||||
renderReactlog <- function(sessionToken = NULL, time = TRUE) {
|
||||
check_reactlog()
|
||||
@@ -98,7 +123,6 @@ RLog <- R6Class(
|
||||
private = list(
|
||||
option = "shiny.reactlog",
|
||||
msgOption = "shiny.reactlog.console",
|
||||
|
||||
appendEntry = function(domain, logEntry) {
|
||||
if (self$isLogging()) {
|
||||
sessionToken <- if (is.null(domain)) NULL else domain$token
|
||||
@@ -113,20 +137,19 @@ RLog <- R6Class(
|
||||
public = list(
|
||||
msg = "<MessageLogger>",
|
||||
logStack = "<Stack>",
|
||||
|
||||
noReactIdLabel = "NoCtxReactId",
|
||||
noReactId = reactIdStr("NoCtxReactId"),
|
||||
dummyReactIdLabel = "DummyReactId",
|
||||
dummyReactId = reactIdStr("DummyReactId"),
|
||||
|
||||
asList = function() {
|
||||
ret <- self$logStack$as_list()
|
||||
attr(ret, "version") <- "1"
|
||||
ret
|
||||
},
|
||||
|
||||
ctxIdStr = function(ctxId) {
|
||||
if (is.null(ctxId) || identical(ctxId, "")) return(NULL)
|
||||
if (is.null(ctxId) || identical(ctxId, "")) {
|
||||
return(NULL)
|
||||
}
|
||||
paste0("ctx", ctxId)
|
||||
},
|
||||
namesIdStr = function(reactId) {
|
||||
@@ -141,7 +164,6 @@ RLog <- R6Class(
|
||||
keyIdStr = function(reactId, key) {
|
||||
paste0(reactId, "$", key)
|
||||
},
|
||||
|
||||
valueStr = function(value, n = 200) {
|
||||
if (!self$isLogging()) {
|
||||
# return a placeholder string to avoid calling str
|
||||
@@ -151,10 +173,9 @@ RLog <- R6Class(
|
||||
# only capture the first level of the object
|
||||
utils::capture.output(utils::str(value, max.level = 1))
|
||||
})
|
||||
outputTxt <- paste0(output, collapse="\n")
|
||||
outputTxt <- paste0(output, collapse = "\n")
|
||||
msg$shortenString(outputTxt, n = n)
|
||||
},
|
||||
|
||||
initialize = function(rlogOption = "shiny.reactlog", msgOption = "shiny.reactlog.console") {
|
||||
private$option <- rlogOption
|
||||
private$msgOption <- msgOption
|
||||
@@ -174,7 +195,6 @@ RLog <- R6Class(
|
||||
isLogging = function() {
|
||||
isTRUE(getOption(private$option, FALSE))
|
||||
},
|
||||
|
||||
define = function(reactId, value, label, type, domain) {
|
||||
valueStr <- self$valueStr(value)
|
||||
if (msg$hasReact(reactId)) {
|
||||
@@ -205,9 +225,10 @@ RLog <- R6Class(
|
||||
defineObserver = function(reactId, label, domain) {
|
||||
self$define(reactId, value = NULL, label, "observer", domain)
|
||||
},
|
||||
|
||||
dependsOn = function(reactId, depOnReactId, ctxId, domain) {
|
||||
if (is.null(reactId)) return()
|
||||
if (is.null(reactId)) {
|
||||
return()
|
||||
}
|
||||
ctxId <- ctxIdStr(ctxId)
|
||||
msg$log("dependsOn:", msg$reactStr(reactId), " on", msg$reactStr(depOnReactId), msg$ctxStr(ctxId))
|
||||
private$appendEntry(domain, list(
|
||||
@@ -220,7 +241,6 @@ RLog <- R6Class(
|
||||
dependsOnKey = function(reactId, depOnReactId, key, ctxId, domain) {
|
||||
self$dependsOn(reactId, self$keyIdStr(depOnReactId, key), ctxId, domain)
|
||||
},
|
||||
|
||||
dependsOnRemove = function(reactId, depOnReactId, ctxId, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
msg$log("dependsOnRemove:", msg$reactStr(reactId), " on", msg$reactStr(depOnReactId), msg$ctxStr(ctxId))
|
||||
@@ -234,7 +254,6 @@ RLog <- R6Class(
|
||||
dependsOnKeyRemove = function(reactId, depOnReactId, key, ctxId, domain) {
|
||||
self$dependsOnRemove(reactId, self$keyIdStr(depOnReactId, key), ctxId, domain)
|
||||
},
|
||||
|
||||
createContext = function(ctxId, label, type, prevCtxId, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
prevCtxId <- self$ctxIdStr(prevCtxId)
|
||||
@@ -245,10 +264,9 @@ RLog <- R6Class(
|
||||
label = msg$shortenString(label),
|
||||
type = type,
|
||||
prevCtxId = prevCtxId,
|
||||
srcref = as.vector(attr(label, "srcref")), srcfile=attr(label, "srcfile")
|
||||
srcref = as.vector(attr(label, "srcref")), srcfile = attr(label, "srcfile")
|
||||
))
|
||||
},
|
||||
|
||||
enter = function(reactId, ctxId, type, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
if (identical(type, "isolate")) {
|
||||
@@ -291,7 +309,6 @@ RLog <- R6Class(
|
||||
))
|
||||
}
|
||||
},
|
||||
|
||||
valueChange = function(reactId, value, domain) {
|
||||
valueStr <- self$valueStr(value)
|
||||
msg$log("valueChange:", msg$reactStr(reactId), msg$valueStr(valueStr))
|
||||
@@ -313,8 +330,6 @@ RLog <- R6Class(
|
||||
valueChangeKey = function(reactId, key, value, domain) {
|
||||
self$valueChange(self$keyIdStr(reactId, key), value, domain)
|
||||
},
|
||||
|
||||
|
||||
invalidateStart = function(reactId, ctxId, type, domain) {
|
||||
ctxId <- self$ctxIdStr(ctxId)
|
||||
if (identical(type, "isolate")) {
|
||||
@@ -357,7 +372,6 @@ RLog <- R6Class(
|
||||
))
|
||||
}
|
||||
},
|
||||
|
||||
invalidateLater = function(reactId, runningCtx, millis, domain) {
|
||||
msg$log("invalidateLater: ", millis, "ms", msg$reactStr(reactId), msg$ctxStr(runningCtx))
|
||||
private$appendEntry(domain, list(
|
||||
@@ -367,14 +381,12 @@ RLog <- R6Class(
|
||||
millis = millis
|
||||
))
|
||||
},
|
||||
|
||||
idle = function(domain = NULL) {
|
||||
msg$log("idle")
|
||||
private$appendEntry(domain, list(
|
||||
action = "idle"
|
||||
))
|
||||
},
|
||||
|
||||
asyncStart = function(domain = NULL) {
|
||||
msg$log("asyncStart")
|
||||
private$appendEntry(domain, list(
|
||||
@@ -387,7 +399,6 @@ RLog <- R6Class(
|
||||
action = "asyncStop"
|
||||
))
|
||||
},
|
||||
|
||||
freezeReactiveVal = function(reactId, domain) {
|
||||
msg$log("freeze:", msg$reactStr(reactId))
|
||||
private$appendEntry(domain, list(
|
||||
@@ -398,7 +409,6 @@ RLog <- R6Class(
|
||||
freezeReactiveKey = function(reactId, key, domain) {
|
||||
self$freezeReactiveVal(self$keyIdStr(reactId, key), domain)
|
||||
},
|
||||
|
||||
thawReactiveVal = function(reactId, domain) {
|
||||
msg$log("thaw:", msg$reactStr(reactId))
|
||||
private$appendEntry(domain, list(
|
||||
@@ -409,54 +419,60 @@ RLog <- R6Class(
|
||||
thawReactiveKey = function(reactId, key, domain) {
|
||||
self$thawReactiveVal(self$keyIdStr(reactId, key), domain)
|
||||
},
|
||||
|
||||
userMark = function(domain = NULL) {
|
||||
msg$log("userMark")
|
||||
private$appendEntry(domain, list(
|
||||
action = "userMark"
|
||||
))
|
||||
}
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
MessageLogger = R6Class(
|
||||
MessageLogger <- R6Class(
|
||||
"MessageLogger",
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
depth = 0L,
|
||||
reactCache = list(),
|
||||
option = "shiny.reactlog.console",
|
||||
|
||||
initialize = function(option = "shiny.reactlog.console", depth = 0L) {
|
||||
if (!missing(depth)) self$depth <- depth
|
||||
if (!missing(option)) self$option <- option
|
||||
},
|
||||
|
||||
isLogging = function() {
|
||||
isTRUE(getOption(self$option))
|
||||
},
|
||||
isNotLogging = function() {
|
||||
! isTRUE(getOption(self$option))
|
||||
!isTRUE(getOption(self$option))
|
||||
},
|
||||
depthIncrement = function() {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
if (self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
self$depth <- self$depth + 1L
|
||||
},
|
||||
depthDecrement = function() {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
if (self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
self$depth <- self$depth - 1L
|
||||
},
|
||||
hasReact = function(reactId) {
|
||||
if (self$isNotLogging()) return(FALSE)
|
||||
if (self$isNotLogging()) {
|
||||
return(FALSE)
|
||||
}
|
||||
!is.null(self$getReact(reactId))
|
||||
},
|
||||
getReact = function(reactId, force = FALSE) {
|
||||
if (identical(force, FALSE) && self$isNotLogging()) return(NULL)
|
||||
if (identical(force, FALSE) && self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
self$reactCache[[reactId]]
|
||||
},
|
||||
setReact = function(reactObj, force = FALSE) {
|
||||
if (identical(force, FALSE) && self$isNotLogging()) return(NULL)
|
||||
if (identical(force, FALSE) && self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
self$reactCache[[reactObj$reactId]] <- reactObj
|
||||
},
|
||||
shortenString = function(txt, n = 250) {
|
||||
@@ -475,13 +491,17 @@ MessageLogger = R6Class(
|
||||
},
|
||||
valueStr = function(valueStr) {
|
||||
paste0(
|
||||
" '", self$shortenString(self$singleLine(valueStr)), "'"
|
||||
" '", self$shortenString(self$singleLine(valueStr)), "'"
|
||||
)
|
||||
},
|
||||
reactStr = function(reactId) {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
if (self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
reactInfo <- self$getReact(reactId)
|
||||
if (is.null(reactInfo)) return(" <UNKNOWN_REACTID>")
|
||||
if (is.null(reactInfo)) {
|
||||
return(" <UNKNOWN_REACTID>")
|
||||
}
|
||||
paste0(
|
||||
" ", reactInfo$reactId, ":'", self$shortenString(self$singleLine(reactInfo$label)), "'"
|
||||
)
|
||||
@@ -490,11 +510,15 @@ MessageLogger = R6Class(
|
||||
self$ctxStr(ctxId = NULL, type = type)
|
||||
},
|
||||
ctxStr = function(ctxId = NULL, type = NULL) {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
if (self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
self$ctxPrevCtxStr(ctxId = ctxId, prevCtxId = NULL, type = type)
|
||||
},
|
||||
ctxPrevCtxStr = function(ctxId = NULL, prevCtxId = NULL, type = NULL, preCtxIdTxt = " in ") {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
if (self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
paste0(
|
||||
if (!is.null(ctxId)) paste0(preCtxIdTxt, ctxId),
|
||||
if (!is.null(prevCtxId)) paste0(" from ", prevCtxId),
|
||||
@@ -502,7 +526,9 @@ MessageLogger = R6Class(
|
||||
)
|
||||
},
|
||||
log = function(...) {
|
||||
if (self$isNotLogging()) return(NULL)
|
||||
if (self$isNotLogging()) {
|
||||
return(NULL)
|
||||
}
|
||||
msg <- paste0(
|
||||
paste0(rep("= ", depth), collapse = ""), "- ", paste0(..., collapse = ""),
|
||||
collapse = ""
|
||||
|
||||
@@ -153,6 +153,12 @@ datePickerDependency <- function(theme) {
|
||||
)
|
||||
}
|
||||
|
||||
datePickerSass <- function() {
|
||||
sass::sass_file(
|
||||
system_file(package = "shiny", "www/shared/datepicker/scss/build3.scss")
|
||||
)
|
||||
}
|
||||
|
||||
datePickerCSS <- function(theme) {
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
@@ -164,10 +170,8 @@ datePickerCSS <- function(theme) {
|
||||
))
|
||||
}
|
||||
|
||||
scss_file <- system_file(package = "shiny", "www/shared/datepicker/scss/build3.scss")
|
||||
|
||||
bslib::bs_dependency(
|
||||
input = sass::sass_file(scss_file),
|
||||
input = datePickerSass(),
|
||||
theme = theme,
|
||||
name = "bootstrap-datepicker",
|
||||
version = version_bs_date_picker,
|
||||
|
||||
@@ -241,11 +241,8 @@ selectizeDependencyFunc <- function(theme) {
|
||||
return(selectizeStaticDependency(version_selectize))
|
||||
}
|
||||
|
||||
selectizeDir <- system_file(package = "shiny", "www/shared/selectize/")
|
||||
bs_version <- bslib::theme_version(theme)
|
||||
stylesheet <- file.path(
|
||||
selectizeDir, "scss", paste0("selectize.bootstrap", bs_version, ".scss")
|
||||
)
|
||||
|
||||
# It'd be cleaner to ship the JS in a separate, href-based,
|
||||
# HTML dependency (which we currently do for other themable widgets),
|
||||
# but DT, crosstalk, and maybe other pkgs include selectize JS/CSS
|
||||
@@ -253,10 +250,11 @@ selectizeDependencyFunc <- function(theme) {
|
||||
# name, the JS/CSS would be loaded/included twice, which leads to
|
||||
# strange issues, especially since we now include a 3rd party
|
||||
# accessibility plugin https://github.com/rstudio/shiny/pull/3153
|
||||
selectizeDir <- system_file(package = "shiny", "www/shared/selectize/")
|
||||
script <- file.path(selectizeDir, selectizeScripts())
|
||||
|
||||
bslib::bs_dependency(
|
||||
input = sass::sass_file(stylesheet),
|
||||
input = selectizeSass(bs_version),
|
||||
theme = theme,
|
||||
name = "selectize",
|
||||
version = version_selectize,
|
||||
@@ -265,6 +263,14 @@ selectizeDependencyFunc <- function(theme) {
|
||||
)
|
||||
}
|
||||
|
||||
selectizeSass <- function(bs_version) {
|
||||
selectizeDir <- system_file(package = "shiny", "www/shared/selectize/")
|
||||
stylesheet <- file.path(
|
||||
selectizeDir, "scss", paste0("selectize.bootstrap", bs_version, ".scss")
|
||||
)
|
||||
sass::sass_file(stylesheet)
|
||||
}
|
||||
|
||||
selectizeStaticDependency <- function(version) {
|
||||
htmlDependency(
|
||||
"selectize",
|
||||
|
||||
@@ -222,6 +222,15 @@ ionRangeSliderDependency <- function() {
|
||||
)
|
||||
}
|
||||
|
||||
ionRangeSliderDependencySass <- function() {
|
||||
list(
|
||||
list(accent = "$component-active-bg"),
|
||||
sass::sass_file(
|
||||
system_file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ionRangeSliderDependencyCSS <- function(theme) {
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(htmlDependency(
|
||||
@@ -234,12 +243,7 @@ ionRangeSliderDependencyCSS <- function(theme) {
|
||||
}
|
||||
|
||||
bslib::bs_dependency(
|
||||
input = list(
|
||||
list(accent = "$component-active-bg"),
|
||||
sass::sass_file(
|
||||
system_file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
|
||||
)
|
||||
),
|
||||
input = ionRangeSliderDependencySass(),
|
||||
theme = theme,
|
||||
name = "ionRangeSlider",
|
||||
version = version_ion_range_slider,
|
||||
|
||||
14
R/react.R
@@ -53,10 +53,12 @@ Context <- R6Class(
|
||||
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
withReactiveDomain(.domain, {
|
||||
env <- .getReactiveEnvironment()
|
||||
rLog$enter(.reactId, id, .reactType, .domain)
|
||||
on.exit(rLog$exit(.reactId, id, .reactType, .domain), add = TRUE)
|
||||
env$runWith(self, func)
|
||||
captureStackTraces({
|
||||
env <- .getReactiveEnvironment()
|
||||
rLog$enter(.reactId, id, .reactType, .domain)
|
||||
on.exit(rLog$exit(.reactId, id, .reactType, .domain), add = TRUE)
|
||||
env$runWith(self, func)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
@@ -223,9 +225,7 @@ wrapForContext <- function(func, ctx) {
|
||||
|
||||
function(...) {
|
||||
.getReactiveEnvironment()$runWith(ctx, function() {
|
||||
captureStackTraces(
|
||||
func(...)
|
||||
)
|
||||
func(...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -951,7 +951,10 @@ Observable <- R6Class(
|
||||
#' See the [Shiny tutorial](https://shiny.rstudio.com/tutorial/) for
|
||||
#' more information about reactive expressions.
|
||||
#'
|
||||
#' @param x For `is.reactive()`, an object to test. For `reactive()`, an expression. When passing in a [`quo()`]sure with `reactive()`, remember to use [`rlang::inject()`] to distinguish that you are passing in the content of your quosure, not the expression of the quosure.
|
||||
#' @param x For `is.reactive()`, an object to test. For `reactive()`, an
|
||||
#' expression. When passing in a [`rlang::quo()`]sure with `reactive()`,
|
||||
#' remember to use [`rlang::inject()`] to distinguish that you are passing in
|
||||
#' the content of your quosure, not the expression of the quosure.
|
||||
#' @template param-env
|
||||
#' @templateVar x x
|
||||
#' @templateVar env env
|
||||
@@ -2301,7 +2304,7 @@ observeEvent <- function(eventExpr, handlerExpr,
|
||||
priority = priority,
|
||||
domain = domain,
|
||||
autoDestroy = TRUE,
|
||||
..stacktraceon = FALSE # TODO: Does this go in the bindEvent?
|
||||
..stacktraceon = TRUE
|
||||
))
|
||||
|
||||
o <- inject(bindEvent(
|
||||
|
||||
@@ -445,7 +445,9 @@ stopApp <- function(returnValue = invisible()) {
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the `shiny.host` option, if set, or `"127.0.0.1"` if not.
|
||||
#' @param display.mode The mode in which to display the example. Defaults to
|
||||
#' `showcase`, but may be set to `normal` to see the example without
|
||||
#' `"auto"`, which uses the value of `DisplayMode` in the example's
|
||||
#' `DESCRIPTION` file. Set to `"showcase"` to show the app code and
|
||||
#' description with the running app, or `"normal"` to see the example without
|
||||
#' code or commentary.
|
||||
#' @param package The package in which to find the example (defaults to
|
||||
#' `"shiny"`).
|
||||
|
||||
@@ -151,6 +151,11 @@ getShinyOption <- function(name, default = NULL) {
|
||||
# ' \item{shiny.devmode.verbose (defaults to `TRUE`)}{If `TRUE`, will display messages printed
|
||||
# ' about which options are being set. See [devmode()] for more details. }
|
||||
### (end not documenting 'shiny.devmode.verbose')
|
||||
### start shiny.client_devmode is primarily for niche, internal shinylive usage
|
||||
# ' \item{shiny.client_devmode (defaults to `FALSE`)}{If `TRUE`, enables client-
|
||||
# ' side devmode features. Currently the primary feature is the client-side
|
||||
# ' error console.}
|
||||
### end shiny.client_devmode
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
|
||||
@@ -2024,7 +2024,7 @@ ShinySession <- R6Class(
|
||||
tmpdata <- tempfile(fileext = ext)
|
||||
return(Context$new(getDefaultReactiveDomain(), '[download]')$run(function() {
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
promises::with_promise_domain(createStackTracePromiseDomain(), {
|
||||
captureStackTraces({
|
||||
self$incrementBusyCount()
|
||||
hybrid_chain(
|
||||
# ..stacktraceon matches with the top-level ..stacktraceoff..
|
||||
|
||||
18
R/shinyui.R
@@ -69,7 +69,7 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
|
||||
)
|
||||
}
|
||||
|
||||
if (in_devmode()) {
|
||||
if (in_devmode() || in_client_devmode()) {
|
||||
# If we're in dev mode, add a simple script to the head that injects a
|
||||
# global variable for the client to use to detect dev mode.
|
||||
shiny_deps[[length(shiny_deps) + 1]] <-
|
||||
@@ -114,6 +114,7 @@ jqueryDependency <- function() {
|
||||
shinyDependencies <- function() {
|
||||
list(
|
||||
bslib::bs_dependency_defer(shinyDependencyCSS),
|
||||
busyIndicatorDependency(),
|
||||
htmlDependency(
|
||||
name = "shiny-javascript",
|
||||
version = get_package_version("shiny"),
|
||||
@@ -134,6 +135,14 @@ shinyDependencies <- function() {
|
||||
)
|
||||
}
|
||||
|
||||
shinyDependencySass <- function(bs_version) {
|
||||
bootstrap_scss <- paste0("shiny.bootstrap", bs_version, ".scss")
|
||||
|
||||
scss_home <- system_file("www/shared/shiny_scss", package = "shiny")
|
||||
scss_files <- file.path(scss_home, c(bootstrap_scss, "shiny.scss"))
|
||||
lapply(scss_files, sass::sass_file)
|
||||
}
|
||||
|
||||
shinyDependencyCSS <- function(theme) {
|
||||
version <- get_package_version("shiny")
|
||||
|
||||
@@ -149,14 +158,9 @@ shinyDependencyCSS <- function(theme) {
|
||||
}
|
||||
|
||||
bs_version <- bslib::theme_version(theme)
|
||||
bootstrap_scss <- paste0("shiny.bootstrap", bs_version, ".scss")
|
||||
|
||||
scss_home <- system_file("www/shared/shiny_scss", package = "shiny")
|
||||
scss_files <- file.path(scss_home, c(bootstrap_scss, "shiny.scss"))
|
||||
scss_files <- lapply(scss_files, sass::sass_file)
|
||||
|
||||
bslib::bs_dependency(
|
||||
input = scss_files,
|
||||
input = shinyDependencySass(bs_version),
|
||||
theme = theme,
|
||||
name = "shiny-sass",
|
||||
version = version,
|
||||
|
||||
@@ -53,8 +53,8 @@ formalsAndBody <- function(x) {
|
||||
|
||||
#' @describeIn createRenderFunction convert a quosure to a function.
|
||||
#' @param q Quosure of the expression `x`. When capturing expressions to create
|
||||
#' your quosure, it is recommended to use [`enquo0()`] to not unquote the
|
||||
#' object too early. See [`enquo0()`] for more details.
|
||||
#' your quosure, it is recommended to use [`rlang::enquo0()`] to not unquote
|
||||
#' the object too early. See [`rlang::enquo0()`] for more details.
|
||||
#' @inheritParams installExprFunction
|
||||
#' @export
|
||||
quoToFunction <- function(
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
## revdepcheck results
|
||||
|
||||
We checked 1278 reverse dependencies (1277 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of shiny.
|
||||
|
||||
* We saw 2 new problems (NOTEs only)
|
||||
* We failed to check 19 packages due to installation issues
|
||||
|
||||
Issues with CRAN packages are summarised below.
|
||||
|
||||
### New problems
|
||||
|
||||
R CMD check displayed NOTEs for two packages, unrelated to changes in shiny.
|
||||
|
||||
* HH
|
||||
checking installed package size ... NOTE
|
||||
|
||||
* PopED
|
||||
checking installed package size ... NOTE
|
||||
|
||||
### Failed to check
|
||||
|
||||
* animalEKF
|
||||
* AovBay
|
||||
* Certara.VPCResults
|
||||
* chipPCR
|
||||
* ctsem
|
||||
* dartR.sim
|
||||
* diveR
|
||||
* gap
|
||||
* jsmodule
|
||||
* loon.shiny
|
||||
* robmedExtra
|
||||
* rstanarm
|
||||
* SensMap
|
||||
* Seurat
|
||||
* shinyTempSignal
|
||||
* Signac
|
||||
* statsr
|
||||
* TestAnaAPP
|
||||
* tidyvpc
|
||||
154
inst/diagrams/outputProgressStateMachine.drawio
Normal file
@@ -0,0 +1,154 @@
|
||||
<mxfile host="app.diagrams.net" modified="2024-05-07T22:40:15.581Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" etag="Zsitjb4PT-sW3A63SWd7" version="24.3.1" type="device">
|
||||
<diagram name="Page-1" id="zz6aoPEyabkTD7ESu8ts">
|
||||
<mxGraphModel dx="595" dy="889" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-1" value="Initial" style="ellipse;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="270" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-2" value="Running" style="ellipse;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="270" y="270" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-3" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-1" target="DS1AFzV_2DL1v2c9v1jZ-2" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="260" y="480" as="sourcePoint" />
|
||||
<mxPoint x="310" y="270" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-4" value="Recalculating" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="210" y="250" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-6" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-2" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="320" y="220" as="sourcePoint" />
|
||||
<mxPoint x="310" y="350" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-7" value="Idle" style="ellipse;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="270" y="350" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-8" value="Recalculated" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="330" y="310" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-9" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-7" target="DS1AFzV_2DL1v2c9v1jZ-10" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="320" y="320" as="sourcePoint" />
|
||||
<mxPoint x="310" y="440" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="320" y="410" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-10" value="Value" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="280" y="440" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-11" value="Error" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="370" y="440" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-12" value="Persistent" style="ellipse;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="90" y="440" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-13" value="Cancel" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="180" y="440" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-14" value="<span style="text-align: start; font-size: 10pt; font-family: Arial;" data-sheets-userformat="{&quot;2&quot;:513,&quot;3&quot;:{&quot;1&quot;:0},&quot;12&quot;:0}" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;{progress: {type: \&quot;binding\&quot;, message: {persistent: true}}}&quot;}" data-sheets-root="1">{progress: {type: "binding", message: {persistent: true}}}</span>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="45" y="340" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-15" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-10" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="320" y="400" as="sourcePoint" />
|
||||
<mxPoint x="310" y="550" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="320" y="520" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-16" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-11" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="320" y="490" as="sourcePoint" />
|
||||
<mxPoint x="320" y="550" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-17" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-12" target="DS1AFzV_2DL1v2c9v1jZ-18" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="330" y="500" as="sourcePoint" />
|
||||
<mxPoint x="290" y="540" as="targetPoint" />
|
||||
<Array as="points" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-18" value="Invalidated" style="ellipse;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="260" y="550" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-20" value="" style="curved=1;endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-18" target="DS1AFzV_2DL1v2c9v1jZ-2" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="260" y="480" as="sourcePoint" />
|
||||
<mxPoint x="310" y="430" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="420" y="610" />
|
||||
<mxPoint x="550" y="470" />
|
||||
<mxPoint x="440" y="320" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-23" value="Recalculating" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="450" y="340" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-24" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-2" target="DS1AFzV_2DL1v2c9v1jZ-12" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="320" y="400" as="sourcePoint" />
|
||||
<mxPoint x="320" y="450" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-25" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0.395;entryY=-0.025;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-7" target="DS1AFzV_2DL1v2c9v1jZ-11" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="330" y="410" as="sourcePoint" />
|
||||
<mxPoint x="330" y="460" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="380" y="410" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-26" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-7" target="DS1AFzV_2DL1v2c9v1jZ-13" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="340" y="420" as="sourcePoint" />
|
||||
<mxPoint x="340" y="470" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-27" value="Value" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="270" y="400" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-28" value="Error" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="330" y="400" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-29" value="No message" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="200" y="400" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-30" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-13" target="DS1AFzV_2DL1v2c9v1jZ-18" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="230" y="490" as="sourcePoint" />
|
||||
<mxPoint x="300" y="558" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="240" y="520" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-31" value="<span style="font-family: Arial; font-size: 13px; text-align: left; white-space: pre-wrap; background-color: rgb(255, 255, 255);">{progress: {type: "binding"}}</span>" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="190" y="490" width="180" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DS1AFzV_2DL1v2c9v1jZ-35" value="<h1 style="margin-top: 0px;">Shiny output progress states</h1><p>This diagram depicts a state machine of output binding progress state. Each node represents a possible state and each edge represents a server-&gt;client message that moves outputs from one state to another. <b>If a node is highlighted in blue</b>, then the output should be showing a busy state when visible (i.e., <font face="Courier New">binding.showProgress(true)</font>)</p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="85" y="120" width="465" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="J9lKobNiy15ndT9nfcn--1" value="" style="curved=1;endArrow=classic;html=1;rounded=0;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="DS1AFzV_2DL1v2c9v1jZ-7" target="DS1AFzV_2DL1v2c9v1jZ-18">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="280" y="480" as="sourcePoint" />
|
||||
<mxPoint x="220" y="510" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="610" y="420" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
4
inst/diagrams/outputProgressStateMachine.svg
Normal file
|
After Width: | Height: | Size: 312 KiB |
2
inst/www/shared/busy-indicators/busy-indicators.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/*! shiny 1.10.0 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
:where([data-shiny-busy-spinners] .recalculating){position:relative}[data-shiny-busy-spinners] .recalculating:after{position:absolute;content:"";--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));--_shiny-spinner-size: var(--shiny-spinner-size, 32px);--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);background:var(--_shiny-spinner-color);width:var(--_shiny-spinner-size);height:var(--_shiny-spinner-size);inset:calc(50% - var(--_shiny-spinner-size) / 2);mask-image:var(--_shiny-spinner-url);-webkit-mask-image:var(--_shiny-spinner-url);opacity:0;animation-delay:var(--_shiny-spinner-delay);animation-name:fade-in;animation-duration:.25s;animation-fill-mode:forwards}[data-shiny-busy-spinners] .recalculating:has(>*),[data-shiny-busy-spinners] .recalculating:empty{opacity:1}[data-shiny-busy-spinners] .recalculating>*:not(.recalculating){opacity:var(--_shiny-fade-opacity);transition:opacity .25s ease var(--shiny-spinner-delay, 1s)}[data-shiny-busy-spinners] .recalculating.shiny-html-output:after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating:not(.shiny-html-output)):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(#shiny-disconnected-overlay):after{display:none}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:has(#shiny-disconnected-overlay):after{display:none}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes busy-page-pulse{0%{left:-14%;right:97%}45%{left:0%;right:14%}55%{left:14%;right:0%}to{left:97%;right:-14%}}.shiny-spinner-output-container{--shiny-spinner-size: 0px}
|
||||
20
inst/www/shared/busy-indicators/spinners/LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Utkarsh Verma
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
1
inst/www/shared/busy-indicators/spinners/bars.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_hzlK{animation:spinner_vc4H .8s linear infinite;animation-delay:-.8s}.spinner_koGT{animation-delay:-.65s}.spinner_YF1u{animation-delay:-.5s}@keyframes spinner_vc4H{0%{y:1px;height:22px}93.75%{y:5px;height:14px;opacity:.2}}</style><rect class="spinner_hzlK" x="1" y="1" width="6" height="22"/><rect class="spinner_hzlK spinner_koGT" x="9" y="1" width="6" height="22"/><rect class="spinner_hzlK spinner_YF1u" x="17" y="1" width="6" height="22"/></svg>
|
||||
|
After Width: | Height: | Size: 526 B |
1
inst/www/shared/busy-indicators/spinners/bars2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_jCIR{animation:spinner_B8Vq .9s linear infinite;animation-delay:-.9s}.spinner_upm8{animation-delay:-.8s}.spinner_2eL5{animation-delay:-.7s}.spinner_Rp9l{animation-delay:-.6s}.spinner_dy3W{animation-delay:-.5s}@keyframes spinner_B8Vq{0%,66.66%{animation-timing-function:cubic-bezier(0.36,.61,.3,.98);y:6px;height:12px}33.33%{animation-timing-function:cubic-bezier(0.36,.61,.3,.98);y:1px;height:22px}}</style><rect class="spinner_jCIR" x="1" y="6" width="2.8" height="12"/><rect class="spinner_jCIR spinner_upm8" x="5.8" y="6" width="2.8" height="12"/><rect class="spinner_jCIR spinner_2eL5" x="10.6" y="6" width="2.8" height="12"/><rect class="spinner_jCIR spinner_Rp9l" x="15.4" y="6" width="2.8" height="12"/><rect class="spinner_jCIR spinner_dy3W" x="20.2" y="6" width="2.8" height="12"/></svg>
|
||||
|
After Width: | Height: | Size: 873 B |
1
inst/www/shared/busy-indicators/spinners/bars3.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_OSmW{transform-origin:center;animation:spinner_T6mA .75s step-end infinite}@keyframes spinner_T6mA{8.3%{transform:rotate(30deg)}16.6%{transform:rotate(60deg)}25%{transform:rotate(90deg)}33.3%{transform:rotate(120deg)}41.6%{transform:rotate(150deg)}50%{transform:rotate(180deg)}58.3%{transform:rotate(210deg)}66.6%{transform:rotate(240deg)}75%{transform:rotate(270deg)}83.3%{transform:rotate(300deg)}91.6%{transform:rotate(330deg)}100%{transform:rotate(360deg)}}</style><g class="spinner_OSmW"><rect x="11" y="1" width="2" height="5" opacity=".14"/><rect x="11" y="1" width="2" height="5" transform="rotate(30 12 12)" opacity=".29"/><rect x="11" y="1" width="2" height="5" transform="rotate(60 12 12)" opacity=".43"/><rect x="11" y="1" width="2" height="5" transform="rotate(90 12 12)" opacity=".57"/><rect x="11" y="1" width="2" height="5" transform="rotate(120 12 12)" opacity=".71"/><rect x="11" y="1" width="2" height="5" transform="rotate(150 12 12)" opacity=".86"/><rect x="11" y="1" width="2" height="5" transform="rotate(180 12 12)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
inst/www/shared/busy-indicators/spinners/dots.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_b2T7{animation:spinner_xe7Q .8s linear infinite}.spinner_YRVV{animation-delay:-.65s}.spinner_c9oY{animation-delay:-.5s}@keyframes spinner_xe7Q{93.75%,100%{r:3px}46.875%{r:.2px}}</style><circle class="spinner_b2T7" cx="4" cy="12" r="3"/><circle class="spinner_b2T7 spinner_YRVV" cx="12" cy="12" r="3"/><circle class="spinner_b2T7 spinner_c9oY" cx="20" cy="12" r="3"/></svg>
|
||||
|
After Width: | Height: | Size: 449 B |
1
inst/www/shared/busy-indicators/spinners/dots2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_Wezc{transform-origin:center;animation:spinner_Oiah .75s step-end infinite}@keyframes spinner_Oiah{8.3%{transform:rotate(30deg)}16.6%{transform:rotate(60deg)}25%{transform:rotate(90deg)}33.3%{transform:rotate(120deg)}41.6%{transform:rotate(150deg)}50%{transform:rotate(180deg)}58.3%{transform:rotate(210deg)}66.6%{transform:rotate(240deg)}75%{transform:rotate(270deg)}83.3%{transform:rotate(300deg)}91.6%{transform:rotate(330deg)}100%{transform:rotate(360deg)}}</style><g class="spinner_Wezc"><circle cx="12" cy="2.5" r="1.5" opacity=".14"/><circle cx="16.75" cy="3.77" r="1.5" opacity=".29"/><circle cx="20.23" cy="7.25" r="1.5" opacity=".43"/><circle cx="21.50" cy="12.00" r="1.5" opacity=".57"/><circle cx="20.23" cy="16.75" r="1.5" opacity=".71"/><circle cx="16.75" cy="20.23" r="1.5" opacity=".86"/><circle cx="12" cy="21.5" r="1.5"/></g></svg>
|
||||
|
After Width: | Height: | Size: 926 B |
1
inst/www/shared/busy-indicators/spinners/dots3.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_DupU{animation:spinner_sM3D 1.2s infinite}.spinner_GWtZ{animation-delay:.1s}.spinner_dwN6{animation-delay:.2s}.spinner_46QP{animation-delay:.3s}.spinner_PD82{animation-delay:.4s}.spinner_eUgh{animation-delay:.5s}.spinner_eUaP{animation-delay:.6s}.spinner_j38H{animation-delay:.7s}.spinner_tVmX{animation-delay:.8s}.spinner_DQhX{animation-delay:.9s}.spinner_GIL4{animation-delay:1s}.spinner_n0Yb{animation-delay:1.1s}@keyframes spinner_sM3D{0%,50%{animation-timing-function:cubic-bezier(0,1,0,1);r:0}10%{animation-timing-function:cubic-bezier(.53,0,.61,.73);r:2px}}</style><circle class="spinner_DupU" cx="12" cy="3" r="0"/><circle class="spinner_DupU spinner_GWtZ" cx="16.50" cy="4.21" r="0"/><circle class="spinner_DupU spinner_n0Yb" cx="7.50" cy="4.21" r="0"/><circle class="spinner_DupU spinner_dwN6" cx="19.79" cy="7.50" r="0"/><circle class="spinner_DupU spinner_GIL4" cx="4.21" cy="7.50" r="0"/><circle class="spinner_DupU spinner_46QP" cx="21.00" cy="12.00" r="0"/><circle class="spinner_DupU spinner_DQhX" cx="3.00" cy="12.00" r="0"/><circle class="spinner_DupU spinner_PD82" cx="19.79" cy="16.50" r="0"/><circle class="spinner_DupU spinner_tVmX" cx="4.21" cy="16.50" r="0"/><circle class="spinner_DupU spinner_eUgh" cx="16.50" cy="19.79" r="0"/><circle class="spinner_DupU spinner_j38H" cx="7.50" cy="19.79" r="0"/><circle class="spinner_DupU spinner_eUaP" cx="12" cy="21" r="0"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
inst/www/shared/busy-indicators/spinners/pulse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_ZCsl{animation:spinner_qV4G 1.2s cubic-bezier(0.52,.6,.25,.99) infinite}.spinner_gaIW{animation-delay:.6s}@keyframes spinner_qV4G{0%{r:0;opacity:1}100%{r:11px;opacity:0}}</style><circle class="spinner_ZCsl" cx="12" cy="12" r="0"/><circle class="spinner_ZCsl spinner_gaIW" cx="12" cy="12" r="0"/></svg>
|
||||
|
After Width: | Height: | Size: 378 B |
1
inst/www/shared/busy-indicators/spinners/pulse2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_ngNb{animation:spinner_ZRWK 1.2s cubic-bezier(0.52,.6,.25,.99) infinite}.spinner_6TBP{animation-delay:.6s}@keyframes spinner_ZRWK{0%{transform:translate(12px,12px) scale(0);opacity:1}100%{transform:translate(0,0) scale(1);opacity:0}}</style><path class="spinner_ngNb" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" transform="translate(12, 12) scale(0)"/><path class="spinner_ngNb spinner_6TBP" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" transform="translate(12, 12) scale(0)"/></svg>
|
||||
|
After Width: | Height: | Size: 635 B |
1
inst/www/shared/busy-indicators/spinners/pulse3.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_Uvk8{animation:spinner_otJF 1.6s cubic-bezier(.52,.6,.25,.99) infinite}.spinner_ypeD{animation-delay:.2s}.spinner_y0Rj{animation-delay:.4s}@keyframes spinner_otJF{0%{transform:translate(12px,12px) scale(0);opacity:1}75%,100%{transform:translate(0,0) scale(1);opacity:0}}</style><path class="spinner_Uvk8" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" transform="translate(12, 12) scale(0)"/><path class="spinner_Uvk8 spinner_ypeD" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" transform="translate(12, 12) scale(0)"/><path class="spinner_Uvk8 spinner_y0Rj" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" transform="translate(12, 12) scale(0)"/></svg>
|
||||
|
After Width: | Height: | Size: 834 B |
1
inst/www/shared/busy-indicators/spinners/ring.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg stroke="#000" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_V8m1{transform-origin:center;animation:spinner_zKoa 2s linear infinite}.spinner_V8m1 circle{stroke-linecap:round;animation:spinner_YpZS 1.5s ease-in-out infinite}@keyframes spinner_zKoa{100%{transform:rotate(360deg)}}@keyframes spinner_YpZS{0%{stroke-dasharray:0 150;stroke-dashoffset:0}47.5%{stroke-dasharray:42 150;stroke-dashoffset:-16}95%,100%{stroke-dasharray:42 150;stroke-dashoffset:-59}}</style><g class="spinner_V8m1"><circle cx="12" cy="12" r="9.5" fill="none" stroke-width="3"></circle></g></svg>
|
||||
|
After Width: | Height: | Size: 598 B |
1
inst/www/shared/busy-indicators/spinners/ring2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_ajPY{transform-origin:center;animation:spinner_AtaB .75s infinite linear}@keyframes spinner_AtaB{100%{transform:rotate(360deg)}}</style><path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_ajPY"/></svg>
|
||||
|
After Width: | Height: | Size: 509 B |
1
inst/www/shared/busy-indicators/spinners/ring3.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_aj0A{transform-origin:center;animation:spinner_KYSC .75s infinite linear}@keyframes spinner_KYSC{100%{transform:rotate(360deg)}}</style><path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" class="spinner_aj0A"/></svg>
|
||||
|
After Width: | Height: | Size: 412 B |
@@ -235,6 +235,10 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-handle.type_last {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-handle.state_hover, .irs--shiny .irs-handle:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@@ -143,6 +143,11 @@ $font-family: $font-family-base !default;
|
||||
border-radius: $handle_width;
|
||||
z-index: 2;
|
||||
|
||||
&.type_last {
|
||||
// Ensure last-used handle is on top if it overlaps with another handle
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&.state_hover,
|
||||
&:hover {
|
||||
background: $handle_color_hover;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.8.1.9000 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.10.0 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,Courier New,monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:400}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav,#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.8.1.9000 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.10.0 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
"use strict";(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
|
||||
//# sourceMappingURL=shiny-testmode.js.map
|
||||
|
||||
24919
inst/www/shared/shiny.js
4
inst/www/shared/shiny.min.css
vendored
4
inst/www/shared/shiny.min.js
vendored
@@ -44,9 +44,9 @@ div:where(.shiny-html-output) {
|
||||
/* uiOutput()/ conditionalPanel() are "pass-through" containers when they have children. */
|
||||
&:has(> *) {
|
||||
display: contents;
|
||||
/* Pass along styles that no longer impact the pass-through container */
|
||||
/* Pass along styles that no longer impact the pass-through container */
|
||||
&.recalculating > * {
|
||||
opacity: 0.3;
|
||||
opacity: var(--_shiny-fade-opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,8 @@ html.autoreload-enabled #shiny-disconnected-overlay.reloading {
|
||||
}
|
||||
|
||||
.recalculating {
|
||||
opacity: 0.3;
|
||||
--_shiny-fade-opacity: var(--shiny-fade-opacity, 0.3);
|
||||
opacity: var(--_shiny-fade-opacity);
|
||||
transition: opacity 250ms ease 500ms;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,56 @@ is, a function that quickly returns a promise) and allows even that very
|
||||
session to immediately unblock and carry on with other user interactions.
|
||||
}
|
||||
|
||||
\examples{
|
||||
\dontshow{if (rlang::is_interactive() && rlang::is_installed("future")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
|
||||
library(shiny)
|
||||
library(bslib)
|
||||
library(future)
|
||||
plan(multisession)
|
||||
|
||||
ui <- page_fluid(
|
||||
titlePanel("Extended Task Demo"),
|
||||
p(
|
||||
'Click the button below to perform a "calculation"',
|
||||
"that takes a while to perform."
|
||||
),
|
||||
input_task_button("recalculate", "Recalculate"),
|
||||
p(textOutput("result"))
|
||||
)
|
||||
|
||||
server <- function(input, output) {
|
||||
rand_task <- ExtendedTask$new(function() {
|
||||
future(
|
||||
{
|
||||
# Slow operation goes here
|
||||
Sys.sleep(2)
|
||||
sample(1:100, 1)
|
||||
},
|
||||
seed = TRUE
|
||||
)
|
||||
})
|
||||
|
||||
# Make button state reflect task.
|
||||
# If using R >=4.1, you can do this instead:
|
||||
# rand_task <- ExtendedTask$new(...) |> bind_task_button("recalculate")
|
||||
bind_task_button(rand_task, "recalculate")
|
||||
|
||||
observeEvent(input$recalculate, {
|
||||
# Invoke the extended in an observer
|
||||
rand_task$invoke()
|
||||
})
|
||||
|
||||
output$result <- renderText({
|
||||
# React to updated results when the task completes
|
||||
number <- rand_task$result()
|
||||
paste0("Your number is ", number, ".")
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
\dontshow{\}) # examplesIf}
|
||||
}
|
||||
\section{Methods}{
|
||||
\subsection{Public methods}{
|
||||
\itemize{
|
||||
|
||||
@@ -182,7 +182,7 @@ If you want to use a cache that is shared across multiple R processes, you
|
||||
can use a \code{\link[cachem:cache_disk]{cachem::cache_disk()}}. You can create a application-level shared
|
||||
cache by putting this at the top of your app.R, server.R, or global.R:
|
||||
|
||||
\if{html}{\out{<div class="sourceCode">}}\preformatted{shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
|
||||
\if{html}{\out{<div class="sourceCode">}}\preformatted{shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
This will create a subdirectory in your system temp directory named
|
||||
|
||||
136
man/busyIndicatorOptions.Rd
Normal file
@@ -0,0 +1,136 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/busy-indicators.R
|
||||
\name{busyIndicatorOptions}
|
||||
\alias{busyIndicatorOptions}
|
||||
\title{Customize busy indicator options}
|
||||
\usage{
|
||||
busyIndicatorOptions(
|
||||
...,
|
||||
spinner_type = NULL,
|
||||
spinner_color = NULL,
|
||||
spinner_size = NULL,
|
||||
spinner_delay = NULL,
|
||||
spinner_selector = NULL,
|
||||
fade_opacity = NULL,
|
||||
fade_selector = NULL,
|
||||
pulse_background = NULL,
|
||||
pulse_height = NULL,
|
||||
pulse_speed = NULL
|
||||
)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{Currently ignored.}
|
||||
|
||||
\item{spinner_type}{The type of spinner. Pre-bundled types include:
|
||||
'ring', 'ring2', 'ring3', 'bars', 'bars2', 'bars3', 'pulse', 'pulse2', 'pulse3', 'dots', 'dots2', 'dots3'.
|
||||
|
||||
A path to a local SVG file can also be provided. The SVG should adhere to
|
||||
the following rules:
|
||||
\itemize{
|
||||
\item The SVG itself should contain the animation.
|
||||
\item It should avoid absolute sizes (the spinner's containing DOM element
|
||||
size is set in CSS by \code{spinner_size}, so it should fill that container).
|
||||
\item It should avoid setting absolute colors (the spinner's containing DOM element
|
||||
color is set in CSS by \code{spinner_color}, so it should inherit that color).
|
||||
}}
|
||||
|
||||
\item{spinner_color}{The color of the spinner. This can be any valid CSS
|
||||
color. Defaults to the app's "primary" color if Bootstrap is on the page.}
|
||||
|
||||
\item{spinner_size}{The size of the spinner. This can be any valid CSS size.}
|
||||
|
||||
\item{spinner_delay}{The amount of time to wait before showing the spinner.
|
||||
This can be any valid CSS time and can be useful for not showing the spinner
|
||||
if the computation finishes quickly.}
|
||||
|
||||
\item{spinner_selector}{A character string containing a CSS selector for
|
||||
scoping the spinner customization. The default (\code{NULL}) will apply the
|
||||
spinner customization to the parent element of the spinner.}
|
||||
|
||||
\item{fade_opacity}{The opacity (a number between 0 and 1) for recalculating
|
||||
output. Set to 1 to "disable" the fade.}
|
||||
|
||||
\item{fade_selector}{A character string containing a CSS selector for
|
||||
scoping the spinner customization. The default (\code{NULL}) will apply the
|
||||
spinner customization to the parent element of the spinner.}
|
||||
|
||||
\item{pulse_background}{A CSS background definition for the pulse. The
|
||||
default uses a
|
||||
\href{https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient}{linear-gradient}
|
||||
of the theme's indigo, purple, and pink colors.}
|
||||
|
||||
\item{pulse_height}{The height of the pulsing banner. This can be any valid
|
||||
CSS size.}
|
||||
|
||||
\item{pulse_speed}{The speed of the pulsing banner. This can be any valid CSS
|
||||
time.}
|
||||
}
|
||||
\description{
|
||||
Shiny automatically includes busy indicators, which more specifically means:
|
||||
\enumerate{
|
||||
\item Calculating/recalculating outputs have a spinner overlay.
|
||||
\item Outputs fade out/in when recalculating.
|
||||
\item When no outputs are calculating/recalculating, but Shiny is busy
|
||||
doing something else (e.g., a download, side-effect, etc), a page-level
|
||||
pulsing banner is shown.
|
||||
}
|
||||
|
||||
This function allows you to customize the appearance of these busy indicators
|
||||
by including the result of this function inside the app's UI. Note that,
|
||||
unless \code{spinner_selector} (or \code{fade_selector}) is specified, the spinner/fade
|
||||
customization applies to the parent element. If the customization should
|
||||
instead apply to the entire page, set \code{spinner_selector = 'html'} and
|
||||
\code{fade_selector = 'html'}.
|
||||
}
|
||||
\examples{
|
||||
\dontshow{if (rlang::is_interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
|
||||
library(bslib)
|
||||
|
||||
card_ui <- function(id, spinner_type = id) {
|
||||
card(
|
||||
busyIndicatorOptions(spinner_type = spinner_type),
|
||||
card_header(paste("Spinner:", spinner_type)),
|
||||
plotOutput(shiny::NS(id, "plot"))
|
||||
)
|
||||
}
|
||||
|
||||
card_server <- function(id, simulate = reactive()) {
|
||||
moduleServer(
|
||||
id = id,
|
||||
function(input, output, session) {
|
||||
output$plot <- renderPlot({
|
||||
Sys.sleep(1)
|
||||
simulate()
|
||||
plot(x = rnorm(100), y = rnorm(100))
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ui <- page_fillable(
|
||||
useBusyIndicators(),
|
||||
input_task_button("simulate", "Simulate", icon = icon("refresh")),
|
||||
layout_columns(
|
||||
card_ui("ring"),
|
||||
card_ui("bars"),
|
||||
card_ui("dots"),
|
||||
card_ui("pulse"),
|
||||
col_widths = 6
|
||||
)
|
||||
)
|
||||
|
||||
server <- function(input, output, session) {
|
||||
simulate <- reactive(input$simulate)
|
||||
card_server("ring", simulate)
|
||||
card_server("bars", simulate)
|
||||
card_server("dots", simulate)
|
||||
card_server("pulse", simulate)
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
\dontshow{\}) # examplesIf}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=useBusyIndicators]{useBusyIndicators()}} to disable/enable busy indicators.
|
||||
}
|
||||
@@ -72,8 +72,8 @@ example, some render functions call \code{\link[=createWebDependency]{createWebD
|
||||
is able to serve JS and CSS resources.}
|
||||
|
||||
\item{q}{Quosure of the expression \code{x}. When capturing expressions to create
|
||||
your quosure, it is recommended to use \code{\link[=enquo0]{enquo0()}} to not unquote the
|
||||
object too early. See \code{\link[=enquo0]{enquo0()}} for more details.}
|
||||
your quosure, it is recommended to use \code{\link[rlang:defusing-advanced]{rlang::enquo0()}} to not unquote
|
||||
the object too early. See \code{\link[rlang:defusing-advanced]{rlang::enquo0()}} for more details.}
|
||||
|
||||
\item{label}{A label for the object to be shown in the debugger. Defaults to
|
||||
the name of the calling function.}
|
||||
|
||||
@@ -18,7 +18,10 @@ reactive(
|
||||
is.reactive(x)
|
||||
}
|
||||
\arguments{
|
||||
\item{x}{For \code{is.reactive()}, an object to test. For \code{reactive()}, an expression. When passing in a \code{\link[=quo]{quo()}}sure with \code{reactive()}, remember to use \code{\link[rlang:inject]{rlang::inject()}} to distinguish that you are passing in the content of your quosure, not the expression of the quosure.}
|
||||
\item{x}{For \code{is.reactive()}, an object to test. For \code{reactive()}, an
|
||||
expression. When passing in a \code{\link[rlang:defusing-advanced]{rlang::quo()}}sure with \code{reactive()},
|
||||
remember to use \code{\link[rlang:inject]{rlang::inject()}} to distinguish that you are passing in
|
||||
the content of your quosure, not the expression of the quosure.}
|
||||
|
||||
\item{env}{The parent environment for the reactive expression. By default,
|
||||
this is the calling environment, the same as when defining an ordinary
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
\alias{reactlog}
|
||||
\alias{reactlogShow}
|
||||
\alias{reactlogReset}
|
||||
\alias{reactlogAddMark}
|
||||
\title{Reactive Log Visualizer}
|
||||
\usage{
|
||||
reactlog()
|
||||
@@ -11,10 +12,15 @@ reactlog()
|
||||
reactlogShow(time = TRUE)
|
||||
|
||||
reactlogReset()
|
||||
|
||||
reactlogAddMark(session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{time}{A boolean that specifies whether or not to display the
|
||||
time that each reactive takes to calculate a result.}
|
||||
|
||||
\item{session}{The Shiny session to assign the mark to. Defaults to the
|
||||
current session.}
|
||||
}
|
||||
\description{
|
||||
Provides an interactive browser-based tool for visualizing reactive
|
||||
@@ -22,7 +28,7 @@ dependencies and execution in your application.
|
||||
}
|
||||
\details{
|
||||
To use the reactive log visualizer, start with a fresh R session and
|
||||
run the command \code{options(shiny.reactlog=TRUE)}; then launch your
|
||||
run the command \code{reactlog::reactlog_enable()}; then launch your
|
||||
application in the usual way (e.g. using \code{\link[=runApp]{runApp()}}). At
|
||||
any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
||||
web browser to launch the reactive log visualization.
|
||||
@@ -45,17 +51,37 @@ browser will not load new activity into the report; you will need to
|
||||
call \code{reactlogShow()} explicitly.
|
||||
|
||||
For security and performance reasons, do not enable
|
||||
\code{shiny.reactlog} in production environments. When the option is
|
||||
enabled, it's possible for any user of your app to see at least some
|
||||
of the source code of your reactive expressions and observers.
|
||||
\code{options(shiny.reactlog=TRUE)} (or \code{reactlog::reactlog_enable()}) in
|
||||
production environments. When the option is enabled, it's possible
|
||||
for any user of your app to see at least some of the source code of
|
||||
your reactive expressions and observers. In addition, reactlog
|
||||
should be considered a memory leak as it will constantly grow and
|
||||
will never reset until the R session is restarted.
|
||||
}
|
||||
\section{Functions}{
|
||||
\itemize{
|
||||
\item \code{reactlog()}: Return a list of reactive information. Can be used in conjunction with
|
||||
\link[reactlog:reactlog_show]{reactlog::reactlog_show} to later display the reactlog graph.
|
||||
\item \code{reactlog()}: Return a list of reactive information. Can be used in
|
||||
conjunction with \link[reactlog:reactlog_show]{reactlog::reactlog_show} to later display the reactlog
|
||||
graph.
|
||||
|
||||
\item \code{reactlogShow()}: Display a full reactlog graph for all sessions.
|
||||
|
||||
\item \code{reactlogReset()}: Resets the entire reactlog stack. Useful for debugging and removing all prior reactive history.
|
||||
\item \code{reactlogReset()}: Resets the entire reactlog stack. Useful for debugging
|
||||
and removing all prior reactive history.
|
||||
|
||||
\item \code{reactlogAddMark()}: Adds "mark" entry into the reactlog stack. This is
|
||||
useful for programmatically adding a marked entry in the reactlog, rather
|
||||
than using your keyboard's key combination.
|
||||
|
||||
For example, we can \emph{mark} the reactlog at the beginning of an
|
||||
\code{observeEvent}'s calculation:
|
||||
|
||||
\if{html}{\out{<div class="sourceCode r">}}\preformatted{observeEvent(input$my_event_trigger, \{
|
||||
# Add a mark in the reactlog
|
||||
reactlogAddMark()
|
||||
# Run your regular event reaction code here...
|
||||
....
|
||||
\})
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
}}
|
||||
|
||||
@@ -12,9 +12,10 @@ registerThemeDependency(func)
|
||||
of them.}
|
||||
}
|
||||
\description{
|
||||
This function registers a function that returns an \code{\link[=htmlDependency]{htmlDependency()}} or list
|
||||
of such objects. If \code{session$setCurrentTheme()} is called, the function will
|
||||
be re-executed, and the resulting html dependency will be sent to the client.
|
||||
This function registers a function that returns an
|
||||
\code{\link[htmltools:htmlDependency]{htmltools::htmlDependency()}} or list of such objects. If
|
||||
\code{session$setCurrentTheme()} is called, the function will be re-executed, and
|
||||
the resulting html dependency will be sent to the client.
|
||||
}
|
||||
\details{
|
||||
Note that \code{func} should \strong{not} be an anonymous function, or a function which
|
||||
|
||||
@@ -33,7 +33,9 @@ interactive sessions only.}
|
||||
to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not.}
|
||||
|
||||
\item{display.mode}{The mode in which to display the example. Defaults to
|
||||
\code{showcase}, but may be set to \code{normal} to see the example without
|
||||
\code{"auto"}, which uses the value of \code{DisplayMode} in the example's
|
||||
\code{DESCRIPTION} file. Set to \code{"showcase"} to show the app code and
|
||||
description with the running app, or \code{"normal"} to see the example without
|
||||
code or commentary.}
|
||||
|
||||
\item{package}{The package in which to find the example (defaults to
|
||||
|
||||
68
man/useBusyIndicators.Rd
Normal file
@@ -0,0 +1,68 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/busy-indicators.R
|
||||
\name{useBusyIndicators}
|
||||
\alias{useBusyIndicators}
|
||||
\title{Enable/disable busy indication}
|
||||
\usage{
|
||||
useBusyIndicators(..., spinners = TRUE, pulse = TRUE, fade = TRUE)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{Currently ignored.}
|
||||
|
||||
\item{spinners}{Whether to show a spinner on each calculating/recalculating
|
||||
output.}
|
||||
|
||||
\item{pulse}{Whether to show a pulsing banner at the top of the page when the
|
||||
app is busy.}
|
||||
|
||||
\item{fade}{Whether to fade recalculating outputs. A value of \code{FALSE} is
|
||||
equivalent to \code{busyIndicatorOptions(fade_opacity=1)}.}
|
||||
}
|
||||
\description{
|
||||
Busy indicators provide a visual cue to users when the server is busy
|
||||
calculating outputs or otherwise performing tasks (e.g., producing
|
||||
downloads). When enabled, a spinner is shown on each
|
||||
calculating/recalculating output, and a pulsing banner is shown at the top of
|
||||
the page when the app is otherwise busy. Busy indication is enabled by
|
||||
default for UI created with \pkg{bslib}, but must be enabled otherwise. To
|
||||
enable/disable, include the result of this function in anywhere in the app's
|
||||
UI.
|
||||
}
|
||||
\details{
|
||||
When both \code{spinners} and \code{pulse} are set to \code{TRUE}, the pulse is
|
||||
automatically disabled when spinner(s) are active. When both \code{spinners} and
|
||||
\code{pulse} are set to \code{FALSE}, no busy indication is shown (other than the
|
||||
graying out of recalculating outputs).
|
||||
}
|
||||
\examples{
|
||||
\dontshow{if (rlang::is_interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
|
||||
library(bslib)
|
||||
|
||||
ui <- page_fillable(
|
||||
useBusyIndicators(),
|
||||
card(
|
||||
card_header(
|
||||
"A plot",
|
||||
input_task_button("simulate", "Simulate"),
|
||||
class = "d-flex justify-content-between align-items-center"
|
||||
),
|
||||
plotOutput("p"),
|
||||
)
|
||||
)
|
||||
|
||||
server <- function(input, output) {
|
||||
output$p <- renderPlot({
|
||||
input$simulate
|
||||
Sys.sleep(4)
|
||||
plot(x = rnorm(100), y = rnorm(100))
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
\dontshow{\}) # examplesIf}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=busyIndicatorOptions]{busyIndicatorOptions()}} for customizing the appearance of the busy
|
||||
indicators.
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"homepage": "https://shiny.rstudio.com",
|
||||
"repository": "github:rstudio/shiny",
|
||||
"name": "@types/rstudio-shiny",
|
||||
"version": "1.8.1-alpha.9000",
|
||||
"version": "1.10.0",
|
||||
"license": "GPL-3.0-only",
|
||||
"main": "",
|
||||
"browser": "",
|
||||
@@ -69,6 +69,7 @@
|
||||
"phantomjs-prebuilt": "^2.1.16",
|
||||
"postcss": "^8.3.5",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"readcontrol": "^1.0.0",
|
||||
"replace": "^1.2.1",
|
||||
"ts-jest": "^26",
|
||||
@@ -94,5 +95,11 @@
|
||||
"coverage": "type-coverage -p tsconfig.json --at-least 90",
|
||||
"circular": "madge --circular --extensions ts srcts/src",
|
||||
"circular_image": "madge --circular --extensions ts --image madge.svg srcts/src"
|
||||
},
|
||||
"prettier": {
|
||||
"plugins": [
|
||||
"prettier-plugin-organize-imports"
|
||||
],
|
||||
"organizeImportsSkipDestructiveCodeActions": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
# Revdeps
|
||||
|
||||
## Failed to check (18)
|
||||
## Failed to check (20)
|
||||
|
||||
|package |version |error |warning |note |
|
||||
|:------------------|:-------|:-----|:-------|:----|
|
||||
|bigPint |? | | | |
|
||||
|bioCancer |? | | | |
|
||||
|ctsem |3.9.1 |1 | | |
|
||||
|animalEKF |1.2 |1 | | |
|
||||
|AovBay |0.1.0 |1 | | |
|
||||
|Certara.VPCResults |3.0.2 |1 | | |
|
||||
|chipPCR |1.0-2 |1 | | |
|
||||
|ctsem |3.10.1 |1 | | |
|
||||
|dartR.sim |? | | | |
|
||||
|diveR |? | | | |
|
||||
|EBImage |? | | | |
|
||||
|g3viz |? | | | |
|
||||
|GeneNetworkBuilder |? | | | |
|
||||
|grandR |? | | | |
|
||||
|InterCellar |? | | | |
|
||||
|LACE |? | | | |
|
||||
|gap |? | | | |
|
||||
|jsmodule |? | | | |
|
||||
|loon.shiny |? | | | |
|
||||
|MatrixQCvis |? | | | |
|
||||
|modchart |? | | | |
|
||||
|multilevelcoda |1.2.3 |1 | | |
|
||||
|omicsViewer |? | | | |
|
||||
|RQuantLib |0.4.21 |1 | | |
|
||||
|robmedExtra |0.1.1 |1 | | |
|
||||
|rstanarm |2.32.1 |1 | | |
|
||||
|Seurat |? | | | |
|
||||
|SensMap |0.7 |1 | | |
|
||||
|Seurat |5.1.0 |1 | |1 |
|
||||
|shinyTempSignal |0.0.8 |1 | | |
|
||||
|Signac |1.14.0 |1 | | |
|
||||
|statsr |0.3.0 |1 | | |
|
||||
|TestAnaAPP |1.1.2 |1 | | |
|
||||
|tidyvpc |1.5.2 |1 | | |
|
||||
|visR |? | | | |
|
||||
|
||||
## New problems (2)
|
||||
|
||||
|package |version |error |warning |note |
|
||||
|:-------|:-------|:-----|:-------|:------|
|
||||
|[HH](problems.md#hh)|3.1-52 | | |__+1__ |
|
||||
|[PopED](problems.md#poped)|0.7.0 | | |__+1__ |
|
||||
|
||||
|
||||
@@ -1,19 +1,39 @@
|
||||
## revdepcheck results
|
||||
|
||||
We checked 1201 reverse dependencies (1191 from CRAN + 10 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
We checked 1278 reverse dependencies (1277 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
|
||||
* We saw 0 new problems
|
||||
* We failed to check 8 packages
|
||||
* We saw 2 new problems
|
||||
* We failed to check 19 packages
|
||||
|
||||
Issues with CRAN packages are summarised below.
|
||||
|
||||
### New problems
|
||||
(This reports the first line of each new failure)
|
||||
|
||||
* HH
|
||||
checking installed package size ... NOTE
|
||||
|
||||
* PopED
|
||||
checking installed package size ... NOTE
|
||||
|
||||
### Failed to check
|
||||
|
||||
* ctsem (NA)
|
||||
* diveR (NA)
|
||||
* grandR (NA)
|
||||
* loon.shiny (NA)
|
||||
* multilevelcoda (NA)
|
||||
* RQuantLib (NA)
|
||||
* rstanarm (NA)
|
||||
* Seurat (NA)
|
||||
* animalEKF (NA)
|
||||
* AovBay (NA)
|
||||
* Certara.VPCResults (NA)
|
||||
* chipPCR (NA)
|
||||
* ctsem (NA)
|
||||
* dartR.sim (NA)
|
||||
* diveR (NA)
|
||||
* gap (NA)
|
||||
* jsmodule (NA)
|
||||
* loon.shiny (NA)
|
||||
* robmedExtra (NA)
|
||||
* rstanarm (NA)
|
||||
* SensMap (NA)
|
||||
* Seurat (NA)
|
||||
* shinyTempSignal (NA)
|
||||
* Signac (NA)
|
||||
* statsr (NA)
|
||||
* TestAnaAPP (NA)
|
||||
* tidyvpc (NA)
|
||||
|
||||
@@ -1 +1,49 @@
|
||||
*Wow, no problems at all. :)*
|
||||
# HH
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 3.1-52
|
||||
* GitHub: NA
|
||||
* Source code: https://github.com/cran/HH
|
||||
* Date/Publication: 2024-02-11 00:00:02 UTC
|
||||
* Number of recursive dependencies: 165
|
||||
|
||||
Run `revdepcheck::cloud_details(, "HH")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking installed package size ... NOTE
|
||||
```
|
||||
installed size is 5.1Mb
|
||||
sub-directories of 1Mb or more:
|
||||
R 1.5Mb
|
||||
help 1.5Mb
|
||||
```
|
||||
|
||||
# PopED
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 0.7.0
|
||||
* GitHub: https://github.com/andrewhooker/PopED
|
||||
* Source code: https://github.com/cran/PopED
|
||||
* Date/Publication: 2024-10-07 19:30:02 UTC
|
||||
* Number of recursive dependencies: 140
|
||||
|
||||
Run `revdepcheck::cloud_details(, "PopED")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking installed package size ... NOTE
|
||||
```
|
||||
installed size is 5.5Mb
|
||||
sub-directories of 1Mb or more:
|
||||
R 1.5Mb
|
||||
doc 1.4Mb
|
||||
test 1.1Mb
|
||||
```
|
||||
|
||||
|
||||
@@ -53,3 +53,11 @@ build({
|
||||
],
|
||||
outfile: outDir + "shiny.min.css",
|
||||
});
|
||||
build({
|
||||
...sassOpts,
|
||||
entryPoints: ["srcts/extras/busy-indicators/busy-indicators.scss"],
|
||||
outfile: outDir + "busy-indicators/busy-indicators.css",
|
||||
plugins: [sassPlugin()],
|
||||
bundle: false,
|
||||
metafile: true,
|
||||
});
|
||||
|
||||
163
srcts/extras/busy-indicators/busy-indicators.scss
Normal file
@@ -0,0 +1,163 @@
|
||||
:where([data-shiny-busy-spinners] .recalculating) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* This data atttribute is set by ui.busy_indicators.use() */
|
||||
[data-shiny-busy-spinners] {
|
||||
|
||||
.recalculating {
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
|
||||
/* ui.busy_indicators.spinner_options() */
|
||||
--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));
|
||||
--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));
|
||||
--_shiny-spinner-size: var(--shiny-spinner-size, 32px);
|
||||
--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);
|
||||
|
||||
background: var(--_shiny-spinner-color);
|
||||
width: var(--_shiny-spinner-size);
|
||||
height: var(--_shiny-spinner-size);
|
||||
inset: calc(50% - var(--_shiny-spinner-size) / 2);
|
||||
|
||||
mask-image: var(--_shiny-spinner-url);
|
||||
-webkit-mask-image: var(--_shiny-spinner-url);
|
||||
|
||||
opacity: 0;
|
||||
animation-delay: var(--_shiny-spinner-delay);
|
||||
animation-name: fade-in;
|
||||
animation-duration: 250ms;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/*
|
||||
shiny.css puts `opacity: 0.3` on .recalculating, which unfortunately applies to
|
||||
the spinner. Undo that, but still apply (smaller) opacity to immediate children
|
||||
that aren't recalculating.
|
||||
*/
|
||||
&:has(> *), &:empty {
|
||||
opacity: 1;
|
||||
}
|
||||
> *:not(.recalculating) {
|
||||
opacity: var(--_shiny-fade-opacity);
|
||||
transition: opacity 250ms ease var(--shiny-spinner-delay, 1s);
|
||||
}
|
||||
|
||||
/*
|
||||
Disable spinner on uiOutput() mainly because (for other reasons) it has
|
||||
`display:contents`, which breaks the ::after positioning.
|
||||
Note that, even if we could position it, we'd probably want to disable it
|
||||
if it has recalculating children.
|
||||
*/
|
||||
&.shiny-html-output::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles for the page-level pulse banner */
|
||||
@mixin shiny-page-busy {
|
||||
/* ui.busy_indicators.pulse_options() */
|
||||
--_shiny-pulse-background: var(
|
||||
--shiny-pulse-background,
|
||||
linear-gradient(
|
||||
120deg,
|
||||
transparent,
|
||||
var(--bs-indigo, #4b00c1),
|
||||
var(--bs-purple, #74149c),
|
||||
var(--bs-pink, #bf007f),
|
||||
transparent
|
||||
)
|
||||
);
|
||||
--_shiny-pulse-height: var(--shiny-pulse-height, 3px);
|
||||
--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);
|
||||
|
||||
/* Color, sizing, & positioning */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: var(--_shiny-pulse-height);
|
||||
background: var(--_shiny-pulse-background);
|
||||
z-index: 9999;
|
||||
|
||||
/* Animation */
|
||||
animation-name: busy-page-pulse;
|
||||
animation-duration: var(--_shiny-pulse-speed);
|
||||
animation-direction: alternate;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
|
||||
content: ""; /* Used in a ::after context */
|
||||
}
|
||||
|
||||
/*
|
||||
In spinners+pulse mode (the recommended default), show a page-level banner if the
|
||||
page is busy, but there are no recalculating elements.
|
||||
*/
|
||||
[data-shiny-busy-spinners][data-shiny-busy-pulse] {
|
||||
&.shiny-busy::after {
|
||||
@include shiny-page-busy;
|
||||
}
|
||||
// Hide the pulse if there are spinners on the page
|
||||
// (Note: UI outputs don't get spinners)
|
||||
&.shiny-busy:has(.recalculating:not(.shiny-html-output))::after {
|
||||
display: none;
|
||||
}
|
||||
&.shiny-busy:has(#shiny-disconnected-overlay)::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* In pulse _only_ mode, show a page-level banner whenever shiny is busy. */
|
||||
[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]) {
|
||||
&.shiny-busy::after {
|
||||
@include shiny-page-busy;
|
||||
}
|
||||
&.shiny-busy:has(#shiny-disconnected-overlay)::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keyframes for the fading spinner */
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keyframes for the pulsing banner */
|
||||
@keyframes busy-page-pulse {
|
||||
0% {
|
||||
left: -14%;
|
||||
right: 97%;
|
||||
}
|
||||
|
||||
45% {
|
||||
left: 0%;
|
||||
right: 14%;
|
||||
}
|
||||
|
||||
55% {
|
||||
left: 14%;
|
||||
right: 0%;
|
||||
}
|
||||
|
||||
to {
|
||||
left: 97%;
|
||||
right: -14%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Effectively disable the spinner when it's wrapped in shinycssloader::withSpinner()
|
||||
// since that's a sign our spinner isn't needed.
|
||||
// The reason this sets size to 0px instead of display:none is so, if someone
|
||||
// really wants to show the spinner, they can override this with a custom size.
|
||||
.shiny-spinner-output-container {
|
||||
--shiny-spinner-size: 0px;
|
||||
}
|
||||
@@ -2,19 +2,12 @@
|
||||
// Project: Shiny <https://shiny.rstudio.com/>
|
||||
// Definitions by: RStudio <https://www.rstudio.com/>
|
||||
|
||||
import type { Shiny as RStudioShiny } from "../src/shiny/index";
|
||||
import type { ShinyClass } from "../src/shiny/index";
|
||||
|
||||
declare global {
|
||||
// Tell Shiny variable globally exists
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const Shiny: RStudioShiny;
|
||||
|
||||
// Tell window.Shiny exists
|
||||
interface Window {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
Shiny: RStudioShiny;
|
||||
Shiny: ShinyClass;
|
||||
}
|
||||
|
||||
// Make `Shiny` a globally available type definition. (No need to import the type)
|
||||
type Shiny = RStudioShiny;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { hasDefinedProperty } from "../../utils";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
type CheckedHTMLElement = HTMLInputElement;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { $escape, updateLabel, hasDefinedProperty } from "../../utils";
|
||||
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
|
||||
import type { CheckedHTMLElement } from "./checkbox";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
type CheckboxGroupHTMLElement = CheckedHTMLElement;
|
||||
type ValueLabelObject = {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import {
|
||||
formatDateUTC,
|
||||
updateLabel,
|
||||
$escape,
|
||||
parseDate,
|
||||
formatDateUTC,
|
||||
hasDefinedProperty,
|
||||
parseDate,
|
||||
updateLabel,
|
||||
} from "../../utils";
|
||||
import type { NotUndefined } from "../../utils/extraTypes";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
declare global {
|
||||
interface JQuery {
|
||||
@@ -40,19 +40,15 @@ class DateInputBindingBase extends InputBinding {
|
||||
el;
|
||||
}
|
||||
subscribe(el: HTMLElement, callback: (x: boolean) => void): void {
|
||||
$(el).on(
|
||||
"keyup.dateInputBinding input.dateInputBinding",
|
||||
// event: Event
|
||||
function () {
|
||||
// Use normal debouncing policy when typing
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
// Don't update when in the middle of typing; listening on keyup or input
|
||||
// tends to send spurious values to the server, based on unpredictable
|
||||
// browser-dependant interpretation of partially-typed date strings.
|
||||
$(el).on(
|
||||
"changeDate.dateInputBinding change.dateInputBinding",
|
||||
// event: Event
|
||||
function () {
|
||||
// Send immediately when clicked
|
||||
// Or if typing, when enter pressed or focus lost
|
||||
callback(false);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -161,19 +161,15 @@ class DateRangeInputBinding extends DateInputBindingBase {
|
||||
this._setMax($endinput[0], $endinput.data("max-date"));
|
||||
}
|
||||
subscribe(el: HTMLElement, callback: (x: boolean) => void): void {
|
||||
$(el).on(
|
||||
"keyup.dateRangeInputBinding input.dateRangeInputBinding",
|
||||
// event: Event
|
||||
function () {
|
||||
// Use normal debouncing policy when typing
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
// Don't update when in the middle of typing; listening on keyup or input
|
||||
// tends to send spurious values to the server, based on unpredictable
|
||||
// browser-dependant interpretation of partially-typed date strings.
|
||||
$(el).on(
|
||||
"changeDate.dateRangeInputBinding change.dateRangeInputBinding",
|
||||
// event: Event
|
||||
function () {
|
||||
// Send immediately when clicked
|
||||
// Or if typing, when enter pressed or focus lost
|
||||
callback(false);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { FileUploader } from "../../file/fileProcessor";
|
||||
import { shinyShinyApp } from "../../shiny/initedMethods";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
const zoneActive = "shiny-file-input-active";
|
||||
const zoneOver = "shiny-file-input-over";
|
||||
|
||||
@@ -2,27 +2,26 @@ import { BindingRegistry } from "../registry";
|
||||
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
import { ActionButtonInputBinding } from "./actionbutton";
|
||||
import { CheckboxInputBinding } from "./checkbox";
|
||||
import { CheckboxGroupInputBinding } from "./checkboxgroup";
|
||||
import { DateInputBinding } from "./date";
|
||||
import { DateRangeInputBinding } from "./daterange";
|
||||
import { FileInputBinding } from "./fileinput";
|
||||
import { NumberInputBinding } from "./number";
|
||||
import { PasswordInputBinding } from "./password";
|
||||
import { RadioInputBinding } from "./radio";
|
||||
import { SelectInputBinding } from "./selectInput";
|
||||
import { SliderInputBinding } from "./slider";
|
||||
import { BootstrapTabInputBinding } from "./tabinput";
|
||||
import { TextInputBinding } from "./text";
|
||||
import { TextareaInputBinding } from "./textarea";
|
||||
import { RadioInputBinding } from "./radio";
|
||||
import { DateInputBinding } from "./date";
|
||||
import { SliderInputBinding } from "./slider";
|
||||
import { DateRangeInputBinding } from "./daterange";
|
||||
import { SelectInputBinding } from "./selectInput";
|
||||
import { ActionButtonInputBinding } from "./actionbutton";
|
||||
import { BootstrapTabInputBinding } from "./tabinput";
|
||||
import { FileInputBinding } from "./fileinput";
|
||||
|
||||
// TODO-barret make this an init method
|
||||
type InitInputBindings = {
|
||||
function initInputBindings(): {
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
fileInputBinding: FileInputBinding;
|
||||
};
|
||||
function initInputBindings(): InitInputBindings {
|
||||
} {
|
||||
const inputBindings = new BindingRegistry<InputBinding>();
|
||||
|
||||
inputBindings.register(new TextInputBinding(), "shiny.textInput");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
type RadioHTMLElement = HTMLInputElement;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
|
||||
import { indirectEval } from "../../utils/eval";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
type SelectHTMLElement = HTMLSelectElement & { nonempty: boolean };
|
||||
|
||||
@@ -142,19 +142,19 @@ class SelectInputBinding extends InputBinding {
|
||||
};
|
||||
};
|
||||
|
||||
// Calling selectize.clear() first works around https://github.com/selectize/selectize.js/issues/2146
|
||||
// As of selectize.js >= v0.13.1, .clearOptions() clears the selection,
|
||||
// but does NOT remove the previously-selected options. So unless we call
|
||||
// .clear() first, the current selection(s) will remain as (deselected)
|
||||
// options. See #3966 #4142
|
||||
selectize.clear();
|
||||
selectize.clearOptions();
|
||||
// If a new `selected` value is provided, also clear the current selection (otherwise it gets added as an option).
|
||||
// Note: although the selectize docs suggest otherwise, as of selectize.js >v0.15.2,
|
||||
// .clearOptions() no longer implicitly .clear()s (see #3967)
|
||||
if (hasDefinedProperty(data, "value")) {
|
||||
selectize.clear();
|
||||
}
|
||||
let loaded = false;
|
||||
|
||||
selectize.settings.load = function (query: string, callback: CallbackFn) {
|
||||
const settings = selectize.settings;
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable-next-line @typescript-eslint/no-floating-promises */
|
||||
$.ajax({
|
||||
url: data.url,
|
||||
data: {
|
||||
|
||||
@@ -5,10 +5,10 @@ import type {
|
||||
import $ from "jquery";
|
||||
// import { NameValueHTMLElement } from ".";
|
||||
import {
|
||||
formatDateUTC,
|
||||
updateLabel,
|
||||
$escape,
|
||||
formatDateUTC,
|
||||
hasDefinedProperty,
|
||||
updateLabel,
|
||||
} from "../../utils";
|
||||
|
||||
import type { TextHTMLElement } from "./text";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { hasDefinedProperty, isBS3 } from "../../utils";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
type TabInputReceiveMessageData = { value?: string };
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { $escape, updateLabel, hasDefinedProperty } from "../../utils";
|
||||
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
|
||||
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
@@ -122,5 +122,4 @@ class TextInputBinding extends TextInputBindingBase {
|
||||
}
|
||||
|
||||
export { TextInputBinding, TextInputBindingBase };
|
||||
|
||||
export type { TextHTMLElement, TextReceiveMessageData };
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
import { shinyUnbindAll } from "../../shiny/initedMethods";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
import { debounce } from "../../time";
|
||||
import { escapeHTML } from "../../utils";
|
||||
import { indirectEval } from "../../utils/eval";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
|
||||
class DatatableOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
|
||||
@@ -7,7 +7,17 @@ class DownloadLinkOutputBinding extends OutputBinding {
|
||||
return $(scope).find("a.shiny-download-link");
|
||||
}
|
||||
renderValue(el: HTMLElement, data: string): void {
|
||||
$(el).attr("href", data);
|
||||
el.setAttribute("href", data);
|
||||
el.classList.remove("disabled");
|
||||
el.removeAttribute("aria-disabled");
|
||||
el.removeAttribute("tabindex");
|
||||
}
|
||||
// Progress shouldn't be shown on the download button
|
||||
// (progress will be shown as a page level pulse instead)
|
||||
showProgress(el: HTMLElement, show: boolean): void {
|
||||
return;
|
||||
el;
|
||||
show;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
import { shinyUnbindAll } from "../../shiny/initedMethods";
|
||||
import { renderContentAsync } from "../../shiny/render";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
|
||||
class HtmlOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import $ from "jquery";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
import {
|
||||
createBrushHandler,
|
||||
createClickHandler,
|
||||
@@ -8,16 +7,17 @@ import {
|
||||
disableDrag,
|
||||
initCoordmap,
|
||||
} from "../../imageutils";
|
||||
import type { CoordmapInit } from "../../imageutils/initCoordmap";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
import {
|
||||
strToBool,
|
||||
getComputedLinkColor,
|
||||
getStyle,
|
||||
hasOwnProperty,
|
||||
strToBool,
|
||||
} from "../../utils";
|
||||
import { isIE, IEVersion } from "../../utils/browser";
|
||||
import type { CoordmapInit } from "../../imageutils/initCoordmap";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
import { IEVersion, isIE } from "../../utils/browser";
|
||||
import { ifUndefined } from "../../utils/object";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
|
||||
class ImageOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { TextOutputBinding } from "./text";
|
||||
import { BindingRegistry } from "../registry";
|
||||
import { DownloadLinkOutputBinding } from "./downloadlink";
|
||||
import { DatatableOutputBinding } from "./datatable";
|
||||
import { DownloadLinkOutputBinding } from "./downloadlink";
|
||||
import { HtmlOutputBinding } from "./html";
|
||||
import { imageOutputBinding } from "./image";
|
||||
import { TextOutputBinding } from "./text";
|
||||
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
import { asArray } from "../../utils";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
import { asArray } from "../../utils";
|
||||
|
||||
class OutputBinding {
|
||||
name!: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { Shiny } from "..";
|
||||
import { ShinyClientError } from "../shiny/error";
|
||||
|
||||
const buttonStyles = css`
|
||||
@@ -306,6 +307,7 @@ export class ShinyErrorMessage extends LitElement {
|
||||
|
||||
.error-message {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.decoration-container {
|
||||
@@ -488,6 +490,52 @@ export class ShinyErrorMessage extends LitElement {
|
||||
|
||||
customElements.define("shiny-error-message", ShinyErrorMessage);
|
||||
|
||||
export type ShinyClientMessage = {
|
||||
message: string;
|
||||
headline?: string;
|
||||
status?: "error" | "info" | "warning";
|
||||
};
|
||||
|
||||
function showShinyClientMessage({
|
||||
headline = "",
|
||||
message,
|
||||
status = "warning",
|
||||
}: ShinyClientMessage): void {
|
||||
const consoleMessage = `[shiny] ${headline}${
|
||||
headline ? " - " : ""
|
||||
}${message}`;
|
||||
|
||||
switch (status) {
|
||||
case "error":
|
||||
console.error(consoleMessage);
|
||||
break;
|
||||
case "warning":
|
||||
console.warn(consoleMessage);
|
||||
break;
|
||||
default:
|
||||
console.log(consoleMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Shiny.inDevMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if an Error Console Container element already exists. If it
|
||||
// doesn't we need to add it before putting an error on the screen
|
||||
let errorConsoleContainer = document.querySelector("shiny-error-console");
|
||||
if (!errorConsoleContainer) {
|
||||
errorConsoleContainer = document.createElement("shiny-error-console");
|
||||
document.body.appendChild(errorConsoleContainer);
|
||||
}
|
||||
|
||||
const errorConsole = document.createElement("shiny-error-message");
|
||||
errorConsole.setAttribute("headline", headline);
|
||||
errorConsole.setAttribute("message", message);
|
||||
|
||||
errorConsoleContainer.appendChild(errorConsole);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to show an error message to user in shiny-error-message web
|
||||
* component. Only shows the error if we're in development mode.
|
||||
@@ -496,11 +544,6 @@ customElements.define("shiny-error-message", ShinyErrorMessage);
|
||||
* object.
|
||||
*/
|
||||
export function showErrorInClientConsole(e: unknown): void {
|
||||
if (!Shiny.inDevMode()) {
|
||||
// If we're in production, don't show the error to the user
|
||||
return;
|
||||
}
|
||||
|
||||
let errorMsg: string | null = null;
|
||||
let headline = "Error on client while running Shiny app";
|
||||
|
||||
@@ -515,17 +558,24 @@ export function showErrorInClientConsole(e: unknown): void {
|
||||
errorMsg = "Unknown error";
|
||||
}
|
||||
|
||||
// Check to see if an Error Console Container element already exists. If it
|
||||
// doesn't we need to add it before putting an error on the screen
|
||||
let errorConsoleContainer = document.querySelector("shiny-error-console");
|
||||
if (!errorConsoleContainer) {
|
||||
errorConsoleContainer = document.createElement("shiny-error-console");
|
||||
document.body.appendChild(errorConsoleContainer);
|
||||
}
|
||||
|
||||
const errorConsole = document.createElement("shiny-error-message");
|
||||
errorConsole.setAttribute("headline", headline || "");
|
||||
errorConsole.setAttribute("message", errorMsg);
|
||||
|
||||
errorConsoleContainer.appendChild(errorConsole);
|
||||
showShinyClientMessage({ headline, message: errorMsg, status: "error" });
|
||||
}
|
||||
|
||||
export class ShinyClientMessageEvent extends CustomEvent<ShinyClientMessage> {
|
||||
constructor(detail: ShinyClientMessage) {
|
||||
super("shiny:client-message", { detail, bubbles: true, cancelable: true });
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("shiny:client-message", (ev: Event) => {
|
||||
if (!(ev instanceof CustomEvent)) {
|
||||
throw new Error("[shiny] shiny:client-message expected a CustomEvent");
|
||||
}
|
||||
const { headline, message, status } = ev.detail;
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
"[shiny] shiny:client-message expected a `message` property in `event.detail`."
|
||||
);
|
||||
}
|
||||
showShinyClientMessage({ headline, message, status });
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import $ from "jquery";
|
||||
import { triggerFileInputChanged } from "../events/inputChanged";
|
||||
import { $escape } from "../utils";
|
||||
import type { ShinyApp } from "../shiny/shinyapp";
|
||||
import { getFileInputBinding } from "../shiny/initedMethods";
|
||||
import type { ShinyApp } from "../shiny/shinyapp";
|
||||
import { $escape } from "../utils";
|
||||
|
||||
type JobId = string;
|
||||
type UploadUrl = string;
|
||||
@@ -180,7 +180,7 @@ class FileUploader extends FileProcessor {
|
||||
onFile(file: File, cont: () => void): void {
|
||||
this.onProgress(file, 0);
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable-next-line @typescript-eslint/no-floating-promises */
|
||||
$.ajax(this.uploadUrl, {
|
||||
type: "POST",
|
||||
cache: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { equal, isnan, mapValues, roundSignif } from "../utils";
|
||||
import type { Coordmap } from "./initCoordmap";
|
||||
import { findOrigin } from "./initCoordmap";
|
||||
import { equal, isnan, mapValues, roundSignif } from "../utils";
|
||||
import type { Panel } from "./initPanelScales";
|
||||
|
||||
import type { Offset } from "./findbox";
|
||||
@@ -656,5 +656,4 @@ function createBrush(
|
||||
}
|
||||
|
||||
export { createBrush };
|
||||
|
||||
export type { Bounds, BrushOpts, BoundsCss };
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import $ from "jquery";
|
||||
import { imageOutputBinding } from "../bindings/output/image";
|
||||
import type { InputRatePolicy } from "../inputPolicies";
|
||||
import { shinySetInputValue } from "../shiny/initedMethods";
|
||||
import { Debouncer, Throttler } from "../time";
|
||||
import type { Bounds, BoundsCss, BrushOpts } from "./createBrush";
|
||||
import { createBrush } from "./createBrush";
|
||||
import type { BoundsCss, Bounds, BrushOpts } from "./createBrush";
|
||||
import type { Offset } from "./findbox";
|
||||
import type { Coordmap } from "./initCoordmap";
|
||||
import type { Panel } from "./initPanelScales";
|
||||
import type { InputRatePolicy } from "../inputPolicies";
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Handler creators for click, hover, brush.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createBrush } from "./createBrush";
|
||||
import { createClickInfo } from "./createClickInfo";
|
||||
import {
|
||||
createBrushHandler,
|
||||
createClickHandler,
|
||||
createHoverHandler,
|
||||
createBrushHandler,
|
||||
} from "./createHandlers";
|
||||
import { disableDrag } from "./disableDrag";
|
||||
import { findBox } from "./findbox";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import $ from "jquery";
|
||||
import { shinySetInputValue } from "../shiny/initedMethods";
|
||||
import { mapValues } from "../utils";
|
||||
import type { Offset } from "./findbox";
|
||||
import type { Bounds } from "./createBrush";
|
||||
import type { Offset } from "./findbox";
|
||||
import type { Panel, PanelInit } from "./initPanelScales";
|
||||
import { initPanelScales } from "./initPanelScales";
|
||||
|
||||
@@ -162,8 +162,8 @@ function initCoordmap(
|
||||
const bounds = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: img.clientWidth - 1,
|
||||
bottom: img.clientHeight - 1,
|
||||
right: img.naturalWidth - 1,
|
||||
bottom: img.naturalHeight - 1,
|
||||
};
|
||||
|
||||
coordmap_.panels[0] = {
|
||||
@@ -290,11 +290,10 @@ function initCoordmap(
|
||||
|
||||
const matches = []; // Panels that match
|
||||
const dists = []; // Distance of offset to each matching panel
|
||||
let b;
|
||||
let i;
|
||||
|
||||
for (i = 0; i < coordmap.panels.length; i++) {
|
||||
b = coordmap.panels[i].range;
|
||||
const b = coordmap.panels[i].range;
|
||||
|
||||
if (
|
||||
x <= b.right + expandImg.x &&
|
||||
@@ -413,5 +412,5 @@ function initCoordmap(
|
||||
return coordmap;
|
||||
}
|
||||
|
||||
export { findOrigin, initCoordmap };
|
||||
export type { Coordmap, CoordmapInit };
|
||||
export { initCoordmap, findOrigin };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Map a value x from a domain to a range. If clip is true, clip it to the
|
||||
|
||||
import type { Offset } from "./findbox";
|
||||
import { mapValues } from "../utils";
|
||||
import type { Bounds } from "./createBrush";
|
||||
import type { Offset } from "./findbox";
|
||||
|
||||
// range.
|
||||
function mapLinear(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { init } from "./initialize";
|
||||
export { Shiny, type ShinyClass } from "./initialize";
|
||||
|
||||
init();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import { isIE, setIsQt, setIsIE, setIEVersion } from "../utils/browser";
|
||||
import { isIE, setIEVersion, setIsIE, setIsQt } from "../utils/browser";
|
||||
import { userAgent } from "../utils/userAgent";
|
||||
|
||||
function getIEVersion() {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { determineBrowserInfo } from "./browser";
|
||||
import { disableFormSubmission } from "./disableForm";
|
||||
import { trackHistory } from "./history";
|
||||
import { determineBrowserInfo } from "./browser";
|
||||
|
||||
import { windowShiny } from "../window/libraries";
|
||||
import { setShiny } from "../shiny";
|
||||
import { ShinyClass } from "../shiny";
|
||||
import { setUserAgent } from "../utils/userAgent";
|
||||
import { windowUserAgent } from "../window/userAgent";
|
||||
|
||||
import { initReactlog } from "../shiny/reactlog";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
let Shiny: ShinyClass;
|
||||
|
||||
function init(): void {
|
||||
setShiny(windowShiny());
|
||||
if (window.Shiny) {
|
||||
throw new Error("Trying to create window.Shiny, but it already exists!");
|
||||
}
|
||||
Shiny = window.Shiny = new ShinyClass();
|
||||
setUserAgent(windowUserAgent()); // before determineBrowserInfo()
|
||||
|
||||
determineBrowserInfo();
|
||||
@@ -21,4 +26,4 @@ function init(): void {
|
||||
initReactlog();
|
||||
}
|
||||
|
||||
export { init };
|
||||
export { init, Shiny, type ShinyClass };
|
||||
|
||||