Compare commits

...

1 Commits

Author SHA1 Message Date
Joe Cheng
44a98b34cf Revert "Reconnect" 2016-03-29 09:53:28 -07:00
11 changed files with 31 additions and 426 deletions

View File

@@ -3,10 +3,6 @@
#' These functions show and remove notifications in a Shiny application.
#'
#' @param ui Content of message.
#' @param action Message content that represents an action. For example, this
#' could be a link that the user can click on. This is separate from \code{ui}
#' so customized layouts can handle the main notification content separately
#' from action content.
#' @param duration Number of seconds to display the message before it
#' disappears. Use \code{NULL} to make the message not automatically
#' disappear.
@@ -32,9 +28,7 @@
#' ),
#' server = function(input, output) {
#' observeEvent(input$show, {
#' showNotification("Message text",
#' action = a(href = "javascript:location.reload();", "Reload page")
#' )
#' showNotification("Message text")
#' })
#' }
#' )
@@ -67,9 +61,8 @@
#' )
#' }
#' @export
showNotification <- function(ui, action = NULL, duration = 5,
closeButton = TRUE, id = NULL,
type = c("default", "message", "warning", "error"),
showNotification <- function(ui, duration = 5, closeButton = TRUE,
id = NULL, type = c("default", "message", "warning", "error"),
session = getDefaultReactiveDomain())
{
@@ -77,13 +70,11 @@ showNotification <- function(ui, action = NULL, duration = 5,
id <- randomID()
res <- processDeps(ui, session)
actionRes <- processDeps(action, session)
session$sendNotification("show",
list(
html = res$html,
action = actionRes$html,
deps = c(res$deps, actionRes$deps),
deps = res$deps,
duration = if (!is.null(duration)) duration * 1000,
closeButton = closeButton,
id = id,

View File

@@ -241,18 +241,6 @@ workerId <- local({
#' This is the request that was used to initiate the websocket connection
#' (as opposed to the request that downloaded the web page for the app).
#' }
#' \item{allowReconnect(value)}{
#' If \code{value} is \code{TRUE} and run in a hosting environment (Shiny
#' Server or Connect) with reconnections enabled, then when the session ends
#' due to the network connection closing, the client will attempt to
#' reconnect to the server. If a reconnection is successful, the browser will
#' send all the current input values to the new session on the server, and
#' the server will recalculate any outputs and send them back to the client.
#' If \code{value} is \code{FALSE}, reconnections will be disabled (this is
#' the default state). If \code{"force"}, then the client browser will always
#' attempt to reconnect. The only reason to use \code{"force"} is for testing
#' on a local connection (without Shiny Server or Connect).
#' }
#' \item{sendCustomMessage(type, message)}{
#' Sends a custom message to the web page. \code{type} must be a
#' single-element character vector giving the type of message, while
@@ -542,14 +530,6 @@ ShinySession <- R6Class(
setShowcase = function(value) {
private$showcase <- !is.null(value) && as.logical(value)
},
allowReconnect = function(value) {
if (!(identical(value, TRUE) || identical(value, FALSE) || identical(value, "force"))) {
stop('value must be TRUE, FALSE, or "force"')
}
private$write(toJSON(list(allowReconnect = value)))
},
defineOutput = function(name, func, label) {
"Binds an output generating function to this name. The function can either
take no parameters, or have named parameters for \\code{name} and

View File

@@ -1,13 +1,6 @@
#shiny-disconnected-overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
body.disconnected {
background-color: #999;
opacity: 0.5;
overflow: hidden;
z-index: 99998;
}
.table.shiny-table > thead > tr > th,
@@ -284,7 +277,7 @@
background-color: rgba(0,0,0,0);
padding: 2px;
width: 250px;
z-index: 99999;
z-index: 9999;
}
.shiny-notification {
@@ -329,9 +322,3 @@
.shiny-notification-close:hover {
color: #000;
}
.shiny-notification-content-action a {
color: rgb(51, 122, 183);
text-decoration: underline;
font-weight: bold;
}

View File

@@ -550,8 +550,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
this.$pendingMessages = [];
this.$activeRequests = {};
this.$nextRequestId = 0;
this.$allowReconnect = false;
};
(function () {
@@ -575,19 +573,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
return !!this.$socket;
};
var scheduledReconnect = null;
this.reconnect = function () {
// This function can be invoked directly even if there's a scheduled
// reconnect, so be sure to clear any such scheduled reconnects.
clearTimeout(scheduledReconnect);
if (this.isConnected()) throw "Attempted to reconnect, but already connected.";
this.$socket = this.createSocket();
this.$initialInput = $.extend({}, this.$inputValues);
this.$updateConditionals();
};
this.createSocket = function () {
var self = this;
@@ -610,22 +595,15 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
ws.binaryType = 'arraybuffer';
return ws;
};
var socket = createSocketFunc();
var hasOpened = false;
socket.onopen = function () {
hasOpened = true;
$(document).trigger({
type: 'shiny:connected',
socket: socket
});
self.onConnected();
socket.send(JSON.stringify({
method: 'init',
data: self.$initialInput
@@ -639,22 +617,13 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
socket.onmessage = function (e) {
self.dispatchMessage(e.data);
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
socket.onclose = function () {
// These things are needed only if we've successfully opened the
// websocket.
if (hasOpened) {
$(document).trigger({
type: 'shiny:disconnected',
socket: socket
});
self.$notifyDisconnected();
}
self.onDisconnected(); // Must be run before self.$removeSocket()
self.$removeSocket();
$(document).trigger({
type: 'shiny:disconnected',
socket: socket
});
$(document.body).addClass('disconnected');
self.$notifyDisconnected();
};
return socket;
};
@@ -695,73 +664,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}
};
this.$removeSocket = function () {
this.$socket = null;
};
this.$scheduleReconnect = function (delay) {
var self = this;
console.log("Waiting " + delay / 1000 + "s before trying to reconnect.");
scheduledReconnect = setTimeout(function () {
self.reconnect();
}, delay);
};
// How long should we wait before trying the next reconnection?
// The delay will increase with subsequent attempts.
// .next: Return the time to wait for next connection, and increment counter.
// .reset: Reset the attempt counter.
var reconnectDelay = function () {
var attempts = 0;
// Time to wait before each reconnection attempt. If we go through all of
// these values, use otherDelay. Add 500ms to each one so that in the last
// 0.5s, it shows "..."
var delays = [1500, 1500, 2500, 2500, 5500, 5500];
var otherDelay = 10500;
return {
next: function next() {
var delay;
if (attempts >= delays.length) {
delay = otherDelay;
} else {
delay = delays[attempts];
}
attempts++;
return delay;
},
reset: function reset() {
attempts = 0;
}
};
}();
this.onDisconnected = function () {
// Add gray-out overlay, if not already present
var $overlay = $('#shiny-disconnected-overlay');
if ($overlay.length === 0) {
$(document.body).append('<div id="shiny-disconnected-overlay"></div>');
}
// To try a reconnect, both the app (this.$allowReconnect) and the
// server (this.$socket.allowReconnect) must allow reconnections, or
// session$allowReconnect("force") was called. The "force" option should
// only be used for testing.
if (this.$allowReconnect === true && this.$socket.allowReconnect === true || this.$allowReconnect === "force") {
var delay = reconnectDelay.next();
exports.showReconnectDialog(delay);
this.$scheduleReconnect(delay);
}
};
this.onConnected = function () {
$('#shiny-disconnected-overlay').remove();
exports.hideReconnectDialog();
reconnectDelay.reset();
};
// NB: Including blobs will cause IE to break!
// TODO: Make blobs work with Internet Explorer
//
@@ -1088,14 +990,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}
});
addMessageHandler('allowReconnect', function (message) {
if (message === true || message === false || message === "force") {
this.$allowReconnect = message;
} else {
throw "Invalid value for allowReconnect: " + message;
}
});
addMessageHandler('custom', function (message) {
// For old-style custom messages - should deprecate and migrate to new
// method
@@ -1214,61 +1108,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
exports.progressHandlers = progressHandlers;
}).call(ShinyApp.prototype);
// showReconnectDialog and hideReconnectDialog are conceptually related to the
// socket code, but they belong in the Shiny/exports object.
{
(function () {
var notificationID = null;
exports.showReconnectDialog = function () {
var reconnectTime = null;
function updateTime() {
var $time = $("#shiny-reconnect-time");
// If the time has been removed, exit and don't reschedule this function.
if ($time.length === 0) return;
var seconds = Math.floor((reconnectTime - new Date().getTime()) / 1000);
if (seconds > 0) {
$time.text(" in " + seconds + "s");
} else {
$time.text("...");
}
// Reschedule this function after 1 second
setTimeout(updateTime, 1000);
}
return function (delay) {
reconnectTime = new Date().getTime() + delay;
// If there's already a reconnect dialog, don't add another
if ($('#shiny-reconnect-text').length > 0) return;
var html = '<span id="shiny-reconnect-text">Attempting to reconnect</span>' + '<span id="shiny-reconnect-time"></span>';
var action = '<a id="shiny-reconnect-now" href="#" onclick="Shiny.shinyapp.reconnect();">Try now</a>';
notificationID = exports.notifications.show({
html: html,
action: action,
duration: null,
closeButton: false,
type: 'warning'
});
updateTime();
};
}();
exports.hideReconnectDialog = function () {
if (notificationID) {
exports.notifications.remove(notificationID);
notificationID = null;
}
};
})();
}
//---------------------------------------------------------------------
// Source file: ../srcjs/notifications.js
@@ -1281,9 +1120,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref$html = _ref.html;
var html = _ref$html === undefined ? '' : _ref$html;
var _ref$action = _ref.action;
var action = _ref$action === undefined ? '' : _ref$action;
var html = _ref$html === undefined ? null : _ref$html;
var _ref$deps = _ref.deps;
var deps = _ref$deps === undefined ? [] : _ref$deps;
var _ref$duration = _ref.duration;
@@ -1305,9 +1142,8 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
if ($notification.length === 0) $notification = _create(id);
// Render html and dependencies
var newHtml = '<div class="shiny-notification-content-text">' + html + '</div>' + ('<div class="shiny-notification-content-action">' + action + '</div>');
var $content = $notification.find('.shiny-notification-content');
exports.renderContent($content, { html: newHtml, deps: deps });
exports.renderContent($content, { html: html, deps: deps });
// Remove any existing classes of the form 'shiny-notification-xxxx'.
// The xxxx would be strings like 'warning'.
@@ -1351,7 +1187,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Returns an individual notification DOM object (wrapped in jQuery).
function _get(id) {
if (!id) return null;
return _getPanel().find('#shiny-notification-' + $escape(id));
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -85,18 +85,6 @@
This is the request that was used to initiate the websocket connection
(as opposed to the request that downloaded the web page for the app).
}
\item{allowReconnect(value)}{
If \code{value} is \code{TRUE} and run in a hosting environment (Shiny
Server or Connect) with reconnections enabled, then when the session ends
due to the network connection closing, the client will attempt to
reconnect to the server. If a reconnection is successful, the browser will
send all the current input values to the new session on the server, and
the server will recalculate any outputs and send them back to the client.
If \code{value} is \code{FALSE}, reconnections will be disabled (this is
the default state). If \code{"force"}, then the client browser will always
attempt to reconnect. The only reason to use \code{"force"} is for testing
on a local connection (without Shiny Server or Connect).
}
\item{sendCustomMessage(type, message)}{
Sends a custom message to the web page. \code{type} must be a
single-element character vector giving the type of message, while

View File

@@ -5,8 +5,8 @@
\alias{showNotification}
\title{Show or remove a notification}
\usage{
showNotification(ui, action = NULL, duration = 5, closeButton = TRUE,
id = NULL, type = c("default", "message", "warning", "error"),
showNotification(ui, duration = 5, closeButton = TRUE, id = NULL,
type = c("default", "message", "warning", "error"),
session = getDefaultReactiveDomain())
removeNotification(id = NULL, session = getDefaultReactiveDomain())
@@ -14,11 +14,6 @@ removeNotification(id = NULL, session = getDefaultReactiveDomain())
\arguments{
\item{ui}{Content of message.}
\item{action}{Message content that represents an action. For example, this
could be a link that the user can click on. This is separate from \code{ui}
so customized layouts can handle the main notification content separately
from action content.}
\item{duration}{Number of seconds to display the message before it
disappears. Use \code{NULL} to make the message not automatically
disappear.}
@@ -52,9 +47,7 @@ shinyApp(
),
server = function(input, output) {
observeEvent(input$show, {
showNotification("Message text",
action = a(href = "javascript:location.reload();", "Reload page")
)
showNotification("Message text")
})
}
)

View File

@@ -3,8 +3,8 @@ exports.notifications = (function() {
// Milliseconds to fade in or out
const fadeDuration = 250;
function show({ html='', action='', deps=[], duration=5000,
id=null, closeButton=true, type=null } = {})
function show({ html=null, deps=[], duration=5000, id=null,
closeButton=true, type=null } = {})
{
if (!id)
id = randomId();
@@ -18,10 +18,8 @@ exports.notifications = (function() {
$notification = _create(id);
// Render html and dependencies
const newHtml = `<div class="shiny-notification-content-text">${html}</div>` +
`<div class="shiny-notification-content-action">${action}</div>`;
const $content = $notification.find('.shiny-notification-content');
exports.renderContent($content, { html: newHtml, deps: deps });
exports.renderContent($content, { html, deps });
// Remove any existing classes of the form 'shiny-notification-xxxx'.
// The xxxx would be strings like 'warning'.
@@ -71,8 +69,6 @@ exports.notifications = (function() {
// Returns an individual notification DOM object (wrapped in jQuery).
function _get(id) {
if (!id)
return null;
return _getPanel().find('#shiny-notification-' + $escape(id));
}

View File

@@ -17,8 +17,6 @@ var ShinyApp = function() {
this.$pendingMessages = [];
this.$activeRequests = {};
this.$nextRequestId = 0;
this.$allowReconnect = false;
};
(function() {
@@ -43,20 +41,6 @@ var ShinyApp = function() {
return !!this.$socket;
};
var scheduledReconnect = null;
this.reconnect = function() {
// This function can be invoked directly even if there's a scheduled
// reconnect, so be sure to clear any such scheduled reconnects.
clearTimeout(scheduledReconnect);
if (this.isConnected())
throw "Attempted to reconnect, but already connected.";
this.$socket = this.createSocket();
this.$initialInput = $.extend({}, this.$inputValues);
this.$updateConditionals();
};
this.createSocket = function () {
var self = this;
@@ -81,22 +65,15 @@ var ShinyApp = function() {
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
ws.binaryType = 'arraybuffer';
return ws;
};
var socket = createSocketFunc();
var hasOpened = false;
socket.onopen = function() {
hasOpened = true;
$(document).trigger({
type: 'shiny:connected',
socket: socket
});
self.onConnected();
socket.send(JSON.stringify({
method: 'init',
data: self.$initialInput
@@ -110,22 +87,13 @@ var ShinyApp = function() {
socket.onmessage = function(e) {
self.dispatchMessage(e.data);
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
socket.onclose = function() {
// These things are needed only if we've successfully opened the
// websocket.
if (hasOpened) {
$(document).trigger({
type: 'shiny:disconnected',
socket: socket
});
self.$notifyDisconnected();
}
self.onDisconnected(); // Must be run before self.$removeSocket()
self.$removeSocket();
$(document).trigger({
type: 'shiny:disconnected',
socket: socket
});
$(document.body).addClass('disconnected');
self.$notifyDisconnected();
};
return socket;
};
@@ -170,73 +138,6 @@ var ShinyApp = function() {
}
};
this.$removeSocket = function() {
this.$socket = null;
};
this.$scheduleReconnect = function(delay) {
var self = this;
console.log("Waiting " + (delay/1000) + "s before trying to reconnect.");
scheduledReconnect = setTimeout(function() { self.reconnect(); }, delay);
};
// How long should we wait before trying the next reconnection?
// The delay will increase with subsequent attempts.
// .next: Return the time to wait for next connection, and increment counter.
// .reset: Reset the attempt counter.
var reconnectDelay = (function() {
var attempts = 0;
// Time to wait before each reconnection attempt. If we go through all of
// these values, use otherDelay. Add 500ms to each one so that in the last
// 0.5s, it shows "..."
var delays = [1500, 1500, 2500, 2500, 5500, 5500];
var otherDelay = 10500;
return {
next: function() {
var delay;
if (attempts >= delays.length) {
delay = otherDelay;
} else {
delay = delays[attempts];
}
attempts++;
return delay;
},
reset: function() {
attempts = 0;
}
};
})();
this.onDisconnected = function() {
// Add gray-out overlay, if not already present
var $overlay = $('#shiny-disconnected-overlay');
if ($overlay.length === 0) {
$(document.body).append('<div id="shiny-disconnected-overlay"></div>');
}
// To try a reconnect, both the app (this.$allowReconnect) and the
// server (this.$socket.allowReconnect) must allow reconnections, or
// session$allowReconnect("force") was called. The "force" option should
// only be used for testing.
if ((this.$allowReconnect === true && this.$socket.allowReconnect === true) ||
this.$allowReconnect === "force")
{
var delay = reconnectDelay.next();
exports.showReconnectDialog(delay);
this.$scheduleReconnect(delay);
}
};
this.onConnected = function() {
$('#shiny-disconnected-overlay').remove();
exports.hideReconnectDialog();
reconnectDelay.reset();
};
// NB: Including blobs will cause IE to break!
// TODO: Make blobs work with Internet Explorer
//
@@ -588,14 +489,6 @@ var ShinyApp = function() {
}
});
addMessageHandler('allowReconnect', function(message) {
if (message === true || message === false || message === "force") {
this.$allowReconnect = message;
} else {
throw "Invalid value for allowReconnect: " + message;
}
});
addMessageHandler('custom', function(message) {
// For old-style custom messages - should deprecate and migrate to new
// method
@@ -726,61 +619,3 @@ var ShinyApp = function() {
}).call(ShinyApp.prototype);
// showReconnectDialog and hideReconnectDialog are conceptually related to the
// socket code, but they belong in the Shiny/exports object.
{
let notificationID = null;
exports.showReconnectDialog = (function() {
var reconnectTime = null;
function updateTime() {
var $time = $("#shiny-reconnect-time");
// If the time has been removed, exit and don't reschedule this function.
if ($time.length === 0) return;
var seconds = Math.floor((reconnectTime - new Date().getTime()) / 1000);
if (seconds > 0) {
$time.text(" in " + seconds + "s");
} else {
$time.text("...");
}
// Reschedule this function after 1 second
setTimeout(updateTime, 1000);
}
return function(delay) {
reconnectTime = new Date().getTime() + delay;
// If there's already a reconnect dialog, don't add another
if ($('#shiny-reconnect-text').length > 0)
return;
var html = '<span id="shiny-reconnect-text">Attempting to reconnect</span>' +
'<span id="shiny-reconnect-time"></span>';
var action = '<a id="shiny-reconnect-now" href="#" onclick="Shiny.shinyapp.reconnect();">Try now</a>';
notificationID = exports.notifications.show({
html: html,
action: action,
duration: null,
closeButton: false,
type: 'warning'
});
updateTime();
};
})();
exports.hideReconnectDialog = function() {
if (notificationID) {
exports.notifications.remove(notificationID);
notificationID = null;
}
};
}