Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev mode aware client and duplicate input/output ID handling updates #3956

Merged
merged 10 commits into from
Jan 24, 2024
15 changes: 15 additions & 0 deletions R/shinyui.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
)
}

if (in_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]] <-
htmlDependency(
"shiny-devmode",
get_package_version("shiny"),
src = "www/shared",
package = "shiny",
head="<script>window.__SHINY_DEV_MODE__ = true;</script>",
all_files = FALSE
)
}


html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
enc2utf8(paste(collapse = "\n", html))
}
Expand Down
15 changes: 12 additions & 3 deletions inst/www/shared/shiny.js
Original file line number Diff line number Diff line change
Expand Up @@ -18565,9 +18565,10 @@
idTypes.forEach(function(type) {
return counts[type] += 1;
});
if (Object.values(counts).some(function(count) {
return count > 1;
})) {
if (counts.input === 1 && counts.output === 1 && !Shiny.inDevMode()) {
return;
}
if (counts.input > 0 || counts.output > 0) {
duplicateIds.set(id, counts);
}
});
Expand Down Expand Up @@ -22344,6 +22345,9 @@
_defineProperty19(ShinyErrorMessage, "styles", [i(_templateObject5 || (_templateObject5 = _taggedTemplateLiteral(['\n :host {\n color: var(--red-11);\n display: block;\n font-size: 1.4rem;\n\n position: relative;\n --icon-size: 1.5rem;\n\n --padding-top: var(--space-1);\n --padding-bottom: var(--space-3);\n\n /* Reset box sizing */\n box-sizing: border-box;\n }\n\n .container {\n display: flex;\n gap: var(--space-2);\n }\n\n .contents {\n width: 40ch;\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n padding-bottom: var(--padding-bottom);\n padding-top: var(--padding-top);\n overflow: auto;\n }\n\n :host(:last-of-type) .contents {\n --padding-bottom: var(--space-1);\n }\n\n .contents > h3 {\n font-size: 1em;\n font-weight: 500;\n color: var(--red-12);\n }\n\n .contents > * {\n margin-block: 0;\n }\n\n .error-message {\n font-family: "Courier New", Courier, monospace;\n }\n\n .decoration-container {\n flex-shrink: 0;\n padding-inline: var(0.375rem);\n position: relative;\n\n --line-w: 2px;\n --dot-size: 1rem;\n }\n\n :host(:hover) .decoration-container {\n --scale: 1.25;\n }\n\n .vertical-line {\n margin-inline: auto;\n width: var(--line-w);\n height: 100%;\n\n background-color: var(--red-10);\n }\n\n :host(:first-of-type) .vertical-line {\n height: calc(100% - var(--dot-size));\n margin-top: var(--dot-size);\n }\n\n .dot {\n position: absolute;\n width: var(--dot-size);\n height: var(--dot-size);\n top: var(--dot-size);\n left: calc(50% - var(--dot-size) / 2);\n border-radius: 100%;\n transform: scale(var(--scale, 1));\n\n color: var(--red-6);\n background-color: var(--red-10);\n }\n\n .actions {\n transform: scaleX(0);\n transition: transform calc(var(--animation-speed) / 2) ease-in-out;\n display: flex;\n justify-content: center;\n flex-direction: column;\n }\n\n /* Delay transition on mouseout so the buttons don\'t jump away if the user\n overshoots them with their mouse */\n :host(:not(:hover)) .actions {\n transition-delay: 0.15s;\n }\n\n :host(:hover) .actions {\n transform: scaleX(1);\n }\n\n ', "\n\n .copy-button {\n padding: 0;\n width: 4rem;\n height: 4rem;\n position: relative;\n --pad: var(--space-2);\n }\n\n .copy-button-inner {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n transition: transform 0.5s;\n transform-style: preserve-3d;\n }\n\n /* Animate flipping to the other side when the .copy-success class is\n added to the host */\n :host(.copy-success) .copy-button-inner {\n transform: rotateY(180deg);\n }\n\n /* Position the front and back side */\n .copy-button .front,\n .copy-button .back {\n --side: calc(100% - 2 * var(--pad));\n position: absolute;\n inset: var(--pad);\n height: var(--side);\n width: var(--side);\n -webkit-backface-visibility: hidden; /* Safari */\n backface-visibility: hidden;\n }\n\n .copy-button:hover .copy-button-inner {\n background-color: var(--gray-2);\n }\n\n /* Style the back side */\n .copy-button .back {\n --pad: var(--space-1);\n color: var(--green-8);\n transform: rotateY(180deg);\n }\n "])), buttonStyles)]);
customElements.define("shiny-error-message", ShinyErrorMessage);
function showErrorInClientConsole(e4) {
if (!Shiny.inDevMode()) {
return;
}
var errorMsg = null;
var headline = "Error on client while running Shiny app";
if (typeof e4 === "string") {
Expand Down Expand Up @@ -25101,6 +25105,11 @@
windowShiny2.renderContent = renderContent;
windowShiny2.renderHtmlAsync = renderHtmlAsync;
windowShiny2.renderHtml = renderHtml2;
windowShiny2.inDevMode = function() {
if ("__SHINY_DEV_MODE__" in window)
return Boolean(window.__SHINY_DEV_MODE__);
return false;
};
(0, import_jquery40.default)(function() {
setTimeout(/* @__PURE__ */ _asyncToGenerator15(/* @__PURE__ */ _regeneratorRuntime15().mark(function _callee() {
return _regeneratorRuntime15().wrap(function _callee$(_context) {
Expand Down
4 changes: 2 additions & 2 deletions inst/www/shared/shiny.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions inst/www/shared/shiny.min.js.map

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion srcts/src/components/errorConsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,12 +484,17 @@ customElements.define("shiny-error-message", ShinyErrorMessage);

/**
* Function to show an error message to user in shiny-error-message web
* component
* component. Only shows the error if we're in development mode.
* @param e - Error object to show to user. This is whatever is caught in
* a try-catch statement so it may be a string or it may be a proper Error
* 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";

Expand Down
12 changes: 11 additions & 1 deletion srcts/src/shiny/bind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,17 @@ const bindingsRegistry = (() => {

idTypes.forEach((type) => (counts[type] += 1));

if (Object.values(counts).some((count) => count > 1)) {
// If there's a single duplication of ids across both binding types, then
// when we're not in devmode, we allow this to pass because a good amount of
// existing applications use this pattern even though its invalid. Eventually
// this behavior should be removed.
if (counts.input === 1 && counts.output === 1 && !Shiny.inDevMode()) {
return;
}

// If we have duplicated IDs, then add them to the set of duplicated IDs
// to be reported to the user.
if (counts.input > 0 || counts.output > 0) {
duplicateIds.set(id, counts);
}
});
Expand Down
15 changes: 15 additions & 0 deletions srcts/src/shiny/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ interface Shiny {
// Eventually deprecate
// For old-style custom messages - should deprecate and migrate to new
oncustommessage?: Handler;

/**
* Method to check if Shiny is running in development mode. By packaging as a
* method, we can we can avoid needing to look for the `__SHINY_DEV_MODE__`
* variable in the global scope.
* @returns `true` if Shiny is running in development mode, `false` otherwise.
*/
inDevMode: () => boolean;
}

let windowShiny: Shiny;
Expand Down Expand Up @@ -108,6 +116,13 @@ function setShiny(windowShiny_: Shiny): void {
windowShiny.renderHtmlAsync = renderHtmlAsync;
windowShiny.renderHtml = renderHtml;

windowShiny.inDevMode = () => {
if ("__SHINY_DEV_MODE__" in window)
return Boolean(window.__SHINY_DEV_MODE__);

return false;
};

$(function () {
// Init Shiny a little later than document ready, so user code can
// run first (i.e. to register bindings)
Expand Down
2 changes: 1 addition & 1 deletion srcts/types/src/components/errorConsole.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export declare class ShinyErrorMessage extends LitElement {
}
/**
* Function to show an error message to user in shiny-error-message web
* component
* component. Only shows the error if we're in development mode.
* @param e - Error object to show to user. This is whatever is caught in
* a try-catch statement so it may be a string or it may be a proper Error
* object.
Expand Down
7 changes: 7 additions & 0 deletions srcts/types/src/shiny/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ interface Shiny {
unbindAll?: typeof shinyUnbindAll;
initializeInputs?: typeof shinyInitializeInputs;
oncustommessage?: Handler;
/**
* Method to check if Shiny is running in development mode. By packaging as a
* method, we can we can avoid needing to look for the `__SHINY_DEV_MODE__`
* variable in the global scope.
* @returns `true` if Shiny is running in development mode, `false` otherwise.
*/
inDevMode: () => boolean;
}
declare let windowShiny: Shiny;
declare function setShiny(windowShiny_: Shiny): void;
Expand Down