mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-12 08:27:56 -05:00
Compare commits
4 Commits
app-url
...
bootstrapL
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
923c1b2450 | ||
|
|
914dc71d67 | ||
|
|
a13ca9fd3a | ||
|
|
b8b34370da |
@@ -21,19 +21,3 @@
|
||||
^TODO-promises.md$
|
||||
^manualtests$
|
||||
^\.github$
|
||||
|
||||
^\.yarn$
|
||||
^\.vscode$
|
||||
^\.madgerc$
|
||||
^\.prettierrc\.yml$
|
||||
^babel\.config\.json$
|
||||
^jest\.config\.js$
|
||||
^package\.json$
|
||||
^tsconfig\.json$
|
||||
^yarn\.lock$
|
||||
^node_modules$
|
||||
^coverage$
|
||||
^.ignore$
|
||||
^\.browserslistrc$
|
||||
^\.eslintrc\.yml$
|
||||
^\.yarnrc\.yml$
|
||||
|
||||
108
.eslintrc.yml
108
.eslintrc.yml
@@ -1,108 +0,0 @@
|
||||
root: true
|
||||
env:
|
||||
browser: true
|
||||
es6: true
|
||||
extends:
|
||||
- 'eslint:recommended'
|
||||
- 'plugin:@typescript-eslint/recommended'
|
||||
- 'plugin:jest/recommended'
|
||||
- 'prettier/@typescript-eslint'
|
||||
- 'plugin:prettier/recommended'
|
||||
- 'plugin:jest-dom/recommended'
|
||||
globals:
|
||||
Atomics: readonly
|
||||
SharedArrayBuffer: readonly
|
||||
parser: '@typescript-eslint/parser'
|
||||
parserOptions:
|
||||
ecmaVersion: 2018
|
||||
sourceType: module
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
- prettier
|
||||
- jest-dom
|
||||
- unicorn
|
||||
rules:
|
||||
"@typescript-eslint/explicit-function-return-type":
|
||||
- off
|
||||
"@typescript-eslint/no-explicit-any":
|
||||
- off
|
||||
"@typescript-eslint/explicit-module-boundary-types":
|
||||
- error
|
||||
|
||||
default-case:
|
||||
- error
|
||||
indent:
|
||||
- error
|
||||
- 2
|
||||
- SwitchCase: 1
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
quotes:
|
||||
- error
|
||||
- double
|
||||
- avoid-escape
|
||||
semi:
|
||||
- error
|
||||
- always
|
||||
newline-after-var:
|
||||
- error
|
||||
- always
|
||||
dot-location:
|
||||
- error
|
||||
- property
|
||||
|
||||
camelcase:
|
||||
# - error
|
||||
- "off"
|
||||
|
||||
unicorn/filename-case:
|
||||
- error
|
||||
- case: camelCase
|
||||
|
||||
"@typescript-eslint/array-type":
|
||||
- error
|
||||
- default: array-simple
|
||||
readonly: array-simple
|
||||
"@typescript-eslint/consistent-indexed-object-style":
|
||||
- error
|
||||
- index-signature
|
||||
|
||||
"@typescript-eslint/sort-type-union-intersection-members":
|
||||
- error
|
||||
|
||||
"@typescript-eslint/consistent-type-imports":
|
||||
- error
|
||||
"@typescript-eslint/naming-convention":
|
||||
- error
|
||||
|
||||
- selector: default
|
||||
format: [camelCase]
|
||||
|
||||
- selector: method
|
||||
modifiers: [private]
|
||||
format: [camelCase]
|
||||
leadingUnderscore: require
|
||||
- selector: method
|
||||
modifiers: [protected]
|
||||
format: [camelCase]
|
||||
leadingUnderscore: require
|
||||
|
||||
- selector: variable
|
||||
format: [camelCase]
|
||||
trailingUnderscore: forbid
|
||||
leadingUnderscore: forbid
|
||||
|
||||
- selector: parameter
|
||||
format: [camelCase]
|
||||
trailingUnderscore: allow
|
||||
leadingUnderscore: forbid
|
||||
|
||||
- selector: [enum, enumMember]
|
||||
format: [PascalCase]
|
||||
|
||||
- selector: typeLike
|
||||
format: [PascalCase]
|
||||
custom:
|
||||
regex: "(t|T)ype$"
|
||||
match: false
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,6 +1,4 @@
|
||||
/NEWS merge=union
|
||||
/inst/www/shared/shiny.js -merge -diff
|
||||
/inst/www/shared/shiny-*.js -merge -diff
|
||||
/inst/www/shared/shiny*.css -merge -diff
|
||||
*.min.js -merge -diff
|
||||
*.js.map -merge -diff
|
||||
|
||||
34
.github/workflows/rituals.yaml
vendored
34
.github/workflows/rituals.yaml
vendored
@@ -71,12 +71,13 @@ jobs:
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
pak::local_install_dev_deps(upgrade = TRUE)
|
||||
pak::pkg_install("sessioninfo")
|
||||
pak::pkg_install("devtools")
|
||||
|
||||
- name: Session info
|
||||
shell: Rscript {0}
|
||||
run: |
|
||||
options(width = 100)
|
||||
pak::pkg_install("sessioninfo")
|
||||
pkgs <- installed.packages()[, "Package"]
|
||||
sessioninfo::session_info(pkgs, include_base = TRUE)
|
||||
|
||||
@@ -99,7 +100,6 @@ jobs:
|
||||
|
||||
- name: Document
|
||||
run: |
|
||||
Rscript -e 'pak::pkg_install("devtools")'
|
||||
Rscript -e 'devtools::document()'
|
||||
git add man/\* NAMESPACE
|
||||
git commit -m 'Document (GitHub Actions)' || echo "No documentation changes to commit"
|
||||
@@ -123,24 +123,14 @@ jobs:
|
||||
key: ${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-
|
||||
|
||||
- name: Sync DESCRIPTION and package.json versions
|
||||
run: |
|
||||
Rscript -e 'pak::pkg_install("jsonlite")'
|
||||
Rscript -e 'pkg <- jsonlite::read_json("package.json", simplifyVector = TRUE)' \
|
||||
-e 'version <- as.list(read.dcf("DESCRIPTION")[1,])$Version' \
|
||||
-e 'pkg$version <- gsub("^(\\d+).(\\d+).(\\d+).(.+)$", "\\1.\\2.\\3-alpha.\\4", version)' \
|
||||
-e 'pkg$files <- as.list(pkg$files)' \
|
||||
-e 'jsonlite::write_json(pkg, path = "package.json", pretty = TRUE, auto_unbox = TRUE)'
|
||||
git add package.json && git commit -m 'sync package version (GitHub Actions)' || echo "No version changes to commit"
|
||||
- name: Build JS
|
||||
run: |
|
||||
tree srcts
|
||||
rm -r srcts/types
|
||||
cd srcts
|
||||
tree src
|
||||
yarn install --immutable && yarn build
|
||||
git add ./srcts/src && git commit -m 'yarn lint (GitHub Actions)' || echo "No yarn lint changes to commit"
|
||||
git add ./srcts/types && git commit -m 'yarn tsc (GitHub Actions)' || echo "No type definition changes to commit"
|
||||
git add ./inst && git commit -m 'yarn build (GitHub Actions)' || echo "No yarn build changes to commit"
|
||||
git add ./src && git commit -m 'yarn lint (GitHub Actions)' || echo "No yarn lint changes to commit"
|
||||
git add ./src_d && git commit -m 'yarn tsc (GitHub Actions)' || echo "No type definition changes to commit"
|
||||
git add ../inst && git commit -m 'yarn build (GitHub Actions)' || echo "No yarn build changes to commit"
|
||||
if [ -n "$(git status --porcelain)" ]
|
||||
then
|
||||
git status --porcelain
|
||||
@@ -155,16 +145,10 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Verify no un-pushed commits (MASTER)
|
||||
- name: Git Push (MASTER)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
# Can't push to a protected branch
|
||||
if [ -z "`git cherry`"]; then
|
||||
echo "Un-pushed commits:"
|
||||
git cherry -v
|
||||
echo "\nCan not push to a protected branch. Exiting"
|
||||
exit 1
|
||||
fi
|
||||
git push https://${{github.actor}}:${{secrets.GITHUB_TOKEN}}@github.com/${{github.repository}}.git HEAD:${{ github.ref }} || echo "No changes to push"
|
||||
|
||||
# Execute after pushing, as no updated files will be produced
|
||||
- name: Test TypeScript code
|
||||
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -11,18 +11,5 @@ README.html
|
||||
.*.Rnb.cached
|
||||
tools/yarn-error.log
|
||||
|
||||
# TypeScript / yarn
|
||||
node_modules/
|
||||
.cache
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
coverage/
|
||||
madge.svg
|
||||
|
||||
|
||||
# GHA remotes installation
|
||||
.github/r-depends.rds
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.6.0.9021
|
||||
Version: 1.6.0.9022
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com", comment = c(ORCID = "0000-0002-1576-2126")),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
@@ -79,7 +79,7 @@ Imports:
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
fontawesome (>= 0.2.1),
|
||||
htmltools (>= 0.5.1.9003),
|
||||
htmltools (>= 0.5.1.9006),
|
||||
R6 (>= 2.0),
|
||||
sourcetools,
|
||||
later (>= 1.0.0),
|
||||
|
||||
6
NEWS.md
6
NEWS.md
@@ -1,7 +1,3 @@
|
||||
Addresses #2521: Updated the list of TCP ports that will [be rejected](https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/port_util.cc)
|
||||
by default in runapp.R, adding 5060, 5061 and 6566. Added documentation describing the port range (3000:8000)
|
||||
and which ports are rejected.
|
||||
|
||||
shiny 1.6.0.9000
|
||||
================
|
||||
|
||||
@@ -11,8 +7,6 @@ shiny 1.6.0.9000
|
||||
|
||||
* The `format` and `locale` arguments to `sliderInput()` have been removed. They have been deprecated since 0.10.2.2 (released on 2014-12-08).
|
||||
|
||||
* Closed #3403: `insertTab()`'s `position` parameter now defaults to `"after"` instead of `"before"`. This has the benefit of allowing us to fix a bug in positioning when `target = NULL`, but has the drawback of changing the default behavior when `target` is not `NULL`. (#3404)
|
||||
|
||||
### New features and improvements
|
||||
|
||||
* Bootstrap 5 support. (#3410 and rstudio/bslib#304)
|
||||
|
||||
@@ -50,7 +50,7 @@ bootstrapPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
|
||||
# the tagList() contents to avoid breaking user code that makes assumptions
|
||||
# about the return value https://github.com/rstudio/shiny/issues/3235
|
||||
if (is_bs_theme(theme)) {
|
||||
args <- c(bootstrapLib(theme), args)
|
||||
args <- list(bootstrapLib(theme), args)
|
||||
ui <- do.call(tagList, args)
|
||||
} else {
|
||||
ui <- do.call(tagList, args)
|
||||
@@ -82,53 +82,30 @@ getLang <- function(ui) {
|
||||
#' @inheritParams bootstrapPage
|
||||
#' @export
|
||||
bootstrapLib <- function(theme = NULL) {
|
||||
tagFunction(function() {
|
||||
if (isRunning()) {
|
||||
# In the non-bslib case, return static HTML dependencies
|
||||
if (!is_bs_theme(theme)) {
|
||||
return(bootstrapDependency(theme))
|
||||
}
|
||||
# To support static rendering of Bootstrap version dependent markup (e.g.,
|
||||
# tabsetPanel()), setCurrentTheme() at the start of the render (since
|
||||
# bootstrapLib() comes first in bootstrapPage(), all other UI should then know
|
||||
# what version of Bootstrap is being used). Then restore state after render as
|
||||
# long as shiny isn't running (in that case, since setCurrentTheme() uses
|
||||
# shinyOptions(), state will be automatically be restored when the app exits)
|
||||
oldTheme <- NULL
|
||||
tagList(
|
||||
.renderHook = function(x) {
|
||||
oldTheme <<- getCurrentTheme()
|
||||
setCurrentTheme(theme)
|
||||
# For refreshing Bootstrap CSS when session$setCurrentTheme() happens
|
||||
if (isRunning()) registerThemeDependency(bs_theme_deps)
|
||||
attachDependencies(x, bslib::bs_theme_dependencies(theme))
|
||||
},
|
||||
.postRenderHook = function() {
|
||||
if (!isRunning()) setCurrentTheme(oldTheme)
|
||||
NULL
|
||||
}
|
||||
|
||||
# If we're not compiling Bootstrap Sass (from bslib), return the
|
||||
# static Bootstrap build.
|
||||
if (!is_bs_theme(theme)) {
|
||||
# We'll enter here if `theme` is the path to a .css file, like that
|
||||
# provided by `shinythemes::shinytheme("darkly")`.
|
||||
return(bootstrapDependency(theme))
|
||||
}
|
||||
|
||||
# Make bootstrap Sass available so other tagFunction()s (e.g.,
|
||||
# sliderInput() et al) can resolve their HTML dependencies at render time
|
||||
# using getCurrentTheme(). Note that we're making an implicit assumption
|
||||
# that this tagFunction() executes *before* all other tagFunction()s; but
|
||||
# that should be fine considering that, DOM tree order is preorder,
|
||||
# depth-first traversal, and at least in the bootstrapPage(theme) case, we
|
||||
# have control over the relative ordering.
|
||||
# https://dom.spec.whatwg.org/#concept-tree
|
||||
# https://stackoverflow.com/a/16113998/1583084
|
||||
#
|
||||
# Note also that since this is shinyOptions() (and not options()), the
|
||||
# option is automatically reset when the app (or session) exits
|
||||
if (isRunning()) {
|
||||
registerThemeDependency(bs_theme_deps)
|
||||
|
||||
} else {
|
||||
# Technically, this a potential issue (someone trying to execute/render
|
||||
# bootstrapLib outside of a Shiny app), but it seems that, in that case,
|
||||
# you likely have other problems, since sliderInput() et al. already assume
|
||||
# that Shiny is the one doing the rendering
|
||||
#warning(
|
||||
# "It appears `shiny::bootstrapLib()` was rendered outside of an Shiny ",
|
||||
# "application context, likely by calling `as.tags()`, `as.character()`, ",
|
||||
# "or `print()` directly on `bootstrapLib()` or UI components that may ",
|
||||
# "depend on it (e.g., `fluidPage()`, etc). For 'themable' UI components ",
|
||||
# "(e.g., `sliderInput()`, `selectInput()`, `dateInput()`, etc) to style ",
|
||||
# "themselves based on the Bootstrap theme, make sure `bootstrapLib()` is ",
|
||||
# "provided directly to the UI and that the UI is provided direction to ",
|
||||
# "`shinyApp()` (or `runApp()`)", call. = FALSE
|
||||
#)
|
||||
}
|
||||
|
||||
bslib::bs_theme_dependencies(theme)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
# This is defined outside of bootstrapLib() because registerThemeDependency()
|
||||
|
||||
@@ -229,11 +229,8 @@ ionRangeSliderDependencyCSS <- function(theme) {
|
||||
}
|
||||
|
||||
bslib::bs_dependency(
|
||||
input = list(
|
||||
list(accent = "$component-active-bg"),
|
||||
sass::sass_file(
|
||||
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
|
||||
)
|
||||
input = sass::sass_file(
|
||||
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
|
||||
),
|
||||
theme = theme,
|
||||
name = "ionRangeSlider",
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
#' }
|
||||
#' @export
|
||||
insertTab <- function(inputId, tab, target = NULL,
|
||||
position = c("after", "before"), select = FALSE,
|
||||
position = c("before", "after"), select = FALSE,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
bslib::nav_insert(
|
||||
inputId, tab, target,
|
||||
@@ -137,14 +137,14 @@ insertTab <- function(inputId, tab, target = NULL,
|
||||
#' @export
|
||||
prependTab <- function(inputId, tab, select = FALSE, menuName = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
bslib::nav_prepend(inputId, tab, menu_title = menuName, select = select, session = session)
|
||||
bslib::tab_prepend(inputId, tab, menu_title = menuName, select = select, session = session)
|
||||
}
|
||||
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
appendTab <- function(inputId, tab, select = FALSE, menuName = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
bslib::nav_append(inputId, tab, menu_title = menuName, select = select, session = session)
|
||||
bslib::tab_append(inputId, tab, menu_title = menuName, select = select, session = session)
|
||||
}
|
||||
|
||||
#' @rdname insertTab
|
||||
|
||||
@@ -178,15 +178,14 @@ modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"),
|
||||
if (!is.null(footer)) div(class = "modal-footer", footer)
|
||||
)
|
||||
),
|
||||
# jQuery plugin doesn't work in Bootstrap 5, but vanilla JS doesn't work in Bootstrap 4 :sob:
|
||||
tags$script(HTML(
|
||||
"if (window.bootstrap && !window.bootstrap.Modal.VERSION.match(/^4\\./)) {
|
||||
tags$script(
|
||||
"if (window.bootstrap) {
|
||||
var modal = new bootstrap.Modal(document.getElementById('shiny-modal'));
|
||||
modal.show();
|
||||
} else {
|
||||
$('#shiny-modal').modal().focus();
|
||||
}"
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ custom_print.ggplot <- function(x) {
|
||||
|
||||
# Infer alt text description from renderPlot() value
|
||||
# (currently just ggplot2 is supported)
|
||||
getAltText <- function(x, default = "Plot object") {
|
||||
getAltText <- function(x, default = "Plot Object") {
|
||||
# Since, inside renderPlot(), custom_print.ggplot()
|
||||
# overrides print.ggplot, this class indicates a ggplot()
|
||||
if (!inherits(x, "ggplot_build_gtable")) {
|
||||
|
||||
@@ -22,10 +22,7 @@
|
||||
#' @param port The TCP port that the application should listen on. If the
|
||||
#' `port` is not specified, and the `shiny.port` option is set (with
|
||||
#' `options(shiny.port = XX)`), then that port will be used. Otherwise,
|
||||
#' use a random port between 3000:8000, excluding ports that are blocked
|
||||
#' by Google Chrome for being considered unsafe: 3659, 4045, 5060,
|
||||
#' 5061, 6000, 6566, 6665:6669 and 6697. Up to twenty random
|
||||
#' ports will be tried.
|
||||
#' use a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only. This value of this parameter can also be a
|
||||
@@ -304,8 +301,7 @@ runApp <- function(appDir=getwd(),
|
||||
# Reject ports in this range that are considered unsafe by Chrome
|
||||
# http://superuser.com/questions/188058/which-ports-are-considered-unsafe-on-chrome
|
||||
# https://github.com/rstudio/shiny/issues/1784
|
||||
# https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/port_util.cc
|
||||
if (!port %in% c(3659, 4045, 5060, 5061, 6000, 6566, 6665:6669, 6697)) {
|
||||
if (!port %in% c(3659, 4045, 6000, 6665:6669, 6697)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
45
R/shiny.R
45
R/shiny.R
@@ -2126,51 +2126,6 @@ ShinySession <- R6Class(
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getUrl = function() {
|
||||
req <- self$request
|
||||
|
||||
url <-
|
||||
# Connect
|
||||
req[["HTTP_X_RSC_REQUEST"]] %||%
|
||||
req[["HTTP_RSTUDIO_CONNECT_APP_BASE_URL"]] %||%
|
||||
# ShinyApps.io
|
||||
if (!is.null(req[["HTTP_X_REDX_FRONTEND_NAME"]])) {
|
||||
paste0("https://", req[["HTTP_X_REDX_FRONTEND_NAME"]])
|
||||
}
|
||||
|
||||
if (is.null(url)) {
|
||||
forwarded_host <- req[["HTTP_X_FORWARDED_HOST"]]
|
||||
forwarded_port <- req[["HTTP_X_FORWARDED_PORT"]]
|
||||
|
||||
host <- if (!is.null(forwarded_host) && !is.null(forwarded_port)) {
|
||||
paste0(forwarded_host, ":", forwarded_port)
|
||||
} else {
|
||||
req[["HTTP_HOST"]] %||% paste0(req[["SERVER_NAME"]], ":", req[["SERVER_PORT"]])
|
||||
}
|
||||
|
||||
proto <- req[["HTTP_X_FORWARDED_PROTO"]] %||% req[["rook.url_scheme"]]
|
||||
|
||||
if (tolower(proto) == "http") {
|
||||
host <- sub(":80$", "", host)
|
||||
} else if (tolower(proto) == "https") {
|
||||
host <- sub(":443$", "", host)
|
||||
}
|
||||
|
||||
url <- paste0(
|
||||
proto,
|
||||
"://",
|
||||
host,
|
||||
req[["SCRIPT_NAME"]],
|
||||
req[["PATH_INFO"]]
|
||||
)
|
||||
}
|
||||
|
||||
# Strip existing querystring, if any
|
||||
url <- sub("\\?.*", "", url)
|
||||
|
||||
url
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1159,7 +1159,7 @@ reactiveStop <- function(message = "", class = NULL) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
|
||||
#' selectizeInput('in2', 'Select a state', choices = c("", state.name)),
|
||||
#' selectizeInput('in2', 'Select a state', choices = state.name),
|
||||
#' plotOutput('plot')
|
||||
#' )
|
||||
#'
|
||||
|
||||
@@ -58,7 +58,3 @@ We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.
|
||||
## License
|
||||
|
||||
The shiny package as a whole is licensed under the GPLv3. See the [LICENSE](LICENSE) file for more details.
|
||||
|
||||
## R version support
|
||||
|
||||
Shiny is supported on the latest release version of R, as well as the previous four minor release versions of R. For example, if the latest release R version is 4.1, then that version is supported, as well as 4.0, 3.6, 3.5, and 3.4.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -13,13 +13,6 @@
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
/* https://github.com/rstudio/shiny/issues/3443 */
|
||||
/* https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.irs *, .irs *:before, .irs *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.irs-line {
|
||||
@@ -179,9 +172,9 @@
|
||||
.irs--shiny .irs-bar {
|
||||
top: 25px;
|
||||
height: 8px;
|
||||
border-top: 1px solid #428bca;
|
||||
border-bottom: 1px solid #428bca;
|
||||
background: #428bca;
|
||||
border-top: 1px solid #337ab7;
|
||||
border-bottom: 1px solid #337ab7;
|
||||
background: #337ab7;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-bar--single {
|
||||
@@ -235,7 +228,7 @@
|
||||
color: #fff;
|
||||
text-shadow: none;
|
||||
padding: 1px 3px;
|
||||
background-color: #428bca;
|
||||
background-color: #337ab7;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
line-height: 1.333;
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
@include pos-r();
|
||||
-webkit-touch-callout: none;
|
||||
@include no-click();
|
||||
/* https://github.com/rstudio/shiny/issues/3443 */
|
||||
/* https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ */
|
||||
box-sizing: border-box;
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
&-line {
|
||||
@include pos-r();
|
||||
|
||||
@@ -37,7 +37,7 @@ $font-family: $font-family-base !default;
|
||||
// "High-level" coloring
|
||||
$bg: $body-bg !default;
|
||||
$fg: color-contrast($body-bg) !default;
|
||||
$accent: #428bca !default;
|
||||
$accent: $component-active-bg !default;
|
||||
|
||||
// "Low-level" coloring, borders, and fonts
|
||||
$line_bg: linear-gradient(to bottom, mix($bg, $fg, 87%) -50%, $bg 150%) !default;
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
/*! shiny 1.6.0.9021 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
(function(){var t="ws:";window.location.protocol==="https:"&&(t="wss:");var o=window.location.pathname;/\/$/.test(o)||(o+="/");o+="autoreload/";var n=new WebSocket(t+"//"+window.location.host+o);n.onmessage=function(a){a.data==="autoreload"&&window.location.reload()};})();
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LWF1dG9yZWxvYWQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qIGVzbGludC1kaXNhYmxlIHVuaWNvcm4vZmlsZW5hbWUtY2FzZSAqL1xudmFyIHByb3RvY29sID0gXCJ3czpcIjtcbmlmICh3aW5kb3cubG9jYXRpb24ucHJvdG9jb2wgPT09IFwiaHR0cHM6XCIpIHByb3RvY29sID0gXCJ3c3M6XCI7XG52YXIgZGVmYXVsdFBhdGggPSB3aW5kb3cubG9jYXRpb24ucGF0aG5hbWU7XG5pZiAoIS9cXC8kLy50ZXN0KGRlZmF1bHRQYXRoKSkgZGVmYXVsdFBhdGggKz0gXCIvXCI7XG5kZWZhdWx0UGF0aCArPSBcImF1dG9yZWxvYWQvXCI7XG52YXIgd3MgPSBuZXcgV2ViU29ja2V0KHByb3RvY29sICsgXCIvL1wiICsgd2luZG93LmxvY2F0aW9uLmhvc3QgKyBkZWZhdWx0UGF0aCk7XG5cbndzLm9ubWVzc2FnZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICBpZiAoZXZlbnQuZGF0YSA9PT0gXCJhdXRvcmVsb2FkXCIpIHtcbiAgICB3aW5kb3cubG9jYXRpb24ucmVsb2FkKCk7XG4gIH1cbn07XG5cbmV4cG9ydCB7fTsiXSwKICAibWFwcGluZ3MiOiAiO1lBQ0EsR0FBSSxHQUFXLE1BQ2YsQUFBSSxPQUFPLFNBQVMsV0FBYSxVQUFVLEdBQVcsUUFDdEQsR0FBSSxHQUFjLE9BQU8sU0FBUyxTQUNsQyxBQUFLLE1BQU0sS0FBSyxJQUFjLElBQWUsS0FDN0MsR0FBZSxjQUNmLEdBQUksR0FBSyxHQUFJLFdBQVUsRUFBVyxLQUFPLE9BQU8sU0FBUyxLQUFPLEdBRWhFLEVBQUcsVUFBWSxTQUFVLEVBQU8sQ0FDOUIsQUFBSSxFQUFNLE9BQVMsY0FDakIsT0FBTyxTQUFTIiwKICAibmFtZXMiOiBbXQp9Cg==
|
||||
(function() {
|
||||
var protocol = 'ws:';
|
||||
if (window.location.protocol === 'https:')
|
||||
protocol = 'wss:';
|
||||
|
||||
var defaultPath = window.location.pathname;
|
||||
if (!/\/$/.test(defaultPath))
|
||||
defaultPath += '/';
|
||||
defaultPath += 'autoreload/';
|
||||
|
||||
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
|
||||
ws.onmessage = function(event) {
|
||||
if (event.data === "autoreload") {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
6755
inst/www/shared/shiny-es5.js
Normal file
6755
inst/www/shared/shiny-es5.js
Normal file
File diff suppressed because it is too large
Load Diff
1
inst/www/shared/shiny-es5.js.map
Normal file
1
inst/www/shared/shiny-es5.js.map
Normal file
File diff suppressed because one or more lines are too long
4
inst/www/shared/shiny-es5.min.js
vendored
Normal file
4
inst/www/shared/shiny-es5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
inst/www/shared/shiny-es5.min.js.map
Normal file
1
inst/www/shared/shiny-es5.min.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,2 +1,87 @@
|
||||
/*! shiny 1.6.0.9021 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,"Courier New",monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:normal}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav{margin-bottom:0}#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
#showcase-well {
|
||||
border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
}
|
||||
|
||||
.shiny-code {
|
||||
background-color: white;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.shiny-code code {
|
||||
font-family: Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
.shiny-code-container {
|
||||
margin-top: 20px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.shiny-code-container h3 {
|
||||
display: inline;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.showcase-header {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.showcase-code-link {
|
||||
text-align: right;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#showcase-app-container {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#showcase-code-tabs pre {
|
||||
border: none;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
#showcase-code-tabs .nav,
|
||||
#showcase-code-tabs ul {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#showcase-app-code {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#showcase-code-tabs {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#showcase-code-tabs .tab-content {
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
border-width: 0px 1px 1px 1px;
|
||||
overflow:auto;
|
||||
-webkit-border-bottom-right-radius: 4px;
|
||||
-webkit-border-bottom-left-radius: 4px;
|
||||
-moz-border-radius-bottomright: 4px;
|
||||
-moz-border-radius-bottomleft: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
#showcase-code-position-toggle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#showcase-sxs-code {
|
||||
padding-top: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.showcase-code-license {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#showcase-code-content pre {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,8 @@
|
||||
/*! shiny 1.6.0.9021 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvc3JjL3V0aWxzL2V2YWwudHMiLCAiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LXRlc3Rtb2RlLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvL2VzYnVpbGQuZ2l0aHViLmlvL2NvbnRlbnQtdHlwZXMvI2RpcmVjdC1ldmFsXG4vL3RsL2RyO1xuLy8gKiBEaXJlY3QgdXNhZ2Ugb2YgYGV2YWwoXCJ4XCIpYCBpcyBiYWQgd2l0aCBidW5kbGVkIGNvZGUuXG4vLyAqIEluc3RlYWQsIHVzZSBpbmRpcmVjdCBjYWxscyB0byBgZXZhbGAgc3VjaCBhcyBgaW5kaXJlY3RFdmFsKFwieFwiKWBcbi8vICAgKiBFdmVuIGp1c3QgcmVuYW1pbmcgdGhlIGZ1bmN0aW9uIHdvcmtzIHdlbGwgZW5vdWdoLlxuLy8gPiBUaGlzIGlzIGtub3duIGFzIFwiaW5kaXJlY3QgZXZhbFwiIGJlY2F1c2UgZXZhbCBpcyBub3QgYmVpbmcgY2FsbGVkIGRpcmVjdGx5LCBhbmQgc28gZG9lcyBub3QgdHJpZ2dlciB0aGUgZ3JhbW1hdGljYWwgc3BlY2lhbCBjYXNlIGZvciBkaXJlY3QgZXZhbCBpbiB0aGUgSmF2YVNjcmlwdCBWTS4gWW91IGNhbiBjYWxsIGluZGlyZWN0IGV2YWwgdXNpbmcgYW55IHN5bnRheCBhdCBhbGwgZXhjZXB0IGZvciBhbiBleHByZXNzaW9uIG9mIHRoZSBleGFjdCBmb3JtIGV2YWwoJ3gnKS4gRm9yIGV4YW1wbGUsIHZhciBldmFsMiA9IGV2YWw7IGV2YWwyKCd4JykgYW5kIFtldmFsXVswXSgneCcpIGFuZCB3aW5kb3cuZXZhbCgneCcpIGFyZSBhbGwgaW5kaXJlY3QgZXZhbCBjYWxscy5cbi8vID4gV2hlbiB5b3UgdXNlIGluZGlyZWN0IGV2YWwsIHRoZSBjb2RlIGlzIGV2YWx1YXRlZCBpbiB0aGUgZ2xvYmFsIHNjb3BlIGluc3RlYWQgb2YgaW4gdGhlIGlubGluZSBzY29wZSBvZiB0aGUgY2FsbGVyLlxudmFyIGluZGlyZWN0RXZhbCA9IGV2YWw7XG5leHBvcnQgeyBpbmRpcmVjdEV2YWwgfTsiLCAiLyogZXNsaW50LWRpc2FibGUgdW5pY29ybi9maWxlbmFtZS1jYXNlICovXG5pbXBvcnQgeyBpbmRpcmVjdEV2YWwgfSBmcm9tIFwiLi4vc3JjL3V0aWxzL2V2YWxcIjsgLy8gTGlzdGVuIGZvciBtZXNzYWdlcyBmcm9tIHBhcmVudCBmcmFtZS4gVGhpcyBmaWxlIGlzIG9ubHkgYWRkZWQgd2hlbiB0aGVcbi8vIHNoaW55LnRlc3Rtb2RlIG9wdGlvbiBpcyBUUlVFLlxuXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcIm1lc3NhZ2VcIiwgZnVuY3Rpb24gKGUpIHtcbiAgdmFyIG1lc3NhZ2UgPSBlLmRhdGE7XG4gIGlmIChtZXNzYWdlLmNvZGUpIGluZGlyZWN0RXZhbChtZXNzYWdlLmNvZGUpO1xufSk7Il0sCiAgIm1hcHBpbmdzIjogIjtZQU9BLEdBQUksR0FBZSxLQ0huQixPQUFPLGlCQUFpQixVQUFXLFNBQVUsRUFBRyxDQUM5QyxHQUFJLEdBQVUsRUFBRSxLQUNoQixBQUFJLEVBQVEsTUFBTSxFQUFhLEVBQVEiLAogICJuYW1lcyI6IFtdCn0K
|
||||
// Listen for messages from parent frame. This file is only added when the
|
||||
// shiny.testmode option is TRUE.
|
||||
window.addEventListener("message", function(e) {
|
||||
var message = e.data;
|
||||
|
||||
if (message.code)
|
||||
eval(message.code);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
3
inst/www/shared/shiny.min.css
vendored
3
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
3
inst/www/shared/shiny.min.js
vendored
3
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
@@ -11,7 +11,7 @@ insertTab(
|
||||
inputId,
|
||||
tab,
|
||||
target = NULL,
|
||||
position = c("after", "before"),
|
||||
position = c("before", "after"),
|
||||
select = FALSE,
|
||||
session = getDefaultReactiveDomain()
|
||||
)
|
||||
|
||||
@@ -30,10 +30,7 @@ expression that produces a Shiny app object.
|
||||
\item{port}{The TCP port that the application should listen on. If the
|
||||
\code{port} is not specified, and the \code{shiny.port} option is set (with
|
||||
\code{options(shiny.port = XX)}), then that port will be used. Otherwise,
|
||||
use a random port between 3000:8000, excluding ports that are blocked
|
||||
by Google Chrome for being considered unsafe: 3659, 4045, 5060,
|
||||
5061, 6000, 6566, 6665:6669 and 6697. Up to twenty random
|
||||
ports will be tried.}
|
||||
use a random port.}
|
||||
|
||||
\item{launch.browser}{If true, the system's default web browser will be
|
||||
launched automatically after the app is started. Defaults to true in
|
||||
|
||||
@@ -19,10 +19,7 @@ list the available examples.}
|
||||
\item{port}{The TCP port that the application should listen on. If the
|
||||
\code{port} is not specified, and the \code{shiny.port} option is set (with
|
||||
\code{options(shiny.port = XX)}), then that port will be used. Otherwise,
|
||||
use a random port between 3000:8000, excluding ports that are blocked
|
||||
by Google Chrome for being considered unsafe: 3659, 4045, 5060,
|
||||
5061, 6000, 6566, 6665:6669 and 6697. Up to twenty random
|
||||
ports will be tried.}
|
||||
use a random port.}
|
||||
|
||||
\item{launch.browser}{If true, the system's default web browser will be
|
||||
launched automatically after the app is started. Defaults to true in
|
||||
|
||||
@@ -68,7 +68,7 @@ options(device.ask.default = FALSE)
|
||||
|
||||
ui <- fluidPage(
|
||||
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
|
||||
selectizeInput('in2', 'Select a state', choices = c("", state.name)),
|
||||
selectizeInput('in2', 'Select a state', choices = state.name),
|
||||
plotOutput('plot')
|
||||
)
|
||||
|
||||
|
||||
53
srcts/.eslintrc.yml
Normal file
53
srcts/.eslintrc.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
root: true
|
||||
env:
|
||||
browser: true
|
||||
es6: true
|
||||
extends:
|
||||
- 'eslint:recommended'
|
||||
- 'plugin:@typescript-eslint/recommended'
|
||||
- 'plugin:jest/recommended'
|
||||
- 'prettier/@typescript-eslint'
|
||||
- 'plugin:prettier/recommended'
|
||||
- 'plugin:jest-dom/recommended'
|
||||
globals:
|
||||
Atomics: readonly
|
||||
SharedArrayBuffer: readonly
|
||||
parser: '@typescript-eslint/parser'
|
||||
parserOptions:
|
||||
ecmaVersion: 2018
|
||||
sourceType: module
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
- prettier
|
||||
- jest-dom
|
||||
rules:
|
||||
"@typescript-eslint/explicit-function-return-type":
|
||||
- off
|
||||
"@typescript-eslint/no-explicit-any":
|
||||
- off
|
||||
"@typescript-eslint/explicit-module-boundary-types":
|
||||
- error
|
||||
camelcase:
|
||||
- error
|
||||
default-case:
|
||||
- error
|
||||
indent:
|
||||
- error
|
||||
- 2
|
||||
- SwitchCase: 1
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
quotes:
|
||||
- error
|
||||
- double
|
||||
- avoid-escape
|
||||
semi:
|
||||
- error
|
||||
- always
|
||||
newline-after-var:
|
||||
- error
|
||||
- always
|
||||
dot-location:
|
||||
- error
|
||||
- property
|
||||
10
srcts/.gitignore
vendored
Normal file
10
srcts/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules/
|
||||
.cache
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
coverage/
|
||||
madge.svg
|
||||
114
srcts/README.md
114
srcts/README.md
@@ -1,7 +1,5 @@
|
||||
# TypeScript build tools
|
||||
|
||||
All files will be described as if the working directory is the root folder of `rstudio/shiny`, not relative to this `README.md` file.
|
||||
|
||||
## First-time setup
|
||||
Shiny's TypeScript build tools use Node.js, along with [yarn](https://yarnpkg.com/) v2 to manage the JavaScript packages.
|
||||
|
||||
@@ -16,10 +14,9 @@ node --version
|
||||
yarn --version
|
||||
```
|
||||
|
||||
Once both are installed, run the following in the root repo directory to install the packages :
|
||||
Once both are installed, run the following in this directory (`srcts/`) to install the packages :
|
||||
|
||||
```bash
|
||||
# Sitting in `rstudio/shiny` repo
|
||||
yarn install
|
||||
```
|
||||
|
||||
@@ -50,12 +47,12 @@ If in the future you want to upgrade or add a package, run:
|
||||
yarn add --dev [packagename]
|
||||
```
|
||||
|
||||
This will automatically add the package to the dependencies in `package.json`, and it will also update the `yarn.lock` to reflect that change. If someone other than yourself does this, simply run `yarn` to update your local packages to match the new `package.json`.
|
||||
This will automatically add the package to the dependencies in `./package.json`, and it will also update the `./yarn.lock` to reflect that change. If someone other than yourself does this, simply run `yarn` to update your local packages to match the new `./package.json`.
|
||||
|
||||
## Upgrading packages
|
||||
Periodically, it's good to upgrade the packages to a recent version. There's two ways of doing this, depending on your intention:
|
||||
|
||||
1. Use `yarn up` to upgrade all dependencies to their latest version based on the version range specified in the package.json file (the `yarn.lock` file will be recreated as well. Yarn packages use [semantic versioning](https://yarnpkg.com/en/docs/dependency-versions), i.e. each version is writen with a maximum of 3 dot-separated numbers such that: `major.minor.patch`. For example in the version `3.1.4`, 3 is the major version number, 1 is the minor version number and 4 is the patch version number. Here are the most used operators (these appear before the version number):
|
||||
1. Use `yarn up` to upgrade all dependencies to their latest version based on the version range specified in the package.json file (the `./yarn.lock` file will be recreated as well. Yarn packages use [semantic versioning](https://yarnpkg.com/en/docs/dependency-versions), i.e. each version is writen with a maximum of 3 dot-separated numbers such that: `major.minor.patch`. For example in the version `3.1.4`, 3 is the major version number, 1 is the minor version number and 4 is the patch version number. Here are the most used operators (these appear before the version number):
|
||||
|
||||
- `~` is for upgrades that keep the minor version the same (assuming that was specified);
|
||||
|
||||
@@ -65,70 +62,16 @@ Periodically, it's good to upgrade the packages to a recent version. There's two
|
||||
|
||||
3. To see all outdated packages, run `yarn outdated`
|
||||
|
||||
# TypeScript
|
||||
|
||||
## Learn about TypeScript
|
||||
|
||||
The documentation by [TypeScript](https://www.typescriptlang.org/docs/) is a solid resource to know each and every bell and whistle. Most features have examples and convey the thoughts well.
|
||||
|
||||
[TypeScript Deep Dive](https://basarat.gitbook.io/typescript/) is an online `bookdown`-like approach to TypeScript by "Microsoft MVP for TypeScript", Basarat Ali Syed. In his book, he goes through many examples of what you "should do", not necessarily "what is possible" like the [TypeScript docs](https://www.typescriptlang.org/docs/).
|
||||
|
||||
## TypeScript StyleGuide
|
||||
|
||||
Using the style guid from [TypeScript Deep Dive / StyleGuide](https://basarat.gitbook.io/typescript/styleguide), we extend it to have the usage be more familiar to R developers and preexisting Shiny development. The goal is to produce consistent code that can be injested quickly.
|
||||
|
||||
|
||||
### StyleGuide
|
||||
|
||||
* `null` vs. `undefined`
|
||||
* Do not use `x === null` unless you truly mean it.
|
||||
* Safer to use _truthy_ or _falsey_ checks instead. Ex: `if (x) {}`
|
||||
* `type` vs `interface`
|
||||
* > Use `type` when you might need a union or intersection: `type Foo = number | { someProperty: number }`
|
||||
* > Use `interface` when you want extends or implements: `interface FooBar extends Foo { bar: string;}`
|
||||
* > Otherwise use whatever makes you happy that day.
|
||||
* Namespace
|
||||
* `PascalCase`
|
||||
* Ex: `Shiny`
|
||||
|
||||
### Enforced (by `eslint`) StyleGuide
|
||||
|
||||
* Variable
|
||||
* `camelCase`
|
||||
* Ex: `const hello = "world`
|
||||
* Class
|
||||
* `PascalCase`
|
||||
* Ex: `class InputBinding {}`
|
||||
* Type, Interface definitions:
|
||||
* `PascalCase`
|
||||
* Ex: `type BindingBase = {name: string}`
|
||||
* Ex: `interface ShinyEventMessage extends JQuery.Event {}`
|
||||
* Enum
|
||||
* `PascalCase`
|
||||
* (Currently unused)
|
||||
* Single vs. Double Quotes
|
||||
* While the JS community has decided on single quotes, R has decided on double quotes.
|
||||
* > When you can't use double quotes, try using back ticks (`).
|
||||
* Annotate Arrays as `Type[]`
|
||||
* Ex: `Foo[]` (vs `Array<Foo>`)
|
||||
* Annotate Records as `{[key: string]: valueType}`
|
||||
* Ex: `const x: {[key: string]: number} = {a: 4}`
|
||||
* Ex: Extend the unknown key definition with static keys: `const x: {known: string, [key: string]: number} = {known: "yes", a: 4}`
|
||||
* File Names
|
||||
* `camelCase` - Enforced by `eslint`
|
||||
|
||||
## Config files
|
||||
# Configure TypeScript
|
||||
|
||||
The JavaScript community likes to build many small, effective packages that do minimal work. The unfortunate side effect is needing a config file for everything.
|
||||
|
||||
All config files are located in the root folder to avoid opening two separate VS Code projects.
|
||||
## Config files
|
||||
|
||||
* `.browserslistrc`
|
||||
* Used with `browserslist` and `core-js` to determine which polyfills should be incorporated.
|
||||
* `.eslintrc.yml`
|
||||
* Used with `eslint` and `prettier` to determine how the TypeScript files should be formatted and which lint failures should cause warnings, errors, or be ignored.
|
||||
* `.madgerc`
|
||||
* Package used to determine if circular dependencies are found. `type` only imports are ignored as they are not included in the final bundle.
|
||||
* `.prettierrc.yml`
|
||||
* Used by `prettier` to know how to adjust code when a file is saved in VSCode or within `eslint`'s linting process.
|
||||
* `yarnrc.yml`
|
||||
@@ -139,13 +82,15 @@ All config files are located in the root folder to avoid opening two separate VS
|
||||
* `"useBuiltIns": "usage"` - `core-js` polyfills are only added as they are _used_.
|
||||
* `"corejs": "3.9"` - This number should match the installed `core-js` number.
|
||||
* `"ignore":["node_modules/core-js"]` - The `core-js` library is directly ignored to [avoid being processed by `babel`](https://github.com/zloirock/core-js/issues/743#issuecomment-571983318).
|
||||
* `esbuild.config.mjs`
|
||||
* Script that will build `shiny.js` and `shiny.min.js` with their sourcemaps
|
||||
* `jest.config.js`
|
||||
* Used to configure [`jest` testing](https://jestjs.io/)
|
||||
* `package.json`
|
||||
* Contains useful scripts that can be run by `yarn` via `yarn run SCRIPTNAME`.
|
||||
* The scripts described below are inteded for developer use. All other scripts are means to an end.
|
||||
* `yarn run watch` - Watch `srcts/src` for changes and rebuild the JavaScript files.
|
||||
* `yarn run build` - Build `shiny.js` and `shiny.min.js` in `inst/www/shared`. Both files will have a corresponding sourcemap
|
||||
* `yarn run watch` - Watch `./src` for changes and rebuild the JavaScript files.
|
||||
* `yarn run build` - Build `shiny.js` and `shiny.min.js` in `../inst/www/shared`. Both files will have a corresponding sourcemap
|
||||
* `yarn run lint` - Fix all TypeScript lints using [`eslint`](https://eslint.org/) and [`prettier`](https://prettier.io/)
|
||||
* `yarn run test` - Run all TypeScript tests
|
||||
* `tsconfig.json` -
|
||||
@@ -155,11 +100,13 @@ All config files are located in the root folder to avoid opening two separate VS
|
||||
* `preserveConstEnums: false` - Do no preserve enum values into the final code. (If true, produces bloat / unused code)
|
||||
* `isolatedModules: true` & `esModuleInterop: true` - Requested by `esbuild`. This [allows for `esbuild`](https://esbuild.github.io/content-types/#typescript) to safely compile the files in parallel
|
||||
|
||||
|
||||
|
||||
## Bundle TypeScript
|
||||
|
||||
[esbuild](https://esbuild.github.io/) is a build tool that (for Shiny's purposes) compiles the TypeScript into a single JavaScript file.
|
||||
|
||||
To run all build tasks, run:
|
||||
To run all build tasks, from within the `./srcts` directory, run:
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
@@ -173,25 +120,6 @@ yarn watch
|
||||
|
||||
Both JavaScript files will produce a sourcemap (`**.js.map`) that the browser will understand. This will help you debug Shiny's JavaScript code within the browser and point back to the original TypeScript files.
|
||||
|
||||
### Exported types
|
||||
|
||||
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `Shiny` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type defintion when the Type definitions are installed by external developers.
|
||||
|
||||
#### External development
|
||||
|
||||
When developing TypeScript projects that leverage Shiny, we recommend installing the Shiny TypeScript definitions to your package. To install the definitions, call
|
||||
|
||||
```bash
|
||||
yarn add https://github.com/rstudio/shiny\#v1.7.0
|
||||
```
|
||||
|
||||
, matching the GitHub tag to your current the Shiny CRAN release (ex: `v1.7.0`). If you are asked to select a version of `@types/jquery`, please select the closest version.
|
||||
|
||||
This will provide a global type defintion of `Shiny`, let your IDE know that `window.Shiny` is of type `Shiny`, and declare a globally available variable `Shiny` within your project. You **should not** need to import anything. Similar to `jQuery`, it should _Just Work_<sup>TM</sup>.
|
||||
|
||||
When loading your compiled file, it should be loaded after Shiny is loaded. If you are using an `htmlDependency()` to add your code to the page, your script will automatically be loaded after has been loaded.
|
||||
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
On push to the `master` branch or push to a Pull Request to the `master` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
|
||||
@@ -213,6 +141,13 @@ For this to work you must first install `xdotool` using your distribution's pack
|
||||
```bash
|
||||
find ../srcts/ | entr bash -c './node_modules/grunt/bin/grunt && xdotool search --onlyvisible --class Chrome windowfocus key ctrl+r'
|
||||
``` -->
|
||||
|
||||
|
||||
|
||||
# Development in VSCode
|
||||
|
||||
VSCode does not like to develop TypeScript with the configuration files in a subfolder. To leverage full VSCode capabilities, it is recommended to open the `./srcts` folder as the root folder of a VSCode project. This will enable VSCode to readily find all of the configuration files.
|
||||
|
||||
# Updating dependencies
|
||||
### `@types/jquery`
|
||||
|
||||
@@ -230,14 +165,3 @@ To update the version of `core-js`:
|
||||
|
||||
* Check if there is a newer version available by running `yarn outdated core-js`. (If there's no output, then you have the latest version.)
|
||||
* Run `yarn add --dev core-js --exact`.
|
||||
|
||||
|
||||
### External libraries
|
||||
|
||||
Shiny already has a handful of html dependencies that should NOT be bundled within `shiny.js`. To update the dependencies below, see the directions in in [`tools/README.md`](../tools).
|
||||
* `jquery` / `@types/jquery`
|
||||
* `bootstrap` / `@types/bootstrap`
|
||||
* Bootstrap is not being updated anymore. Only bootstrap 3.4 will be utilized within shiny.js. To use the latest bootstrap, see [`rstudio/bslib`](https://github.com/rstudio/bslib)
|
||||
* `bootstrap-datepicker` / `@types/bootstrap-datepicker`
|
||||
* `ion-rangeslider` / `@types/ion-rangeslider`
|
||||
* `selectize` / `@types/selectize`
|
||||
|
||||
@@ -85,12 +85,12 @@
|
||||
* √ Each _file_ will be pulled out as possible into smaller files in separate PRs
|
||||
* √ Convert `FileProcessor` to a true class definition
|
||||
* Break up `./utils` into many files
|
||||
* √ Remove any `: any` types
|
||||
* √ Make `@typescript-eslint/explicit-module-boundary-types` an error
|
||||
* √ Fix all `// eslint-disable-next-line no-prototype-builtins` lines
|
||||
* Remove any `: any` types
|
||||
* Make `@typescript-eslint/explicit-module-boundary-types` an error
|
||||
* Fix all `// eslint-disable-next-line no-prototype-builtins` lines
|
||||
* TypeScript other shiny files (ex: showcasemode)
|
||||
* √ Completely remove `parcel` from `./package.json` and only use `esbuild`
|
||||
* √ Delete 'shiny-es5' files
|
||||
* Completely remove `parcel` from `./package.json` and only use `esbuild`
|
||||
* Delete 'shiny-es5' files
|
||||
* Delete 'old' folder
|
||||
* _Uglify_ js files (like in previous Gruntfile.js)
|
||||
* datepicker
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import {
|
||||
build as esbuildBuild,
|
||||
BuildIncremental,
|
||||
BuildOptions,
|
||||
BuildResult,
|
||||
} from "esbuild";
|
||||
import readcontrol from "readcontrol";
|
||||
import process from "process";
|
||||
import { basename } from "path";
|
||||
|
||||
const outDir = "./inst/www/shared/";
|
||||
|
||||
type ShinyDesc = { version: string; package: string; license: string };
|
||||
const shinyDesc = readcontrol.readSync("./DESCRIPTION") as ShinyDesc;
|
||||
|
||||
const bannerTxt = [
|
||||
`/*! ${shinyDesc.package} ${shinyDesc.version}`,
|
||||
`(c) 2012-${new Date().getFullYear()} RStudio, PBC.`,
|
||||
`License: ${shinyDesc.license} */`,
|
||||
].join(" | ");
|
||||
const banner = {
|
||||
js: bannerTxt,
|
||||
css: bannerTxt,
|
||||
};
|
||||
|
||||
async function build(
|
||||
opts: BuildOptions
|
||||
): Promise<BuildIncremental | BuildResult> {
|
||||
const outFileNames = opts.outfile
|
||||
? [basename(opts.outfile)]
|
||||
: (opts.entryPoints as string[]).map((entry) => basename(entry));
|
||||
|
||||
const strSizes = outFileNames.map((outFileName) => outFileName.length);
|
||||
|
||||
strSizes.push("shiny.min.js".length);
|
||||
const strSize = Math.max(...strSizes);
|
||||
const printNames = outFileNames;
|
||||
|
||||
for (let i = 0; i < printNames.length; i++) {
|
||||
while (printNames[i].length < strSize) {
|
||||
printNames[i] = printNames[i] + " ";
|
||||
}
|
||||
}
|
||||
|
||||
const onRebuild = function (error?: string) {
|
||||
if (error) {
|
||||
console.error(printNames.join(", "), "watch build failed:\n", error);
|
||||
} else {
|
||||
printNames.map((printName) => {
|
||||
console.log("√ -", printName, "-", new Date().toJSON());
|
||||
});
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
let incremental = false;
|
||||
let watch: false | { onRebuild: (error, result) => void } = false;
|
||||
|
||||
if (process.argv.length >= 3 && process.argv[2] == "--watch") {
|
||||
incremental = true;
|
||||
watch = {
|
||||
onRebuild: onRebuild,
|
||||
};
|
||||
}
|
||||
|
||||
outFileNames.map((outFileName) => {
|
||||
console.log("Building " + outFileName);
|
||||
});
|
||||
return esbuildBuild({
|
||||
incremental: incremental,
|
||||
watch: watch,
|
||||
target: "es5",
|
||||
...opts,
|
||||
}).then((x) => {
|
||||
onRebuild();
|
||||
return x;
|
||||
});
|
||||
}
|
||||
|
||||
export { outDir, build, shinyDesc, banner };
|
||||
@@ -1,57 +0,0 @@
|
||||
// This build script must be executed from the root repo directory via
|
||||
// ```
|
||||
// yarn build
|
||||
// ```
|
||||
|
||||
import { build, outDir } from "./_build";
|
||||
import { readdir, unlink, writeFile } from "fs/promises";
|
||||
import globalsPlugin from "esbuild-plugin-globals";
|
||||
|
||||
const opts = {
|
||||
bundle: false,
|
||||
sourcemap: false,
|
||||
};
|
||||
|
||||
readdir(outDir + "datepicker/js/locales/").then(async (localeFiles) => {
|
||||
const requireFiles = localeFiles
|
||||
.map(function (filename) {
|
||||
return `require("./locales/${filename}");`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const tmpFile = outDir + "datepicker/js/temp.js";
|
||||
|
||||
await writeFile(
|
||||
tmpFile,
|
||||
`require("./bootstrap-datepicker.js");\n${requireFiles}`
|
||||
);
|
||||
|
||||
await build({
|
||||
...opts,
|
||||
plugins: [
|
||||
globalsPlugin({
|
||||
jquery: "window.jQuery",
|
||||
}),
|
||||
],
|
||||
bundle: true,
|
||||
entryPoints: [tmpFile],
|
||||
outfile: outDir + "datepicker/js/bootstrap-datepicker.min.js",
|
||||
minify: true,
|
||||
});
|
||||
// Clean up
|
||||
unlink(tmpFile);
|
||||
});
|
||||
|
||||
build({
|
||||
...opts,
|
||||
entryPoints: [outDir + "ionrangeslider/js/ion.rangeSlider.js"],
|
||||
outfile: outDir + "ionrangeslider/js/ion.rangeSlider.min.js",
|
||||
minify: true,
|
||||
});
|
||||
|
||||
build({
|
||||
...opts,
|
||||
entryPoints: [outDir + "selectize/accessibility/js/selectize-plugin-a11y.js"],
|
||||
outfile: outDir + "selectize/accessibility/js/selectize-plugin-a11y.min.js",
|
||||
minify: true,
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
// This build script must be executed from the root repo directory via
|
||||
// ```
|
||||
// yarn build
|
||||
// ```
|
||||
|
||||
// - TypeScript -----------------------------------------------------------
|
||||
|
||||
import { banner, build, outDir } from "./_build";
|
||||
import babelPlugin from "esbuild-plugin-babel";
|
||||
|
||||
build({
|
||||
bundle: true,
|
||||
sourcemap: "inline",
|
||||
minify: true,
|
||||
plugins: [babelPlugin()],
|
||||
banner: banner,
|
||||
entryPoints: [
|
||||
"srcts/extras/shiny-autoreload.ts",
|
||||
"srcts/extras/shiny-showcase.ts",
|
||||
"srcts/extras/shiny-testmode.ts",
|
||||
],
|
||||
outdir: outDir,
|
||||
});
|
||||
|
||||
// - Sass -----------------------------------------------------------
|
||||
|
||||
import autoprefixer from "autoprefixer";
|
||||
import postCssPlugin from "@deanc/esbuild-plugin-postcss";
|
||||
import sassPlugin from "esbuild-plugin-sass";
|
||||
|
||||
const sassOpts = {
|
||||
minify: true,
|
||||
banner: banner,
|
||||
plugins: [
|
||||
sassPlugin(),
|
||||
postCssPlugin({
|
||||
plugins: [autoprefixer],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
build({
|
||||
...sassOpts,
|
||||
entryPoints: ["srcts/extras/shiny-showcase.scss"],
|
||||
outfile: outDir + "shiny-showcase.css",
|
||||
});
|
||||
build({
|
||||
...sassOpts,
|
||||
entryPoints: [
|
||||
// Must keep shiny.scss within `inst` to be able to use as htmldependency
|
||||
outDir + "shiny_scss/shiny.scss",
|
||||
],
|
||||
outfile: outDir + "shiny.min.css",
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
// This build script must be executed from the root repo directory via
|
||||
// ```
|
||||
// yarn build
|
||||
// ```
|
||||
|
||||
import { banner, build, outDir, shinyDesc } from "./_build";
|
||||
import globalsPlugin from "esbuild-plugin-globals";
|
||||
import babelPlugin from "esbuild-plugin-babel";
|
||||
import type { BuildOptions } from "esbuild";
|
||||
|
||||
const opts: BuildOptions = {
|
||||
entryPoints: ["srcts/src/index.ts"],
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
plugins: [
|
||||
globalsPlugin({
|
||||
jquery: "window.jQuery",
|
||||
//// Loaded dynamically. MUST use `window.strftime` within code
|
||||
// strftime: "window.strftime",
|
||||
}),
|
||||
babelPlugin(),
|
||||
],
|
||||
define: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
"process.env.SHINY_VERSION": `"${shinyDesc.version}"`,
|
||||
},
|
||||
banner: banner,
|
||||
};
|
||||
|
||||
build({
|
||||
...opts,
|
||||
outfile: outDir + "shiny.js",
|
||||
});
|
||||
build({
|
||||
...opts,
|
||||
outfile: outDir + "shiny.min.js",
|
||||
minify: true,
|
||||
});
|
||||
67
srcts/esbuild.config.mjs
Normal file
67
srcts/esbuild.config.mjs
Normal file
@@ -0,0 +1,67 @@
|
||||
import esbuild from "esbuild";
|
||||
import babel from "esbuild-plugin-babel";
|
||||
import readcontrol from "readcontrol";
|
||||
import process from "process";
|
||||
import globalsPlugin from "esbuild-plugin-globals";
|
||||
|
||||
async function buildFile(
|
||||
fileName,
|
||||
extraOpts = {},
|
||||
strSize = "shiny.min.js".length
|
||||
) {
|
||||
let watch = process.argv.length >= 3 && process.argv[2] == "--watch";
|
||||
let incremental = false;
|
||||
|
||||
let printName = fileName;
|
||||
|
||||
while (printName.length < strSize) {
|
||||
printName = printName + " ";
|
||||
}
|
||||
|
||||
const onRebuild = function (error, result) {
|
||||
if (error) {
|
||||
console.error(printName, "watch build failed:\n", error);
|
||||
} else {
|
||||
console.log("√ -", printName, "-", new Date().toJSON());
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
if (watch) {
|
||||
incremental = true;
|
||||
watch = {
|
||||
onRebuild: onRebuild,
|
||||
};
|
||||
}
|
||||
|
||||
const outdir = "../inst/www/shared/";
|
||||
|
||||
console.log("Building " + fileName);
|
||||
await esbuild.build({
|
||||
outfile: outdir + fileName,
|
||||
entryPoints: ["src/index.ts"],
|
||||
bundle: true,
|
||||
incremental: incremental,
|
||||
watch: watch,
|
||||
plugins: [
|
||||
globalsPlugin({
|
||||
jquery: "window.jQuery",
|
||||
//// Loaded dynamically. MUST use `window.strftime` within code
|
||||
// strftime: "window.strftime",
|
||||
}),
|
||||
babel(),
|
||||
],
|
||||
target: "es5",
|
||||
sourcemap: true,
|
||||
define: {
|
||||
"process.env.SHINY_VERSION": `"${
|
||||
readcontrol.readSync("../DESCRIPTION").version
|
||||
}"`,
|
||||
},
|
||||
...extraOpts,
|
||||
});
|
||||
onRebuild();
|
||||
}
|
||||
|
||||
buildFile("shiny.js");
|
||||
buildFile("shiny.min.js", { minify: true });
|
||||
67
srcts/esbuild.external_libs.mjs
Normal file
67
srcts/esbuild.external_libs.mjs
Normal file
@@ -0,0 +1,67 @@
|
||||
import { readdirSync, unlinkSync, writeFileSync } from "fs";
|
||||
import esbuild from "esbuild";
|
||||
import globalsPlugin from "esbuild-plugin-globals";
|
||||
|
||||
// import process from "process";
|
||||
// let watch = process.argv.length >= 3 && process.argv[2] == "--watch";
|
||||
|
||||
let instdir = "../inst/";
|
||||
|
||||
let opts = {
|
||||
bundle: false,
|
||||
watch: false,
|
||||
target: "es5",
|
||||
sourcemap: false,
|
||||
};
|
||||
|
||||
console.log("Building datepicker");
|
||||
const localeFiles = readdirSync(instdir + "www/shared/datepicker/js/locales/");
|
||||
|
||||
let requireFiles = localeFiles
|
||||
.map(function (filename) {
|
||||
return `require("./locales/${filename}");`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
let tmpfile = instdir + "www/shared/datepicker/js/temp.js";
|
||||
|
||||
writeFileSync(
|
||||
tmpfile,
|
||||
`require("./bootstrap-datepicker.js");
|
||||
${requireFiles}`
|
||||
);
|
||||
await esbuild.build({
|
||||
...opts,
|
||||
plugins: [
|
||||
globalsPlugin({
|
||||
jquery: "window.jQuery",
|
||||
}),
|
||||
],
|
||||
bundle: true,
|
||||
entryPoints: [tmpfile],
|
||||
outfile: instdir + "www/shared/datepicker/js/bootstrap-datepicker.min.js",
|
||||
external: ["jquery"],
|
||||
minify: true,
|
||||
});
|
||||
// Clean up
|
||||
unlinkSync(tmpfile);
|
||||
|
||||
console.log("Building ionrangeslider");
|
||||
await esbuild.build({
|
||||
...opts,
|
||||
entryPoints: [instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.js"],
|
||||
outfile: instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.min.js",
|
||||
minify: true,
|
||||
});
|
||||
|
||||
console.log("Building selectize");
|
||||
await esbuild.build({
|
||||
...opts,
|
||||
entryPoints: [
|
||||
instdir + "www/shared/selectize/accessibility/js/selectize-plugin-a11y.js",
|
||||
],
|
||||
outfile:
|
||||
instdir +
|
||||
"www/shared/selectize/accessibility/js/selectize-plugin-a11y.min.js",
|
||||
minify: true,
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
// Type definitions for @types-rstudio/shiny
|
||||
// Project: Shiny <https://shiny.rstudio.com/>
|
||||
// Definitions by: RStudio <https://www.rstudio.com/>
|
||||
|
||||
import type { Shiny as RStudioShiny } from "../src/shiny/index";
|
||||
|
||||
declare global {
|
||||
// Tell Shiny variable globally exists
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const Shiny: RStudioShiny;
|
||||
|
||||
// Tell window.Shiny exists
|
||||
interface Window {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
Shiny: RStudioShiny;
|
||||
}
|
||||
|
||||
// Make `Shiny` a globally available type definition. (No need to import the type)
|
||||
type Shiny = RStudioShiny;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/* eslint-disable unicorn/filename-case */
|
||||
let protocol = "ws:";
|
||||
|
||||
if (window.location.protocol === "https:") protocol = "wss:";
|
||||
|
||||
let defaultPath = window.location.pathname;
|
||||
|
||||
if (!/\/$/.test(defaultPath)) defaultPath += "/";
|
||||
defaultPath += "autoreload/";
|
||||
|
||||
const ws = new WebSocket(protocol + "//" + window.location.host + defaultPath);
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
if (event.data === "autoreload") {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
export {};
|
||||
@@ -1,86 +0,0 @@
|
||||
#showcase-well {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.shiny-code {
|
||||
background-color: white;
|
||||
margin-bottom: 0;
|
||||
|
||||
code {
|
||||
font-family: Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.shiny-code-container {
|
||||
margin-top: 20px;
|
||||
clear: both;
|
||||
|
||||
h3 {
|
||||
display: inline;
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.showcase-header {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.showcase-code-link {
|
||||
text-align: right;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#showcase-app-container {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#showcase-code-tabs {
|
||||
pre {
|
||||
border: none;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.nav {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
margin-right: 15px;
|
||||
|
||||
.tab-content {
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
border-width: 0px 1px 1px 1px;
|
||||
overflow: auto;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
#showcase-app-code {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#showcase-code-position-toggle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#showcase-sxs-code {
|
||||
padding-top: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.showcase-code-license {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#showcase-code-content {
|
||||
pre {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
/* eslint-disable unicorn/filename-case */
|
||||
import "./globalShiny";
|
||||
|
||||
type ShowcaseSrcMessage = {
|
||||
srcref: number[];
|
||||
srcfile: string;
|
||||
};
|
||||
|
||||
const animateMs = 400;
|
||||
|
||||
// Given a DOM node and a column (count of characters), walk recursively
|
||||
// through the node's siblings counting characters until the given number
|
||||
// of characters have been found.
|
||||
//
|
||||
// If the given count is bigger than the number of characters contained by
|
||||
// the node and its siblings, returns a null node and the number of
|
||||
// characters found.
|
||||
function findTextColPoint(node: Node, col: number) {
|
||||
let cols = 0;
|
||||
|
||||
if (node.nodeType === 3) {
|
||||
const nchar = node.nodeValue.replace(/\n/g, "").length;
|
||||
|
||||
if (nchar >= col) {
|
||||
return { element: node, offset: col };
|
||||
} else {
|
||||
cols += nchar;
|
||||
}
|
||||
} else if (node.nodeType === 1 && node.firstChild) {
|
||||
const ret = findTextColPoint(node.firstChild, col);
|
||||
|
||||
if (ret.element !== null) {
|
||||
return ret;
|
||||
} else {
|
||||
cols += ret.offset;
|
||||
}
|
||||
}
|
||||
if (node.nextSibling) return findTextColPoint(node.nextSibling, col - cols);
|
||||
else return { element: null, offset: cols };
|
||||
}
|
||||
|
||||
// Returns an object indicating the element containing the given line and
|
||||
// column of text, and the offset into that element where the text was found.
|
||||
//
|
||||
// If the given line and column are not found, returns a null element and
|
||||
// the number of lines found.
|
||||
function findTextPoint(el: Node, line: number, col: number) {
|
||||
let newlines = 0;
|
||||
|
||||
for (let childId = 0; childId < el.childNodes.length; childId++) {
|
||||
const child = el.childNodes[childId];
|
||||
// If this is a text node, count the number of newlines it contains.
|
||||
|
||||
if (child.nodeType === 3) {
|
||||
// TEXT_NODE
|
||||
const newlinere = /\n/g;
|
||||
let match: ReturnType<RegExp["exec"]>;
|
||||
|
||||
while ((match = newlinere.exec(child.nodeValue)) !== null) {
|
||||
newlines++;
|
||||
// Found the desired line, now find the column.
|
||||
if (newlines === line) {
|
||||
return findTextColPoint(child, match.index + col + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If this is not a text node, descend recursively to see how many
|
||||
// lines it contains.
|
||||
else if (child.nodeType === 1) {
|
||||
// ELEMENT_NODE
|
||||
const ret = findTextPoint(child, line - newlines, col);
|
||||
|
||||
if (ret.element !== null) return ret;
|
||||
else newlines += ret.offset;
|
||||
}
|
||||
}
|
||||
return { element: null, offset: newlines };
|
||||
}
|
||||
|
||||
// Draw a highlight effect for the given source ref. srcref is assumed to be
|
||||
// an integer array of length 6, following the standard R format for source
|
||||
// refs.
|
||||
function highlightSrcref(
|
||||
srcref: ShowcaseSrcMessage["srcref"],
|
||||
srcfile: ShowcaseSrcMessage["srcfile"]
|
||||
) {
|
||||
// Check to see if the browser supports text ranges (IE8 doesn't)
|
||||
if (!document.createRange) return;
|
||||
|
||||
// Check to see if we already have a marker for this source ref
|
||||
let el = document.getElementById("srcref_" + srcref);
|
||||
|
||||
if (!el) {
|
||||
// We don't have a marker, create one
|
||||
el = document.createElement("span");
|
||||
el.id = "srcref_" + srcref;
|
||||
const ref = srcref;
|
||||
const code = document.getElementById(srcfile.replace(/\./g, "_") + "_code");
|
||||
// if there is no code file (might be a shiny file), quit early
|
||||
|
||||
if (!code) return;
|
||||
const start = findTextPoint(code, ref[0], ref[4]);
|
||||
const end = findTextPoint(code, ref[2], ref[5]);
|
||||
|
||||
// If the insertion point can't be found, bail out now
|
||||
if (start.element === null || end.element === null) return;
|
||||
|
||||
const range = document.createRange();
|
||||
// If the text points are inside different <SPAN>s, we may not be able to
|
||||
// surround them without breaking apart the elements to keep the DOM tree
|
||||
// intact. Just move the selection points to encompass the contents of
|
||||
// the SPANs.
|
||||
|
||||
if (
|
||||
start.element.parentNode.nodeName === "SPAN" &&
|
||||
start.element !== end.element
|
||||
) {
|
||||
range.setStartBefore(start.element.parentNode);
|
||||
} else {
|
||||
range.setStart(start.element, start.offset);
|
||||
}
|
||||
if (
|
||||
end.element.parentNode.nodeName === "SPAN" &&
|
||||
start.element !== end.element
|
||||
) {
|
||||
range.setEndAfter(end.element.parentNode);
|
||||
} else {
|
||||
range.setEnd(end.element, end.offset);
|
||||
}
|
||||
range.surroundContents(el);
|
||||
}
|
||||
// End any previous highlight before starting this one
|
||||
$(el).stop(true, true).effect("highlight", null, 1600);
|
||||
}
|
||||
|
||||
// If this is the main Shiny window, wire up our custom message handler.
|
||||
// TODO-barret, this should work
|
||||
|
||||
if (Shiny) {
|
||||
Shiny.addCustomMessageHandler(
|
||||
"showcase-src",
|
||||
function (message: ShowcaseSrcMessage) {
|
||||
if (message.srcref && message.srcfile) {
|
||||
highlightSrcref(message.srcref, message.srcfile);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let isCodeAbove = false;
|
||||
const setCodePosition = function (above: boolean, animate: boolean) {
|
||||
const animateCodeMs = animate ? animateMs : 1;
|
||||
|
||||
// set the source and targets for the tab move
|
||||
const newHostElement = above
|
||||
? document.getElementById("showcase-sxs-code")
|
||||
: document.getElementById("showcase-code-inline");
|
||||
const currentHostElement = above
|
||||
? document.getElementById("showcase-code-inline")
|
||||
: document.getElementById("showcase-sxs-code");
|
||||
|
||||
const metadataElement = document.getElementById("showcase-app-metadata");
|
||||
|
||||
if (metadataElement === null) {
|
||||
// if there's no app metadata, show and hide the entire well container
|
||||
// when the code changes position
|
||||
const wellElement = $("#showcase-well");
|
||||
|
||||
if (above) {
|
||||
wellElement.fadeOut(animateCodeMs);
|
||||
} else {
|
||||
wellElement.fadeIn(animateCodeMs);
|
||||
}
|
||||
}
|
||||
|
||||
// hide the new element before doing anything to it
|
||||
$(newHostElement).hide();
|
||||
$(currentHostElement).fadeOut(animateCodeMs, function () {
|
||||
const tabs = document.getElementById("showcase-code-tabs");
|
||||
|
||||
currentHostElement.removeChild(tabs);
|
||||
newHostElement.appendChild(tabs);
|
||||
|
||||
// remove or set the height of the code
|
||||
if (above) {
|
||||
setCodeHeightFromDocHeight();
|
||||
} else {
|
||||
document.getElementById("showcase-code-content").removeAttribute("style");
|
||||
}
|
||||
|
||||
$(newHostElement).fadeIn(animateCodeMs);
|
||||
if (!above) {
|
||||
// remove the applied width and zoom on the app container, and
|
||||
// scroll smoothly down to the code's new home
|
||||
document
|
||||
.getElementById("showcase-app-container")
|
||||
.removeAttribute("style");
|
||||
if (animate)
|
||||
$(document.body).animate({
|
||||
scrollTop: $(newHostElement).offset().top,
|
||||
});
|
||||
}
|
||||
// if there's a readme, move it either alongside the code or beneath
|
||||
// the app
|
||||
const readme = document.getElementById("readme-md");
|
||||
|
||||
if (readme !== null) {
|
||||
readme.parentElement.removeChild(readme);
|
||||
if (above) {
|
||||
currentHostElement.appendChild(readme);
|
||||
$(currentHostElement).fadeIn(animateCodeMs);
|
||||
} else
|
||||
document.getElementById("showcase-app-metadata").appendChild(readme);
|
||||
}
|
||||
|
||||
// change the text on the toggle button to reflect the new state
|
||||
document.getElementById("showcase-code-position-toggle").innerHTML = above
|
||||
? '<i class="fa fa-level-down"></i> show below'
|
||||
: '<i class="fa fa-level-up"></i> show with app';
|
||||
});
|
||||
if (above) {
|
||||
$(document.body).animate({ scrollTop: 0 }, animateCodeMs);
|
||||
}
|
||||
isCodeAbove = above;
|
||||
setAppCodeSxsWidths(above && animate);
|
||||
$(window).trigger("resize");
|
||||
};
|
||||
|
||||
function setAppCodeSxsWidths(animate: boolean) {
|
||||
const appTargetWidth = 960;
|
||||
let appWidth = appTargetWidth;
|
||||
let zoom = 1.0;
|
||||
const totalWidth = document.getElementById("showcase-app-code").offsetWidth;
|
||||
|
||||
if (totalWidth / 2 > appTargetWidth) {
|
||||
// If the app can use only half the available space and still meet its
|
||||
// target, take half the available space.
|
||||
appWidth = totalWidth / 2;
|
||||
} else if (totalWidth * 0.66 > appTargetWidth) {
|
||||
// If the app can meet its target by taking up more space (up to 66%
|
||||
// of its container), take up more space.
|
||||
appWidth = 960;
|
||||
} else {
|
||||
// The space is too narrow for the app and code to live side-by-side
|
||||
// in a friendly way. Keep the app at 2/3 of the space but scale it.
|
||||
appWidth = totalWidth * 0.66;
|
||||
zoom = appWidth / appTargetWidth;
|
||||
}
|
||||
const app = document.getElementById("showcase-app-container");
|
||||
|
||||
$(app).animate(
|
||||
{
|
||||
width: appWidth + "px",
|
||||
zoom: zoom * 100 + "%",
|
||||
},
|
||||
animate ? animateMs : 0
|
||||
);
|
||||
}
|
||||
|
||||
const toggleCodePosition = function () {
|
||||
setCodePosition(!isCodeAbove, true);
|
||||
};
|
||||
|
||||
// if the browser is sized to wider than 1350px, show the code next to the
|
||||
// app by default
|
||||
const setInitialCodePosition = function () {
|
||||
if (document.body.offsetWidth > 1350) {
|
||||
setCodePosition(true, false);
|
||||
}
|
||||
};
|
||||
|
||||
// make the code scrollable to about the height of the browser, less space
|
||||
// for the tabs
|
||||
function setCodeHeightFromDocHeight() {
|
||||
document.getElementById("showcase-code-content").style.height =
|
||||
$(window).height() + "px";
|
||||
}
|
||||
|
||||
// if there's a block of markdown content, render it to HTML
|
||||
function renderMarkdown() {
|
||||
const mdContent = document.getElementById("showcase-markdown-content");
|
||||
|
||||
if (mdContent !== null) {
|
||||
// IE8 puts the content of <script> tags into innerHTML but
|
||||
// not innerText
|
||||
const content = mdContent.innerText || mdContent.innerHTML;
|
||||
|
||||
const showdownConverter = (window as any).Showdown
|
||||
.converter as showdown.ConverterStatic;
|
||||
|
||||
document.getElementById("readme-md").innerHTML =
|
||||
new showdownConverter().makeHtml(content);
|
||||
}
|
||||
}
|
||||
|
||||
$(window).resize(function () {
|
||||
if (isCodeAbove) {
|
||||
setAppCodeSxsWidths(false);
|
||||
setCodeHeightFromDocHeight();
|
||||
}
|
||||
});
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
toggleCodePosition: () => void;
|
||||
}
|
||||
}
|
||||
window.toggleCodePosition = toggleCodePosition;
|
||||
|
||||
$(window).on("load", setInitialCodePosition);
|
||||
$(window).on("load", renderMarkdown);
|
||||
|
||||
if (window.hljs) window.hljs.initHighlightingOnLoad();
|
||||
|
||||
export {};
|
||||
@@ -1,10 +0,0 @@
|
||||
/* eslint-disable unicorn/filename-case */
|
||||
import { indirectEval } from "../src/utils/eval";
|
||||
|
||||
// Listen for messages from parent frame. This file is only added when the
|
||||
// shiny.testmode option is TRUE.
|
||||
window.addEventListener("message", function (e: { data: { code: string } }) {
|
||||
const message = e.data;
|
||||
|
||||
if (message.code) indirectEval(message.code);
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
(function() {
|
||||
var protocol = 'ws:';
|
||||
if (window.location.protocol === 'https:')
|
||||
protocol = 'wss:';
|
||||
|
||||
var defaultPath = window.location.pathname;
|
||||
if (!/\/$/.test(defaultPath))
|
||||
defaultPath += '/';
|
||||
defaultPath += 'autoreload/';
|
||||
|
||||
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
|
||||
ws.onmessage = function(event) {
|
||||
if (event.data === "autoreload") {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,87 +0,0 @@
|
||||
#showcase-well {
|
||||
border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
}
|
||||
|
||||
.shiny-code {
|
||||
background-color: white;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.shiny-code code {
|
||||
font-family: Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
.shiny-code-container {
|
||||
margin-top: 20px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.shiny-code-container h3 {
|
||||
display: inline;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.showcase-header {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.showcase-code-link {
|
||||
text-align: right;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#showcase-app-container {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#showcase-code-tabs pre {
|
||||
border: none;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
#showcase-code-tabs .nav,
|
||||
#showcase-code-tabs ul {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#showcase-app-code {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#showcase-code-tabs {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#showcase-code-tabs .tab-content {
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
border-width: 0px 1px 1px 1px;
|
||||
overflow:auto;
|
||||
-webkit-border-bottom-right-radius: 4px;
|
||||
-webkit-border-bottom-left-radius: 4px;
|
||||
-moz-border-radius-bottomright: 4px;
|
||||
-moz-border-radius-bottomleft: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
#showcase-code-position-toggle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#showcase-sxs-code {
|
||||
padding-top: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.showcase-code-license {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#showcase-code-content pre {
|
||||
background-color: white;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user