diff --git a/DESCRIPTION b/DESCRIPTION index 41cea6e64..0f3fcb080 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: shiny Type: Package Title: Web Application Framework for R -Version: 1.5.0.9000 +Version: 1.5.0.9001 Authors@R: c( person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"), person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"), diff --git a/NAMESPACE b/NAMESPACE index dfb782061..f20684b2c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -116,6 +116,7 @@ export(hoverOpts) export(hr) export(htmlOutput) export(htmlTemplate) +export(httpResponse) export(icon) export(imageOutput) export(img) diff --git a/NEWS.md b/NEWS.md index e730e06a6..d6a26aea6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,10 @@ shiny 1.5.0.9000 * Added appropriate labels to `icon()` element to provide screen-reader users with alternative descriptions for the `fontawesome` and `glyphicon`: `aria-label` is automatically applied based on the fontawesome name. For example, `icon("calendar")` will be announced as "calendar icon" to screen readers. "presentation" aria role has also been attached to `icon()` to remove redundant semantic info for screen readers. +### Minor new features and improvements + +* When UI is specified as a function (e.g. `ui <- function(req) { ... }`), the response can now be an HTTP response as returned from the (newly exported) `httpResponse()` function. (#2970) + ### Bug fixes * Fixed #2859: `renderPlot()` wasn't correctly setting `showtext::showtext_opts()`'s `dpi` setting with the correct resolution on high resolution displays; which means, if the font was rendered by showtext, font sizes would look smaller than they should on such displays. (#2941) diff --git a/R/middleware.R b/R/middleware.R index d59a47741..44595b117 100644 --- a/R/middleware.R +++ b/R/middleware.R @@ -14,7 +14,26 @@ # returns `NULL`, or an `httpResponse`. # ## ------------------------------------------------------------------------ -httpResponse <- function(status = 200, + +#' Create an HTTP response object +#' +#' @param status HTTP status code for the response. +#' @param content_type The value for the `Content-Type` header. +#' @param content The body of the response, given as a single-element character +#' vector (will be encoded as UTF-8) or a raw vector. +#' @param headers A named list of additional headers to include. Do not include +#' `Content-Length` (as it is automatically calculated) or `Content-Type` (the +#' `content_type` argument is used instead). +#' +#' @examples +#' httpResponse(status = 405L, +#' content_type = "text/plain", +#' content = "The requested method was not allowed" +#' ) +#' +#' @keywords internal +#' @export +httpResponse <- function(status = 200L, content_type = "text/html; charset=UTF-8", content = "", headers = list()) { diff --git a/R/shinyui.R b/R/shinyui.R index cb443b159..d96e6a9fc 100644 --- a/R/shinyui.R +++ b/R/shinyui.R @@ -24,7 +24,7 @@ withMathJax <- function(...) { ) } -renderPage <- function(ui, connection, showcase=0, testMode=FALSE) { +renderPage <- function(ui, showcase=0, testMode=FALSE) { # If the ui is a NOT complete document (created by htmlTemplate()), then do some # preprocessing and make sure it's a complete document. if (!inherits(ui, "html_document")) { @@ -77,7 +77,7 @@ renderPage <- function(ui, connection, showcase=0, testMode=FALSE) { } html <- renderDocument(ui, shiny_deps, processDep = createWebDependency) - writeUTF8(html, con = connection) + enc2utf8(paste(collapse = "\n", html)) } #' Create a Shiny UI handler @@ -101,16 +101,18 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") { force(ui) + allowed_methods <- "GET" + if (is.function(ui)) { + allowed_methods <- attr(ui, "http_methods_supported", exact = TRUE) %OR% allowed_methods + } + function(req) { - if (!identical(req$REQUEST_METHOD, 'GET')) + if (!isTRUE(req$REQUEST_METHOD %in% allowed_methods)) return(NULL) if (!isTRUE(grepl(uiPattern, req$PATH_INFO))) return(NULL) - textConn <- file(open = "w+") - on.exit(close(textConn)) - showcaseMode <- .globals$showcaseDefault if (.globals$showcaseOverride) { mode <- showcaseModeOfReq(req) @@ -150,8 +152,11 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") { if (is.null(uiValue)) return(NULL) - renderPage(uiValue, textConn, showcaseMode, testMode) - html <- paste(readLines(textConn, encoding = 'UTF-8'), collapse='\n') - return(httpResponse(200, content=enc2utf8(html))) + if (inherits(uiValue, "httpResponse")) { + return(uiValue) + } else { + html <- renderPage(uiValue, showcaseMode, testMode) + return(httpResponse(200, content=html)) + } } } diff --git a/inst/www/shared/shiny.js b/inst/www/shared/shiny.js index b19451f2b..e0a20532b 100644 --- a/inst/www/shared/shiny.js +++ b/inst/www/shared/shiny.js @@ -9,7 +9,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope (function () { var $ = jQuery; var exports = window.Shiny = window.Shiny || {}; - exports.version = "1.5.0.9000"; // Version number inserted by Grunt + exports.version = "1.5.0.9001"; // Version number inserted by Grunt var origPushState = window.history.pushState; diff --git a/inst/www/shared/shiny.min.js b/inst/www/shared/shiny.min.js index e1a332424..6c4a9658f 100644 --- a/inst/www/shared/shiny.min.js +++ b/inst/www/shared/shiny.min.js @@ -1,4 +1,4 @@ -/*! shiny 1.5.0.9000 | (c) 2012-2020 RStudio, Inc. | License: GPL-3 | file LICENSE */ +/*! shiny 1.5.0.9001 | (c) 2012-2020 RStudio, Inc. | License: GPL-3 | file LICENSE */ -"use strict";function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function _defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}!function(){var $=jQuery,exports=window.Shiny=window.Shiny||{};exports.version="1.5.0.9000";var origPushState=window.history.pushState;function escapeHTML(e){var t={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return e.replace(/[&<>'"\/]/g,function(e){return t[e]})}function randomId(){return Math.floor(4294967296+64424509440*Math.random()).toString(16)}function strToBool(e){if(e&&e.toLowerCase)switch(e.toLowerCase()){case"true":return!0;case"false":return!1;default:return}}function getStyle(e,t){var n,i;return e.currentStyle?n=e.currentStyle[t]:!window.getComputedStyle||(i=document.defaultView.getComputedStyle(e,null))&&(n=i.getPropertyValue(t)),n}function padZeros(e,t){for(var n=e.toString();n.length?@\[\\\]^`{|}~])/g,"\\$1")};function mapValues(e,t){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[i]=t(e[i],i,e));return n}function isnan(e){return"number"==typeof e&&isNaN(e)}function _equal(e,t){if("object"===$.type(e)&&"object"===$.type(t)){if(Object.keys(e).length!==Object.keys(t).length)return;for(var n in e)if(!t.hasOwnProperty(n)||!_equal(e[n],t[n]))return;return 1}if("array"!==$.type(e)||"array"!==$.type(t))return e===t;if(e.length===t.length){for(var i=0;i="===t)return 0<=i;if(">"===t)return 0'),(!0===this.$allowReconnect&&!0===this.$socket.allowReconnect||"force"===this.$allowReconnect)&&(e=reconnectDelay.next(),exports.showReconnectDialog(e),this.$scheduleReconnect(e))},this.onConnected=function(){$("#shiny-disconnected-overlay").remove(),exports.hideReconnectDialog(),reconnectDelay.reset()},this.makeRequest=function(e,t,n,i,a){for(var r=this.$nextRequestId;this.$activeRequests[r];)r=(r+1)%1e9;this.$nextRequestId=r+1,this.$activeRequests[r]={onSuccess:n,onError:i};var o=JSON.stringify({method:e,args:t,tag:r});if(a){var s=function(e){var t=new ArrayBuffer(4);return new DataView(t).setUint32(0,e,!0),t},u=[];u.push(s(16908802));var l=makeBlob([o]);u.push(s(l.size)),u.push(l);for(var d=0;d a"),l=null;null!==r.target&&(e=getTargetTabs(n,i,r.target),l=e.$liTag);var d,c,p,h=function(){{if(null!==r.menuName){var e=$("a.dropdown-toggle[data-value='"+$escape(r.menuName)+"']");if(0===e.length)throw"There is no navbarMenu with menuName equal to '"+r.menuName+"'";var t=e.find("+ ul.dropdown-menu"),n=t.attr("data-tabsetid");return{$tabset:t,id:n}}if(null!==r.target){var i=l.parent("ul");if(i.hasClass("dropdown-menu")){var a=i.attr("data-tabsetid");return{$tabset:i,id:a}}}}return null}();if(null!==h){if("dropdown"===u.attr("data-toggle"))throw"Cannot insert a navbarMenu inside another one";n=h.$tabset,a=h.id}"tab"===u.attr("data-toggle")&&(d="tab-"+a+"-"+(c=a,p=[0],n.find("> li").each(function(){var e,t=$(this).find("> a[data-toggle='tab']");0 a").attr("href","#"+d),o.attr("id",d)),"before"===r.position?l?l.before(s):n.append(s):"after"===r.position&&(l?l.after(s):n.prepend(s)),exports.renderContent(s[0],{html:s.html(),deps:r.liTag.deps}),exports.renderContent(i[0],{html:"",deps:r.divTag.deps},"beforeend"),o.get().forEach(function(e){i[0].appendChild(e),exports.renderContent(e,e.innerHTML||e.textContent)}),r.select&&s.find("a").tab("show")}),addMessageHandler("shiny-remove-tab",function(e){var t=getTabset(e.inputId);tabApplyFunction(getTargetTabs(t,getTabContent(t),e.target),function(e){exports.unbindAll(e,!0),e.remove()}),ensureTabsetHasVisibleTab(t)}),addMessageHandler("shiny-change-tab-visibility",function(t){var e=getTabset(t.inputId);tabApplyFunction(getTargetTabs(e,getTabContent(e),t.target),function(e){"show"===t.type?e.css("display",""):"hide"===t.type&&(e.hide(),e.removeClass("active"))},!0),ensureTabsetHasVisibleTab(e)}),addMessageHandler("updateQueryString",function(e){if("replace"!==e.mode){var t=null;if("#"===e.queryString.charAt(0))t="hash";else{if("?"!==e.queryString.charAt(0))throw"The 'query' string must start with either '?' (to update the query string) or with '#' (to update the hash).";t="query"}var n=window.location.pathname,i=window.location.search,a=window.location.hash,r=n;r+="query"===t?e.queryString:i+e.queryString,window.history.pushState(null,null,r),-1!==e.queryString.indexOf("#")&&(t="hash"),window.location.hash!==a&&(t="hash"),"hash"===t&&$(document).trigger("hashchange")}else window.history.replaceState(null,null,e.queryString)}),addMessageHandler("resetBrush",function(e){exports.resetBrush(e.brushId)});var progressHandlers={binding:function(e){var t=e.id,n=this.$bindings[t];n&&($(n.el).trigger({type:"shiny:outputinvalidated",binding:n,name:t}),n.showProgress&&n.showProgress(!0))},open:function(e){var t,n,i,a,r;"notification"===e.style?exports.notifications.show({html:'
')+'
message
',id:e.id,duration:null}):"old"===e.style&&(0===(t=$(".shiny-progress-container")).length&&(t=$('
'),$(document.body).append(t)),n=$(".shiny-progress.open").length,(i=$('
message
')).attr("id",e.id),t.append(i),(a=i.find(".progress")).css("top",n*a.height()+"px"),(r=i.find(".progress-text")).css("top",3*a.height()+n*r.outerHeight()+"px"),i.hide())},update:function(e){if("notification"===e.style){if(0===(t=$("#shiny-progress-"+e.id)).length)return;void 0!==e.message&&t.find(".progress-message").text(e.message),void 0!==e.detail&&t.find(".progress-detail").text(e.detail),void 0!==e.value&&null!==e.value&&(t.find(".progress").show(),t.find(".progress-bar").width(100*e.value+"%"))}else{var t;"old"===e.style&&(t=$("#"+e.id+".shiny-progress"),void 0!==e.message&&t.find(".progress-message").text(e.message),void 0!==e.detail&&t.find(".progress-detail").text(e.detail),void 0!==e.value&&null!==e.value&&(t.find(".progress").show(),t.find(".bar").width(100*e.value+"%")),t.fadeIn())}},close:function(e){var t;"notification"===e.style?exports.notifications.remove(e.id):"old"===e.style&&((t=$("#"+e.id+".shiny-progress")).removeClass("open"),t.fadeOut({complete:function(){t.remove(),0===$(".shiny-progress").length&&$(".shiny-progress-container").remove()}}))}};exports.progressHandlers=progressHandlers,this.getTestSnapshotBaseUrl=function(){var e=(0Attempting to reconnect',action:'Try now',duration:null,closeButton:!1,type:"warning"}),Hg())}),exports.hideReconnectDialog=function(){exports.notifications.remove("reconnect")},exports.notifications=(Ng=250,{show:function(){var e=0');var f=Qg(d);0===f.length&&(f=function(t){var e=Qg(t);return 0===e.length&&((e=$('
')+'
×
')).find(".shiny-notification-close").on("click",function(e){e.preventDefault(),e.stopPropagation(),Pg(t)}),Sg().append(e)),e}(d));var m='
'.concat(n,"
")+'
'.concat(a,"
"),v=f.find(".shiny-notification-content");exports.renderContent(v,{html:m,deps:o});var y=f.attr("class").split(/\s+/).filter(function(e){return e.match(/^shiny-notification-/)}).join(" ");f.removeClass(y),g&&"default"!==g&&f.addClass("shiny-notification-"+g);var b=f.find(".shiny-notification-close");return p&&0===b.length?f.append('
×
'):p||0===b.length||b.remove(),u?function(e,t){Wg(e);var n=setTimeout(function(){Pg(e)},t);Qg(e).data("removalCallback",n)}(d,u):Wg(d),d},remove:Pg}),exports.modal={show:function(e){var t=0'),$(document.body).append(o),o.on("hidden.bs.modal",function(e){e.target===$("#shiny-modal")[0]&&(exports.unbindAll(o),o.remove())})),o.on("keydown.shinymodal",function(e){!1!==$("#shiny-modal").data("keyboard")&&27===e.keyCode&&(e.stopPropagation(),e.preventDefault())}),exports.renderContent(o,{html:i,deps:r})},remove:function(){var e=$("#shiny-modal-wrapper");e.off("keydown.shinymodal"),0n.right?t.x=n.right:e.xn.bottom?t.y=n.bottom:e.y=i.left-l&&s<=i.bottom+d&&s>=i.top-d&&(c.push(f.panels[h]),n=t=0,o>i.right&&o<=i.right+l?t=o-i.right:o=i.left-l&&(t=o-i.left),s>i.bottom&&s<=i.bottom+d?n=s-i.bottom:s=i.top-d&&(n=s-i.top),p.push(Math.sqrt(Math.pow(t,2)+Math.pow(n,2))))}if(c.length)for(var g=Math.min.apply(null,p),h=0;h=a&&(e.x=n?o.left=!0:e.x>t.xmax&&e.x<=i&&(o.right=!0)),("xy"===l.brushDirection||"y"===l.brushDirection)&&e.x<=i&&e.x>=n&&(e.y=a?o.top=!0:e.y>t.ymax&&e.y<=r&&(o.bottom=!0)),o}function g(e){if(void 0===e)return $.extend({},c.boundsCss);var t={x:e.xmin,y:e.ymin},n={x:e.xmax,y:e.ymax},i=c.panel,a=i.range;l.brushClip&&(t=h(i.clipImg(p(t))),n=h(i.clipImg(p(n)))),"xy"===l.brushDirection||("x"===l.brushDirection?(t.y=h({y:a.top}).y,n.y=h({y:a.bottom}).y):"y"===l.brushDirection&&(t.x=h({x:a.left}).x,n.x=h({x:a.right}).x)),c.boundsCss={xmin:t.x,xmax:n.x,ymin:t.y,ymax:n.y};var r=c.panel.scaleImgToData(p(t)),o=c.panel.scaleImgToData(p(n));c.boundsData=imageutils.findBox(r,o),c.boundsData=mapValues(c.boundsData,function(e){return roundSignif(e,14)}),d.data("bounds-data",c.boundsData),d.data("panel",c.panel)}function f(e){if(void 0===e)return $.extend({},c.boundsData);var t=mapValues(t=h(c.panel.scaleDataToImg(e)),function(e){return roundSignif(e,13)});g({xmin:Math.min(t.xmin,t.xmax),xmax:Math.max(t.xmin,t.xmax),ymin:Math.min(t.ymin,t.ymax),ymax:Math.max(t.ymin,t.ymax)})}function m(){var e=findOrigin(r.find("img")),t=c.boundsCss;d.offset({top:e.y+t.ymin,left:e.x+t.xmin}).outerWidth(t.xmax-t.xmin+1).outerHeight(t.ymax-t.ymin+1)}return t(),{reset:t,importOldBrush:function(){var e=r.find("#"+u.id+"_brush");if(0!==e.length){var t=e.data("bounds-data"),n=e.data("panel");if(t&&n){for(var i=0;i=t.xmin&&e.y<=t.ymax&&e.y>=t.ymin},isInResizeArea:function(e){var t=n(e);return t.left||t.right||t.top||t.bottom},whichResizeSides:n,onResize:function(){var e=f();for(var t in e)if(isnan(e[t]))return;f(e),m()},boundsCss:g,boundsData:f,getPanel:function(){return c.panel},down:function(e){if(void 0===e)return c.down;c.down=e},up:function(e){if(void 0===e)return c.up;c.up=e},isBrushing:function(){return c.brushing},startBrushing:function(){c.brushing=!0,function(){d&&d.remove(),d=$(document.createElement("div")).attr("id",u.id+"_brush").css({"background-color":l.brushFill,opacity:l.brushOpacity,"pointer-events":"none",position:"absolute"}).hide();var e="1px solid "+l.brushStroke;"xy"===l.brushDirection?d.css({border:e}):"x"===l.brushDirection?d.css({"border-left":e,"border-right":e}):"y"===l.brushDirection&&d.css({"border-top":e,"border-bottom":e}),r.append(d),d.offset({x:0,y:0}).width(0).outerHeight(0)}(),c.panel=o.getPanelCss(c.down,e),g(imageutils.findBox(c.down,c.down)),m()},brushTo:function(e){g(imageutils.findBox(c.down,e)),d.show(),m()},stopBrushing:function(){c.brushing=!1,g(imageutils.findBox(c.down,c.up))},isDragging:function(){return c.dragging},startDragging:function(){c.dragging=!0,c.changeStartBounds=$.extend({},c.boundsCss)},dragTo:function(e){var t,n,i,a,r=e.x-c.down.x,o=e.y-c.down.y,s=c.changeStartBounds,u={xmin:s.xmin+r,xmax:s.xmax+r,ymin:s.ymin+o,ymax:s.ymax+o};l.brushClip&&(t=c.panel.range,i=[(n=p(u)).xmin,n.xmax],a=[n.ymin,n.ymax],i=imageutils.shiftToRange(i,t.left,t.right),a=imageutils.shiftToRange(a,t.top,t.bottom),u=h({xmin:i[0],xmax:i[1],ymin:a[0],ymax:a[1]})),g(u),m()},stopDragging:function(){c.dragging=!1},isResizing:function(){return c.resizing},startResizing:function(){c.resizing=!0,c.changeStartBounds=$.extend({},c.boundsCss),c.resizeSides=n(c.down)},resizeTo:function(e){var t,n,i,a,r={x:e.x-c.down.x,y:e.y-c.down.y},o=p(r),s=p(c.changeStartBounds),u=c.panel.range;c.resizeSides.left?(t=imageutils.shiftToRange(s.xmin+o.x,u.left,s.xmax)[0],s.xmin=t):c.resizeSides.right&&(n=imageutils.shiftToRange(s.xmax+o.x,s.xmin,u.right)[0],s.xmax=n),c.resizeSides.top?(i=imageutils.shiftToRange(s.ymin+o.y,u.top,s.ymax)[0],s.ymin=i):c.resizeSides.bottom&&(a=imageutils.shiftToRange(s.ymax+o.y,s.ymin,u.bottom)[0],s.ymax=a),g(h(s)),m()},stopResizing:function(){c.resizing=!1}}},exports.resetBrush=function(e){exports.setInputValue(e,null),imageOutputBinding.find(document).trigger("shiny-internal:brushed",{brushId:e,outputId:null})};var htmlOutputBinding=new OutputBinding;$.extend(htmlOutputBinding,{find:function(e){return $(e).find(".shiny-html-output")},onValueError:function(e,t){exports.unbindAll(e),this.renderError(e,t)},renderValue:function(e,t){exports.renderContent(e,t)}}),outputBindings.register(htmlOutputBinding,"shiny.htmlOutput");var renderDependencies=exports.renderDependencies=function(e){e&&$.each(e,function(e,t){renderDependency(t)})};exports.renderContent=function(e,t){var n,i=2").attr("name",n).attr("content",e[n])}),l.append(e)),n.stylesheet&&(t=$.map(asArray(n.stylesheet),function(e){return $("").attr("href",u+"/"+encodeURI(e))}),l.append(t)),n.script&&(i=$.map(asArray(n.script),function(e){return $("