Compare commits

...

3 Commits

Author SHA1 Message Date
Joe Cheng
ab69d7292c Only show the lighter curtain if autoreload is on
I added this cause I'm slightly worried about other server environments
sending the same 1012 close code.
2023-10-18 16:59:54 -07:00
Joe Cheng
36a99287d6 Make autoreload survive laptop suspend
Also simplify the implementation for softening the grey curtain when
autoreload is in progress (only applies to Shiny for Python)
2023-10-18 16:59:18 -07:00
Joe Cheng
6560c59c8f Soften visually jarring greyout when autoreloading
This change detects when Shiny is autoreloading, and instead of
showing the typical disconnection treatment (dark grey), it
initially shows nothing and then quickly fades to very light
grey.

The goal is for instant autoreloads to feel seamless, but longer
autoreloads to make it clear when the autoreload has completed,
and still not feel as jarring as a full disconnect.
2023-10-18 16:58:13 -07:00
11 changed files with 114 additions and 35 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -17894,7 +17894,8 @@
}, _callee2);
})));
};
socket.onclose = function() {
socket.onclose = function(e) {
var restarting = e.code === 1012;
if (hasOpened) {
(0, import_jquery38.default)(document).trigger({
type: "shiny:disconnected",
@@ -17902,7 +17903,7 @@
});
_this.$notifyDisconnected();
}
_this.onDisconnected();
_this.onDisconnected(restarting);
_this.$removeSocket();
};
return socket;
@@ -17982,10 +17983,11 @@
}, {
key: "onDisconnected",
value: function onDisconnected() {
var $overlay = (0, import_jquery38.default)("#shiny-disconnected-overlay");
if ($overlay.length === 0) {
var reloading = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : false;
if ((0, import_jquery38.default)("#shiny-disconnected-overlay").length === 0) {
(0, import_jquery38.default)(document.body).append('<div id="shiny-disconnected-overlay"></div>');
}
(0, import_jquery38.default)("#shiny-disconnected-overlay").toggleClass("reloading", reloading);
if (this.$allowReconnect === true && this.$socket.allowReconnect === true || this.$allowReconnect === "force") {
var delay = this.reconnectDelay.next();
showReconnectDialog(delay);

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

File diff suppressed because one or more lines are too long

View File

@@ -88,10 +88,7 @@ pre.shiny-text-output {
#shiny-disconnected-overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
inset: 0;
background-color: $shiny-disconnected-bg;
opacity: 0.5;
overflow: hidden;
@@ -99,6 +96,17 @@ pre.shiny-text-output {
pointer-events: none;
}
html.autoreload-enabled #shiny-disconnected-overlay.reloading {
opacity: 0;
animation: fadeIn 250ms forwards;
animation-delay: 1s;
}
@keyframes fadeIn {
to {
opacity: 0.1;
}
}
.table.shiny-table {
@include table-padding($left: 12px, $right: 12px);
}

View File

@@ -1,18 +1,86 @@
/* eslint-disable unicorn/filename-case */
const protocol = (window.location.protocol === "https:") ? "wss:" : "ws:";
document.documentElement.classList.add("autoreload-enabled");
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
// Add trailing slash to path, if necessary, before appending "autoreload"
const defaultPath = window.location.pathname.replace(/\/?$/, "/") + "autoreload/";
const defaultPath =
window.location.pathname.replace(/\/?$/, "/") + "autoreload/";
const defaultUrl = `${protocol}//${window.location.host}${defaultPath}`;
// By default, use the defaultUrl. But if there's a data-ws-url attribute on our
// <script> tag, use that instead.
const wsUrl = document.currentScript.dataset.wsUrl || defaultUrl;
const ws = new WebSocket(wsUrl);
const wsUrl = document.currentScript?.dataset?.wsUrl || defaultUrl;
ws.onmessage = function (event) {
if (event.data === "autoreload") {
window.location.reload();
/**
* Connects to an autoreload URL and waits for the server to tell us what to do.
*
* @param url The ws:// or wss:// URL to connect to.
* @returns true if the server requests a reload, or false if the connection was
* successfully established but then closed without the server requesting a
* reload
* @throws A nondescript error if the connection fails to be established.
*/
async function autoreload(url: string): Promise<boolean> {
const ws = new WebSocket(url);
let success = false;
return new Promise((resolve, reject) => {
ws.onopen = () => {
success = true;
};
ws.onerror = (err) => {
reject(err);
};
ws.onclose = () => {
if (!success) {
reject(new Error("WebSocket connection failed"));
} else {
resolve(false);
}
};
ws.onmessage = function (event) {
if (event.data === "autoreload") {
resolve(true);
}
};
});
}
async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function initialize() {
while (true) {
try {
if (await autoreload(wsUrl)) {
window.location.reload();
return;
}
} catch (err) {
// It's possible for the autoreload() call to throw. If it does, that
// means we tried but failed to connect to the autoreload socket. This
// probably means that the entire `shiny run --reload` process was
// restarted. As of today, the autoreload websocket port number is
// randomly chosen for each `shiny run --reload` process, so it's
// impossible for us to recover.
console.debug("Giving up on autoreload");
return;
}
// If we get here, the connection to the autoreload server was
// successful but then got broken. Wait for a second, and then
// try to re-establish the connection.
await sleep(1000);
}
};
}
initialize().catch((err) => {
console.error(err);
});
export {};

View File

@@ -244,7 +244,8 @@ class ShinyApp {
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
socket.onclose = () => {
socket.onclose = (e) => {
const restarting = e.code === 1012; // Uvicorn sets this code when autoreloading
// These things are needed only if we've successfully opened the
// websocket.
if (hasOpened) {
@@ -257,7 +258,7 @@ class ShinyApp {
this.$notifyDisconnected();
}
this.onDisconnected(); // Must be run before this.$removeSocket()
this.onDisconnected(restarting); // Must be run before this.$removeSocket()
this.$removeSocket();
};
return socket;
@@ -333,13 +334,12 @@ class ShinyApp {
};
})();
onDisconnected(): void {
onDisconnected(reloading = false): void {
// Add gray-out overlay, if not already present
const $overlay = $("#shiny-disconnected-overlay");
if ($overlay.length === 0) {
if ($("#shiny-disconnected-overlay").length === 0) {
$(document.body).append('<div id="shiny-disconnected-overlay"></div>');
}
$("#shiny-disconnected-overlay").toggleClass("reloading", reloading);
// To try a reconnect, both the app (this.$allowReconnect) and the
// server (this.$socket.allowReconnect) must allow reconnections, or

View File

@@ -61,7 +61,7 @@ declare class ShinyApp {
next: () => number;
reset: () => void;
};
onDisconnected(): void;
onDisconnected(reloading?: boolean): void;
onConnected(): void;
makeRequest(method: string, args: unknown[], onSuccess: OnSuccessRequest, onError: OnErrorRequest, blobs: Array<ArrayBuffer | Blob | string> | undefined): void;
$sendMsg(msg: MessageValue): void;