mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 07:58:11 -05:00
Compare commits
7 Commits
bindAfterR
...
set_app_st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2be21861f | ||
|
|
fce5216000 | ||
|
|
e418f3540a | ||
|
|
977383de7f | ||
|
|
62bb21d5b6 | ||
|
|
4f85268d44 | ||
|
|
611e517bb8 |
2
NEWS.md
2
NEWS.md
@@ -6,7 +6,7 @@
|
||||
|
||||
### New features and improvements
|
||||
|
||||
* Closed #789: `<script>` loaded from dynamic UI are no longer loaded using synchronous `XMLHttpRequest` (via jQuery). (#3666)
|
||||
* Closed #789: Dynamic UI is now rendered asynchronously, thanks in part to the newly exported `Shiny.renderDependenciesAsync()`, `Shiny.renderHtmlAsync()`, and `Shiny.renderContentAsync()`. Importantly, this means `<script>` tags are now loaded asynchronously (the old way used `XMLHttpRequest`, which is synchronous). In addition, `Shiny` now manages a queue of async tasks (exposed via `Shiny.shinyapp.taskQueue`) so that order of execution is preserved. (#3666)
|
||||
|
||||
* For `reactiveValues()` objects, whenever the `$names()` or `$values()` methods are called, the keys are now returned in the order that they were inserted. (#3774)
|
||||
|
||||
|
||||
@@ -7,6 +7,17 @@ NULL
|
||||
|
||||
.globals$appState <- NULL
|
||||
|
||||
#' Check whether a Shiny application is running
|
||||
#'
|
||||
#' This function tests whether a Shiny application is currently running.
|
||||
#'
|
||||
#' @return `TRUE` if a Shiny application is currently running. Otherwise,
|
||||
#' `FALSE`.
|
||||
#' @export
|
||||
isRunning <- function() {
|
||||
!is.null(getCurrentAppState())
|
||||
}
|
||||
|
||||
initCurrentAppState <- function(appobj) {
|
||||
if (!is.null(.globals$appState)) {
|
||||
stop("Can't initialize current app state when another is currently active.")
|
||||
@@ -21,6 +32,14 @@ getCurrentAppState <- function() {
|
||||
.globals$appState
|
||||
}
|
||||
|
||||
getCurrentAppStateOptions <- function() {
|
||||
.globals$appState$options
|
||||
}
|
||||
setCurrentAppStateOptions <- function(options) {
|
||||
stopifnot(isRunning())
|
||||
.globals$appState$options <- options
|
||||
}
|
||||
|
||||
clearCurrentAppState <- function() {
|
||||
.globals$appState <- NULL
|
||||
}
|
||||
|
||||
@@ -1200,19 +1200,25 @@ uiOutput <- htmlOutput
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' ui <- fluidPage(
|
||||
#' p("Choose a dataset to download."),
|
||||
#' selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
|
||||
#' downloadButton("downloadData", "Download")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' # Our dataset
|
||||
#' data <- mtcars
|
||||
#' # The requested dataset
|
||||
#' data <- reactive({
|
||||
#' get(input$dataset)
|
||||
#' })
|
||||
#'
|
||||
#' output$downloadData <- downloadHandler(
|
||||
#' filename = function() {
|
||||
#' paste("data-", Sys.Date(), ".csv", sep="")
|
||||
#' # Use the selected dataset as the suggested file name
|
||||
#' paste0(input$dataset, ".csv")
|
||||
#' },
|
||||
#' content = function(file) {
|
||||
#' write.csv(data, file)
|
||||
#' # Write the dataset to the `file` that will be downloaded
|
||||
#' write.csv(data(), file)
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
|
||||
@@ -274,7 +274,7 @@ MockShinySession <- R6Class(
|
||||
self$token <- createUniqueId(16)
|
||||
|
||||
# Copy app-level options
|
||||
self$options <- getCurrentAppState()$options
|
||||
self$options <- getCurrentAppStateOptions()
|
||||
|
||||
self$cache <- cachem::cache_mem()
|
||||
self$appcache <- cachem::cache_mem()
|
||||
|
||||
10
R/server.R
10
R/server.R
@@ -495,16 +495,6 @@ serviceApp <- function() {
|
||||
|
||||
.shinyServerMinVersion <- '0.3.4'
|
||||
|
||||
#' Check whether a Shiny application is running
|
||||
#'
|
||||
#' This function tests whether a Shiny application is currently running.
|
||||
#'
|
||||
#' @return `TRUE` if a Shiny application is currently running. Otherwise,
|
||||
#' `FALSE`.
|
||||
#' @export
|
||||
isRunning <- function() {
|
||||
!is.null(getCurrentAppState())
|
||||
}
|
||||
|
||||
|
||||
# Returns TRUE if we're running in Shiny Server or other hosting environment,
|
||||
|
||||
@@ -19,10 +19,10 @@ getShinyOption <- function(name, default = NULL) {
|
||||
}
|
||||
|
||||
# Check if there's a current app
|
||||
app_state <- getCurrentAppState()
|
||||
if (!is.null(app_state)) {
|
||||
if (name %in% names(app_state$options)) {
|
||||
return(app_state$options[[name]])
|
||||
if (isRunning()) {
|
||||
app_state_options <- getCurrentAppStateOptions()
|
||||
if (name %in% names(app_state_options)) {
|
||||
return(app_state_options[[name]])
|
||||
} else {
|
||||
return(default)
|
||||
}
|
||||
@@ -199,11 +199,12 @@ shinyOptions <- function(...) {
|
||||
|
||||
# If not in a session, but we have a currently running app, modify options
|
||||
# at the app level.
|
||||
app_state <- getCurrentAppState()
|
||||
if (!is.null(app_state)) {
|
||||
if (isRunning()) {
|
||||
# Modify app-level options
|
||||
app_state$options <- dropNulls(mergeVectors(app_state$options, newOpts))
|
||||
return(invisible(app_state$options))
|
||||
setCurrentAppStateOptions(
|
||||
dropNulls(mergeVectors(getCurrentAppStateOptions(), newOpts))
|
||||
)
|
||||
return(invisible(getCurrentAppStateOptions()))
|
||||
}
|
||||
|
||||
# If no currently running app, modify global options and return them.
|
||||
@@ -218,9 +219,8 @@ shinyOptions <- function(...) {
|
||||
return(session$options)
|
||||
}
|
||||
|
||||
app_state <- getCurrentAppState()
|
||||
if (!is.null(app_state)) {
|
||||
return(app_state$options)
|
||||
if (isRunning()) {
|
||||
return(getCurrentAppStateOptions())
|
||||
}
|
||||
|
||||
return(.globals$options)
|
||||
|
||||
@@ -738,7 +738,7 @@ ShinySession <- R6Class(
|
||||
private$.outputOptions <- list()
|
||||
|
||||
# Copy app-level options
|
||||
self$options <- getCurrentAppState()$options
|
||||
self$options <- getCurrentAppStateOptions()
|
||||
|
||||
self$cache <- cachem::cache_mem(max_size = 200 * 1024^2)
|
||||
|
||||
|
||||
@@ -15583,7 +15583,7 @@
|
||||
if (opts.priority === "event") {
|
||||
this._sendNow();
|
||||
} else if (!this.sendIsEnqueued) {
|
||||
this.shinyapp.actionQueue.enqueue(function() {
|
||||
this.shinyapp.taskQueue.enqueue(function() {
|
||||
_this.sendIsEnqueued = false;
|
||||
_this._sendNow();
|
||||
});
|
||||
@@ -17666,7 +17666,7 @@
|
||||
function ShinyApp2() {
|
||||
_classCallCheck36(this, ShinyApp2);
|
||||
_defineProperty18(this, "$socket", null);
|
||||
_defineProperty18(this, "actionQueue", new AsyncQueue());
|
||||
_defineProperty18(this, "taskQueue", new AsyncQueue());
|
||||
_defineProperty18(this, "config", null);
|
||||
_defineProperty18(this, "$inputValues", {});
|
||||
_defineProperty18(this, "$initialInput", null);
|
||||
@@ -17875,7 +17875,7 @@
|
||||
_this.startActionQueueLoop();
|
||||
};
|
||||
socket.onmessage = function(e) {
|
||||
_this.actionQueue.enqueue(/* @__PURE__ */ _asyncToGenerator8(/* @__PURE__ */ _regeneratorRuntime8().mark(function _callee2() {
|
||||
_this.taskQueue.enqueue(/* @__PURE__ */ _asyncToGenerator8(/* @__PURE__ */ _regeneratorRuntime8().mark(function _callee2() {
|
||||
return _regeneratorRuntime8().wrap(function _callee2$(_context2) {
|
||||
while (1)
|
||||
switch (_context2.prev = _context2.next) {
|
||||
@@ -17918,7 +17918,7 @@
|
||||
break;
|
||||
}
|
||||
_context3.next = 3;
|
||||
return this.actionQueue.dequeue();
|
||||
return this.taskQueue.dequeue();
|
||||
case 3:
|
||||
action = _context3.sent;
|
||||
_context3.prev = 4;
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
inst/www/shared/shiny.min.js
vendored
2
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -36,19 +36,25 @@ function.
|
||||
\examples{
|
||||
\dontrun{
|
||||
ui <- fluidPage(
|
||||
p("Choose a dataset to download."),
|
||||
selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
|
||||
downloadButton("downloadData", "Download")
|
||||
)
|
||||
|
||||
server <- function(input, output) {
|
||||
# Our dataset
|
||||
data <- mtcars
|
||||
# The requested dataset
|
||||
data <- reactive({
|
||||
get(input$dataset)
|
||||
})
|
||||
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() {
|
||||
paste("data-", Sys.Date(), ".csv", sep="")
|
||||
# Use the selected dataset as the suggested file name
|
||||
paste0(input$dataset, ".csv")
|
||||
},
|
||||
content = function(file) {
|
||||
write.csv(data, file)
|
||||
# Write the dataset to the `file` that will be downloaded
|
||||
write.csv(data(), file)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/server.R
|
||||
% Please edit documentation in R/app-state.R
|
||||
\name{isRunning}
|
||||
\alias{isRunning}
|
||||
\title{Check whether a Shiny application is running}
|
||||
|
||||
@@ -22,7 +22,7 @@ class InputBatchSender implements InputPolicy {
|
||||
if (opts.priority === "event") {
|
||||
this._sendNow();
|
||||
} else if (!this.sendIsEnqueued) {
|
||||
this.shinyapp.actionQueue.enqueue(() => {
|
||||
this.shinyapp.taskQueue.enqueue(() => {
|
||||
this.sendIsEnqueued = false;
|
||||
this._sendNow();
|
||||
});
|
||||
|
||||
@@ -114,7 +114,7 @@ class ShinyApp {
|
||||
// without overlapping. This is used for handling incoming messages from the
|
||||
// server and scheduling outgoing messages to the server, and can be used for
|
||||
// other things tasks as well.
|
||||
actionQueue = new AsyncQueue<() => Promise<void> | void>();
|
||||
taskQueue = new AsyncQueue<() => Promise<void> | void>();
|
||||
|
||||
config: {
|
||||
workerId: string;
|
||||
@@ -240,7 +240,7 @@ class ShinyApp {
|
||||
this.startActionQueueLoop();
|
||||
};
|
||||
socket.onmessage = (e) => {
|
||||
this.actionQueue.enqueue(async () => await this.dispatchMessage(e.data));
|
||||
this.taskQueue.enqueue(async () => await this.dispatchMessage(e.data));
|
||||
};
|
||||
// Called when a successfully-opened websocket is closed, or when an
|
||||
// attempt to open a connection fails.
|
||||
@@ -266,7 +266,7 @@ class ShinyApp {
|
||||
async startActionQueueLoop(): Promise<void> {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const action = await this.actionQueue.dequeue();
|
||||
const action = await this.taskQueue.dequeue();
|
||||
|
||||
try {
|
||||
await action();
|
||||
|
||||
2
srcts/types/src/shiny/shinyapp.d.ts
vendored
2
srcts/types/src/shiny/shinyapp.d.ts
vendored
@@ -20,7 +20,7 @@ type MessageValue = Parameters<WebSocket["send"]>[0];
|
||||
declare function addCustomMessageHandler(type: string, handler: Handler): void;
|
||||
declare class ShinyApp {
|
||||
$socket: ShinyWebSocket | null;
|
||||
actionQueue: AsyncQueue<() => Promise<void> | void>;
|
||||
taskQueue: AsyncQueue<() => Promise<void> | void>;
|
||||
config: {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
|
||||
Reference in New Issue
Block a user