├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── srcjs ├── config │ ├── misc.json │ ├── output_path.json │ ├── entry_points.json │ ├── externals.json │ └── loaders.json ├── index.js └── exts │ └── WinBox.js ├── LICENSE ├── .gitignore ├── man ├── figures │ └── winbox.png ├── html_dependency_winbox.Rd ├── wbControls.Rd ├── WinBox.Rd └── wbOptions.Rd ├── webpack.prod.js ├── webpack.dev.js ├── .Rbuildignore ├── R ├── utils.R └── WinBox.R ├── inst ├── examples │ ├── basic.R │ ├── controls.R │ ├── default.R │ ├── htmlwidgets2.R │ ├── ggplot.R │ ├── modal.R │ ├── htmlwidgets.R │ ├── options.R │ ├── close.R │ └── options2.R └── packer │ └── WinBox.js ├── NAMESPACE ├── DESCRIPTION ├── package.json ├── LICENSE.md ├── README.md └── webpack.common.js /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /srcjs/config/misc.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /srcjs/index.js: -------------------------------------------------------------------------------- 1 | import './exts/WinBox.js'; 2 | -------------------------------------------------------------------------------- /srcjs/config/output_path.json: -------------------------------------------------------------------------------- 1 | "./inst/packer" 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022 2 | COPYRIGHT HOLDER: Victor PERRIER 3 | -------------------------------------------------------------------------------- /srcjs/config/entry_points.json: -------------------------------------------------------------------------------- 1 | { 2 | "WinBox": "./srcjs/exts/WinBox.js" 3 | } 4 | -------------------------------------------------------------------------------- /srcjs/config/externals.json: -------------------------------------------------------------------------------- 1 | { 2 | "shiny": "Shiny", 3 | "jquery": "jQuery" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | node_modules 6 | *.Rproj 7 | -------------------------------------------------------------------------------- /man/figures/winbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamRs/shinywb/HEAD/man/figures/winbox.png -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'production', 6 | }); 7 | -------------------------------------------------------------------------------- /srcjs/config/loaders.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test": "\\.css$", 4 | "use": [ 5 | "style-loader", 6 | "css-loader" 7 | ] 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'inline-source-map' 7 | }); 8 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^shinywb\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^srcjs$ 4 | ^node_modules$ 5 | ^package\.json$ 6 | ^package-lock\.json$ 7 | ^webpack\.dev\.js$ 8 | ^webpack\.prod\.js$ 9 | ^webpack\.common\.js$ 10 | ^LICENSE\.md$ 11 | ^\.github$ 12 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | 2 | genId <- function(bytes = 12) { 3 | paste(format(as.hexmode(sample(256, bytes, replace = TRUE) - 1), width = 2), collapse = "") 4 | } 5 | 6 | dropNulls <- function(x) { 7 | x[!vapply(x, is.null, FUN.VALUE = logical(1))] 8 | } 9 | 10 | list1 <- function(x) { 11 | if (length(x) == 1) { 12 | list(x) 13 | } 14 | else { 15 | x 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /inst/examples/basic.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | 5 | ui <- fluidPage( 6 | html_dependency_winbox() 7 | ) 8 | 9 | server <- function(input, output, session) { 10 | 11 | WinBox( 12 | title = "WinBox", 13 | ui = tagList( 14 | tags$h3("Hello from WinBox!") 15 | ) 16 | ) 17 | 18 | } 19 | 20 | if (interactive()) 21 | shinyApp(ui, server) -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(WinBox) 4 | export(closeWinBox) 5 | export(html_dependency_winbox) 6 | export(wbControls) 7 | export(wbOptions) 8 | importFrom(htmltools,css) 9 | importFrom(htmltools,doRenderTags) 10 | importFrom(htmltools,htmlDependency) 11 | importFrom(htmltools,tags) 12 | importFrom(shiny,getDefaultReactiveDomain) 13 | importFrom(utils,packageVersion) 14 | -------------------------------------------------------------------------------- /inst/examples/controls.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | 5 | ui <- fluidPage( 6 | html_dependency_winbox(), 7 | actionButton(inputId = "show", label = "Show WinBox") 8 | ) 9 | 10 | server <- function(input, output, session) { 11 | 12 | observeEvent(input$show, { 13 | WinBox( 14 | title = "Custom controls", 15 | ui = tagList( 16 | tags$h2("Hello from WinBox!"), 17 | "Text content of winbox." 18 | ), 19 | controls = wbControls( 20 | min = FALSE, 21 | max = FALSE, 22 | resize = FALSE 23 | ) 24 | ) 25 | }) 26 | 27 | } 28 | 29 | if (interactive()) 30 | shinyApp(ui, server) 31 | -------------------------------------------------------------------------------- /inst/examples/default.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | 5 | ui <- fluidPage( 6 | html_dependency_winbox(), 7 | actionButton(inputId = "show", label = "Show WinBox"), 8 | verbatimTextOutput("res") 9 | ) 10 | 11 | server <- function(input, output, session) { 12 | 13 | observeEvent(input$show, { 14 | WinBox( 15 | title = "WinBox window", 16 | ui = tagList( 17 | tags$h2("Hello from WinBox!"), 18 | "Text content of winbox.", 19 | selectInput("month", "Select a month:", month.name) 20 | ) 21 | ) 22 | }) 23 | 24 | output$res <- renderPrint(input$month) 25 | 26 | } 27 | 28 | if (interactive()) 29 | shinyApp(ui, server) 30 | -------------------------------------------------------------------------------- /inst/examples/htmlwidgets2.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | library(reactable) 5 | library(ggplot2) 6 | data("midwest", package = "ggplot2") 7 | 8 | ui <- fluidPage( 9 | html_dependency_winbox(), 10 | actionButton(inputId = "show", label = "Show WinBox") 11 | ) 12 | 13 | server <- function(input, output, session) { 14 | 15 | observeEvent(input$show, { 16 | inputId <- paste0("var", input$show) 17 | WinBox( 18 | title = "With an htmlwidget", 19 | ui = tagList( 20 | tags$h3("Midwest demographics"), 21 | renderReactable({ 22 | reactable(data = midwest, bordered = TRUE, striped = TRUE) 23 | }) 24 | ), 25 | options = wbOptions(height = 630) 26 | ) 27 | }) 28 | 29 | } 30 | 31 | if (interactive()) 32 | shinyApp(ui, server) 33 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: shinywb 2 | Title: 'WinBox' Window for 'shiny' Applications 3 | Version: 0.0.0.9200 4 | Authors@R: c( 5 | person("Victor", "Perrier", , "victor.perrier@dreamrs.fr", role = c("aut", "cre")), 6 | person("Fanny", "Meyer", role = "aut"), 7 | person(given = "Thomas", 8 | family = "Wilkerling", 9 | role = "cph", 10 | comment = "WinBox JavaScript library ") 11 | ) 12 | Description: Interface to 'WinBox' 'JavaScript' library to use in 'shiny' applications. 13 | 'WinBox' is a modern HTML5 window manager for the web, . 14 | License: MIT + file LICENSE 15 | Encoding: UTF-8 16 | Roxygen: list(markdown = TRUE) 17 | RoxygenNote: 7.3.1 18 | Imports: 19 | htmltools, 20 | shiny 21 | -------------------------------------------------------------------------------- /inst/examples/ggplot.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | library(ggplot2) 5 | data("economics", package = "ggplot2") 6 | 7 | ui <- fluidPage( 8 | html_dependency_winbox(), 9 | actionButton(inputId = "show", label = "Show WinBox") 10 | ) 11 | 12 | server <- function(input, output, session) { 13 | 14 | observeEvent(input$show, { 15 | inputId <- paste0("var", input$show) 16 | WinBox( 17 | title = "With ggplot2", 18 | ui = tagList( 19 | tags$h3("Economic chart"), 20 | selectInput(inputId, "Select a variable:", names(economics)[-1]), 21 | renderPlot({ 22 | ggplot(economics) + 23 | aes(x = date, y = !!sym(input[[inputId]])) + 24 | geom_line() 25 | }) 26 | ), 27 | options = wbOptions(height = 630) 28 | ) 29 | }) 30 | 31 | } 32 | 33 | if (interactive()) 34 | shinyApp(ui, server) 35 | -------------------------------------------------------------------------------- /inst/examples/modal.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | 5 | ui <- fluidPage( 6 | html_dependency_winbox(), 7 | actionButton(inputId = "show", label = "Show WinBox as modal") 8 | ) 9 | 10 | server <- function(input, output, session) { 11 | 12 | 13 | observeEvent(input$show, { 14 | WinBox( 15 | title = "WinBox as modal", 16 | ui = tagList( 17 | tags$h2("Hello from WinBox!"), 18 | "Text content of winbox.", 19 | actionButton("show2", "Open a normal winbox") 20 | ), 21 | options = wbOptions(modal = TRUE) 22 | ) 23 | }) 24 | 25 | observeEvent(input$show2, { 26 | WinBox( 27 | title = "Normal WinBox", 28 | ui = tagList( 29 | tags$h2("Hello from WinBox!"), 30 | "Text content of winbox." 31 | ), 32 | options = wbOptions(background = "firebrick") 33 | ) 34 | }) 35 | 36 | } 37 | 38 | if (interactive()) 39 | shinyApp(ui, server) 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "winboxr", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "none": "webpack --config webpack.dev.js --mode=none", 9 | "development": "webpack --config webpack.dev.js", 10 | "production": "webpack --config webpack.prod.js", 11 | "watch": "webpack --config webpack.dev.js -d --watch" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/pvictor/winboxr.git" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/pvictor/winboxr/issues" 22 | }, 23 | "homepage": "https://github.com/pvictor/winboxr#readme", 24 | "devDependencies": { 25 | "css-loader": "^6.7.1", 26 | "style-loader": "^3.3.1", 27 | "webpack": "^5.72.0", 28 | "webpack-cli": "^4.9.2", 29 | "webpack-merge": "^5.8.0", 30 | "winbox": "^0.2.82" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /man/html_dependency_winbox.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/WinBox.R 3 | \name{html_dependency_winbox} 4 | \alias{html_dependency_winbox} 5 | \title{Winbox JavaScript Dependencies} 6 | \usage{ 7 | html_dependency_winbox( 8 | css_rules = "body{min-height:100vh}.winbox.modal{display:block;overflow:unset}" 9 | ) 10 | } 11 | \arguments{ 12 | \item{css_rules}{CSS rules to be included in a \code{style} tag in the document head. 13 | By default it set a \code{min-height} to the body element.} 14 | } 15 | \description{ 16 | Include dependencies, place anywhere in the shiny UI. 17 | } 18 | \examples{ 19 | 20 | library(shiny) 21 | library(shinywb) 22 | 23 | ui <- fluidPage( 24 | html_dependency_winbox() 25 | ) 26 | 27 | server <- function(input, output, session) { 28 | 29 | WinBox( 30 | title = "WinBox", 31 | ui = tagList( 32 | tags$h3("Hello from WinBox!") 33 | ) 34 | ) 35 | 36 | } 37 | 38 | if (interactive()) 39 | shinyApp(ui, server) 40 | } 41 | -------------------------------------------------------------------------------- /inst/examples/htmlwidgets.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | library(apexcharter) 5 | library(ggplot2) 6 | data("economics", package = "ggplot2") 7 | 8 | ui <- fluidPage( 9 | html_dependency_winbox(), 10 | actionButton(inputId = "show", label = "Show WinBox") 11 | ) 12 | 13 | server <- function(input, output, session) { 14 | 15 | observeEvent(input$show, { 16 | inputId <- paste0("var", input$show) 17 | WinBox( 18 | title = "With an htmlwidget", 19 | ui = tagList( 20 | tags$h3("Economic chart"), 21 | selectInput(inputId, "Select a variable:", names(economics)[-1]), 22 | renderApexchart({ 23 | apex( 24 | data = economics, 25 | type = "line", 26 | mapping = aes(x = date, y = !!sym(input[[inputId]])) 27 | ) %>% 28 | ax_stroke(width = 1) 29 | }) 30 | ), 31 | options = wbOptions(height = 630) 32 | ) 33 | }) 34 | 35 | } 36 | 37 | if (interactive()) 38 | shinyApp(ui, server) 39 | -------------------------------------------------------------------------------- /inst/examples/options.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | 5 | ui <- fluidPage( 6 | html_dependency_winbox(), 7 | actionButton(inputId = "show1", label = "Show WinBox"), 8 | actionButton(inputId = "show2", label = "Show WinBox as modal") 9 | ) 10 | 11 | server <- function(input, output, session) { 12 | 13 | observeEvent(input$show1, { 14 | WinBox( 15 | title = "Custom background color and border", 16 | ui = tagList( 17 | tags$h2("Hello from WinBox!"), 18 | "Text content of winbox." 19 | ), 20 | options = wbOptions( 21 | background = "#112446", 22 | border = "0.5em", 23 | x = "center", 24 | y = "center", 25 | width = "50%", 26 | height = "50%" 27 | ) 28 | ) 29 | }) 30 | 31 | observeEvent(input$show2, { 32 | WinBox( 33 | title = "WinBox as modal", 34 | ui = tagList( 35 | tags$h2("Hello from WinBox!"), 36 | "Text content of winbox." 37 | ), 38 | options = wbOptions(modal = TRUE) 39 | ) 40 | }) 41 | 42 | } 43 | 44 | if (interactive()) 45 | shinyApp(ui, server) 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2022 Victor PERRIER 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /inst/examples/close.R: -------------------------------------------------------------------------------- 1 | 2 | library(shiny) 3 | library(shinywb) 4 | 5 | ui <- fluidPage( 6 | tags$style("body {min-height: 100vh;}"), 7 | html_dependency_winbox(), 8 | tags$p("With an ID:"), 9 | actionButton(inputId = "show", label = "Show WinBox with ID"), 10 | actionButton(inputId = "close", label = "Close WinBox with ID"), 11 | tags$p("Without ID, close last one:"), 12 | actionButton(inputId = "show_mult", label = "Show multiple WinBox"), 13 | actionButton(inputId = "close_last", label = "Close last WinBox") 14 | ) 15 | 16 | server <- function(input, output, session) { 17 | 18 | observeEvent(input$show, { 19 | WinBox( 20 | id = "mywinbox", 21 | title = "WinBox window", 22 | ui = tags$div( 23 | style = "padding: 10px;", 24 | tags$h2("Hello from WinBox!"), 25 | "Text content of winbox." 26 | ) 27 | ) 28 | }) 29 | 30 | observeEvent(input$close, closeWinBox("mywinbox")) 31 | 32 | observeEvent(input$show_mult, { 33 | WinBox( 34 | title = paste("WinBox window", input$show_mult), 35 | ui = tags$div( 36 | style = "padding: 10px;", 37 | tags$h2("Hello from WinBox!"), 38 | "Text content of winbox." 39 | ) 40 | ) 41 | }) 42 | observeEvent(input$close_last, closeWinBox(NULL)) 43 | 44 | } 45 | 46 | shinyApp(ui, server) 47 | -------------------------------------------------------------------------------- /inst/examples/options2.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinywb) 3 | library(htmltools) 4 | library(phosphoricons) 5 | 6 | 7 | ui <- fluidPage( 8 | html_dependency_winbox(), 9 | actionButton(inputId = "show", label = "Show one or more WinBox"), 10 | radioButtons( 11 | inputId = "background", 12 | label = "Background color:", 13 | choices = c("forestgreen", "firebrick", "steelblue", "goldenrod") 14 | ), 15 | checkboxInput( 16 | inputId = "modal", 17 | label = "Shows the window as modal", 18 | value = FALSE 19 | ), 20 | checkboxInput( 21 | inputId = "autosize", 22 | label = "Automatically size the window to fit the window contents.", 23 | value = FALSE 24 | ), 25 | checkboxInput( 26 | inputId = "max", 27 | label = "Automatically toggles the window into maximized state when created.", 28 | value = FALSE 29 | ), 30 | checkboxInput( 31 | inputId = "min", 32 | label = "Automatically toggles the window into minimized state when created.", 33 | value = FALSE 34 | ) 35 | ) 36 | 37 | server <- function(input, output, session) { 38 | 39 | observeEvent(input$show, { 40 | WinBox( 41 | title = "This is a WinBox", 42 | ui = tagList( 43 | tags$h3("Hello from WinBox!") 44 | ), 45 | options = wbOptions( 46 | background = input$background, 47 | modal = input$modal, 48 | autosize = input$autosize, 49 | max = input$max, 50 | min = input$min 51 | ) 52 | ) 53 | }) 54 | 55 | } 56 | 57 | if (interactive()) 58 | shinyApp(ui, server) 59 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 51 | -------------------------------------------------------------------------------- /srcjs/exts/WinBox.js: -------------------------------------------------------------------------------- 1 | import $ from "jquery"; 2 | import "shiny"; 3 | import WinBox from "winbox/src/js/winbox"; 4 | import "winbox/dist/css/winbox.min.css"; 5 | 6 | let winboxes = {}; 7 | 8 | Shiny.addCustomMessageHandler("WinBox-show", msg => { 9 | var options = msg.options; 10 | options.html = `
`; 11 | options.onclose = function() { 12 | Shiny.unbindAll($content); 13 | delete winboxes[options.id]; 14 | }; 15 | options.onresize = function(width, height) { 16 | $("#shiny-winbox-" + options.id) 17 | .find(".html-widget, .shiny-plot-output") 18 | .trigger("resize"); 19 | }; 20 | if (winboxes.hasOwnProperty(options.id)) { 21 | winboxes[options.id].close(); 22 | } 23 | var winbox = new WinBox(options); 24 | var $content = $("#shiny-winbox-" + options.id); 25 | Shiny.renderContent($content, { html: msg.html, deps: msg.deps }); 26 | if (!options.hasOwnProperty("height") && msg.auto_height) { 27 | setTimeout(function() { 28 | winbox.height = $content.height() + 45; 29 | winbox.resize(); 30 | }, 100); 31 | } 32 | //winbox.body.innerHTML = msg.html; 33 | winboxes[winbox.id] = winbox; 34 | }); 35 | 36 | Shiny.addCustomMessageHandler("WinBox-close", msg => { 37 | if (msg.hasOwnProperty("id")) { 38 | if (winboxes.hasOwnProperty(msg.id)) { 39 | winboxes[msg.id].close(); 40 | delete winboxes[msg.id]; 41 | } 42 | } else { 43 | var keys = Object.keys(winboxes); 44 | var last = keys.length - 1; 45 | if (last > -1) { 46 | winboxes[keys[last]].close(); 47 | delete winboxes[keys[last]]; 48 | } 49 | } 50 | }); 51 | 52 | -------------------------------------------------------------------------------- /man/wbControls.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/WinBox.R 3 | \name{wbControls} 4 | \alias{wbControls} 5 | \title{WinBox controls} 6 | \usage{ 7 | wbControls( 8 | animation = TRUE, 9 | shadow = TRUE, 10 | header = TRUE, 11 | min = TRUE, 12 | max = TRUE, 13 | full = FALSE, 14 | close = TRUE, 15 | resize = TRUE, 16 | move = TRUE 17 | ) 18 | } 19 | \arguments{ 20 | \item{animation}{If \code{FALSE}, disables the windows transition animation.} 21 | 22 | \item{shadow}{If \code{FALSE}, disables the windows drop shadow.} 23 | 24 | \item{header}{If \code{FALSE}, hide the window header incl. title and toolbar.} 25 | 26 | \item{min}{If \code{FALSE}, hide the minimize icon.} 27 | 28 | \item{max}{If \code{FALSE}, hide the maximize icon.} 29 | 30 | \item{full}{If \code{FALSE}, hide the fullscreen icon.} 31 | 32 | \item{close}{If \code{FALSE}, hide the close icon.} 33 | 34 | \item{resize}{If \code{FALSE}, disables the window resizing capability.} 35 | 36 | \item{move}{If \code{FALSE}, disables the window moving capability.} 37 | } 38 | \value{ 39 | A \code{list} of controls to use in \code{\link[=WinBox]{WinBox()}}. 40 | } 41 | \description{ 42 | WinBox controls 43 | } 44 | \examples{ 45 | 46 | library(shiny) 47 | library(shinywb) 48 | 49 | ui <- fluidPage( 50 | html_dependency_winbox(), 51 | actionButton(inputId = "show", label = "Show WinBox") 52 | ) 53 | 54 | server <- function(input, output, session) { 55 | 56 | observeEvent(input$show, { 57 | WinBox( 58 | title = "Custom controls", 59 | ui = tagList( 60 | tags$h2("Hello from WinBox!"), 61 | "Text content of winbox." 62 | ), 63 | controls = wbControls( 64 | min = FALSE, 65 | max = FALSE, 66 | resize = FALSE 67 | ) 68 | ) 69 | }) 70 | 71 | } 72 | 73 | if (interactive()) 74 | shinyApp(ui, server) 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shinywb 2 | 3 | 4 | [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 5 | [![R-CMD-check](https://github.com/dreamRs/shinywb/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/dreamRs/shinywb/actions/workflows/R-CMD-check.yaml) 6 | 7 | 8 | > Interface to [WinBox](https://nextapps-de.github.io/winbox/) JavaScript library to use in [shiny](https://shiny.rstudio.com/) applications. WinBox is a modern HTML5 window manager for the web. 9 | 10 | ## Installation 11 | 12 | You can install the development version of shinywb from [GitHub](https://github.com/) with: 13 | 14 | ``` r 15 | # install.packages("remotes") 16 | remotes::install_github("dreamRs/shinywb") 17 | ``` 18 | 19 | ## Example 20 | 21 | Create window from your `server` function with `WinBox()` : 22 | 23 | ```r 24 | library(shiny) 25 | library(shinywb) 26 | library(apexcharter) 27 | library(ggplot2) 28 | data("economics", package = "ggplot2") 29 | 30 | ui <- fluidPage( 31 | html_dependency_winbox(), 32 | actionButton(inputId = "show", label = "Show WinBox") 33 | ) 34 | 35 | server <- function(input, output, session) { 36 | 37 | observeEvent(input$show, { 38 | inputId <- paste0("var", input$show) 39 | WinBox( 40 | title = "With an htmlwidget", 41 | ui = tagList( 42 | tags$h3("Economic chart"), 43 | selectInput(inputId, "Select a variable:", names(economics)[-1]), 44 | renderApexchart({ 45 | apex( 46 | data = economics, 47 | type = "line", 48 | mapping = aes(x = date, y = !!sym(input[[inputId]])) 49 | ) %>% 50 | ax_stroke(width = 1) 51 | }) 52 | ), 53 | options = wbOptions(height = 630) 54 | ) 55 | }) 56 | 57 | } 58 | 59 | if (interactive()) 60 | shinyApp(ui, server) 61 | ``` 62 | 63 | ![](man/figures/winbox.png) 64 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | // defaults 5 | var outputPath = [], 6 | entryPoints = [], 7 | externals = [], 8 | misc = [], 9 | loaders = []; 10 | 11 | var outputPathFile = './srcjs/config/output_path.json', 12 | entryPointsFile = './srcjs/config/entry_points.json', 13 | externalsFile = './srcjs/config/externals.json', 14 | miscFile = './srcjs/config/misc.json', 15 | loadersFile = './srcjs/config/loaders.json'; 16 | 17 | // Read config files 18 | if(fs.existsSync(outputPathFile)){ 19 | outputPath = fs.readFileSync(outputPathFile, 'utf8'); 20 | } 21 | 22 | if(fs.existsSync(entryPointsFile)){ 23 | entryPoints = fs.readFileSync(entryPointsFile, 'utf8'); 24 | } 25 | 26 | if(fs.existsSync(externalsFile)){ 27 | externals = fs.readFileSync(externalsFile, 'utf8'); 28 | } 29 | 30 | if(fs.existsSync(miscFile)){ 31 | misc = fs.readFileSync(miscFile, 'utf8'); 32 | } 33 | 34 | if(fs.existsSync(loadersFile)){ 35 | loaders = fs.readFileSync(loadersFile, 'utf8'); 36 | } 37 | 38 | if(fs.existsSync(loadersFile)){ 39 | loaders = fs.readFileSync(loadersFile, 'utf8'); 40 | } 41 | 42 | // parse 43 | loaders = JSON.parse(loaders); 44 | misc = JSON.parse(misc); 45 | externals = JSON.parse(externals); 46 | entryPoints = JSON.parse(entryPoints); 47 | 48 | // parse regex 49 | loaders.forEach((loader) => { 50 | loader.test = RegExp(loader.test); 51 | return(loader); 52 | }) 53 | 54 | // placeholder for plugins 55 | var plugins = [ 56 | ]; 57 | 58 | // define options 59 | var options = { 60 | entry: entryPoints, 61 | output: { 62 | filename: '[name].js', 63 | path: path.resolve(__dirname, JSON.parse(outputPath)), 64 | }, 65 | externals: externals, 66 | module: { 67 | rules: loaders 68 | }, 69 | resolve: { 70 | extensions: ['.tsx', '.ts', '.js'], 71 | }, 72 | plugins: plugins 73 | }; 74 | 75 | // add misc 76 | if(misc.resolve) 77 | options.resolve = misc.resolve; 78 | 79 | // export 80 | module.exports = options; 81 | -------------------------------------------------------------------------------- /man/WinBox.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/WinBox.R 3 | \name{WinBox} 4 | \alias{WinBox} 5 | \alias{closeWinBox} 6 | \title{WinBox} 7 | \usage{ 8 | WinBox( 9 | title, 10 | ui, 11 | options = wbOptions(), 12 | controls = wbControls(), 13 | id = NULL, 14 | padding = "5px 10px", 15 | auto_height = FALSE, 16 | session = shiny::getDefaultReactiveDomain() 17 | ) 18 | 19 | closeWinBox(id, session = shiny::getDefaultReactiveDomain()) 20 | } 21 | \arguments{ 22 | \item{title}{Title for the window.} 23 | 24 | \item{ui}{Content of the window.} 25 | 26 | \item{options}{List of options, see \code{\link[=wbOptions]{wbOptions()}}.} 27 | 28 | \item{controls}{List of controls, see \code{\link[=wbControls]{wbControls()}}.} 29 | 30 | \item{id}{An unique identifier for the window, if a window with the same \code{id} is already open, 31 | it will be closed before opening the new one. When closing windows, use \code{id = NULL} to close last one opened.} 32 | 33 | \item{padding}{Padding for the window content.} 34 | 35 | \item{auto_height}{Automatically set height of the window according to content. 36 | Note that if content does not have a fix height it may not work properly.} 37 | 38 | \item{session}{Shiny session.} 39 | } 40 | \value{ 41 | No value, a window is openned in the UI. 42 | } 43 | \description{ 44 | A window manager with JavaScript library \href{https://nextapps-de.github.io/winbox/}{WinBox.js}. 45 | } 46 | \note{ 47 | You need to include \code{\link[=html_dependency_winbox]{html_dependency_winbox()}} in your UI definition for this function to work. 48 | } 49 | \examples{ 50 | 51 | library(shiny) 52 | library(shinywb) 53 | 54 | ui <- fluidPage( 55 | html_dependency_winbox(), 56 | actionButton(inputId = "show", label = "Show WinBox"), 57 | verbatimTextOutput("res") 58 | ) 59 | 60 | server <- function(input, output, session) { 61 | 62 | observeEvent(input$show, { 63 | WinBox( 64 | title = "WinBox window", 65 | ui = tagList( 66 | tags$h2("Hello from WinBox!"), 67 | "Text content of winbox.", 68 | selectInput("month", "Select a month:", month.name) 69 | ) 70 | ) 71 | }) 72 | 73 | output$res <- renderPrint(input$month) 74 | 75 | } 76 | 77 | if (interactive()) 78 | shinyApp(ui, server) 79 | } 80 | -------------------------------------------------------------------------------- /man/wbOptions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/WinBox.R 3 | \name{wbOptions} 4 | \alias{wbOptions} 5 | \title{WinBox Options} 6 | \usage{ 7 | wbOptions( 8 | width = NULL, 9 | height = NULL, 10 | minwidth = NULL, 11 | minheight = NULL, 12 | x = NULL, 13 | y = NULL, 14 | max = NULL, 15 | min = NULL, 16 | top = NULL, 17 | right = NULL, 18 | bottom = NULL, 19 | left = NULL, 20 | background = NULL, 21 | border = NULL, 22 | modal = NULL, 23 | index = 1045, 24 | ... 25 | ) 26 | } 27 | \arguments{ 28 | \item{width, height}{Set the initial width/height of the window (supports units "px" and "\%").} 29 | 30 | \item{minwidth, minheight}{Set the minimal width/height of the window (supports units "px" and "\%").} 31 | 32 | \item{x, y}{Set the initial position of the window (supports: "right" for x-axis, "bottom" for y-axis, 33 | "center" for both, units "px" and "\%" for both).} 34 | 35 | \item{max, min}{Automatically toggles the window into maximized / minimized state when created.} 36 | 37 | \item{top, right, bottom, left}{Set or limit the viewport of the window's available area (supports units "px" and "\%").} 38 | 39 | \item{background}{Set the background of the window (supports all CSS styles which are also supported by the style-attribute "background", 40 | e.g. colors, transparent colors, hsl, gradients, background images).} 41 | 42 | \item{border}{Set the border width of the window (supports all css units, like px, \%, em, rem, vh, vmax).} 43 | 44 | \item{modal}{Shows the window as modal.} 45 | 46 | \item{index}{Set the initial z-index of the window to this value (could be increased automatically when unfocused/focused).} 47 | 48 | \item{...}{Other options, see https://github.com/nextapps-de/winbox?tab=readme-ov-file#options.} 49 | } 50 | \value{ 51 | A \code{list} of options to use in \code{\link[=WinBox]{WinBox()}}. 52 | } 53 | \description{ 54 | WinBox Options 55 | } 56 | \examples{ 57 | 58 | library(shiny) 59 | library(shinywb) 60 | 61 | ui <- fluidPage( 62 | html_dependency_winbox(), 63 | actionButton(inputId = "show1", label = "Show WinBox"), 64 | actionButton(inputId = "show2", label = "Show WinBox as modal") 65 | ) 66 | 67 | server <- function(input, output, session) { 68 | 69 | observeEvent(input$show1, { 70 | WinBox( 71 | title = "Custom background color and border", 72 | ui = tagList( 73 | tags$h2("Hello from WinBox!"), 74 | "Text content of winbox." 75 | ), 76 | options = wbOptions( 77 | background = "#112446", 78 | border = "0.5em", 79 | x = "center", 80 | y = "center", 81 | width = "50\%", 82 | height = "50\%" 83 | ) 84 | ) 85 | }) 86 | 87 | observeEvent(input$show2, { 88 | WinBox( 89 | title = "WinBox as modal", 90 | ui = tagList( 91 | tags$h2("Hello from WinBox!"), 92 | "Text content of winbox." 93 | ), 94 | options = wbOptions(modal = TRUE) 95 | ) 96 | }) 97 | 98 | } 99 | 100 | if (interactive()) 101 | shinyApp(ui, server) 102 | } 103 | -------------------------------------------------------------------------------- /R/WinBox.R: -------------------------------------------------------------------------------- 1 | 2 | #' @title Winbox JavaScript Dependencies 3 | #' 4 | #' @description Include dependencies, place anywhere in the shiny UI. 5 | #' 6 | #' @param css_rules CSS rules to be included in a `style` tag in the document head. 7 | #' By default it set a `min-height` to the body element. 8 | #' 9 | #' @importFrom htmltools htmlDependency doRenderTags tags 10 | #' @importFrom utils packageVersion 11 | #' 12 | #' @export 13 | #' 14 | #' @example inst/examples/basic.R 15 | html_dependency_winbox <- function(css_rules = "body{min-height:100vh}.winbox.modal{display:block;overflow:unset}") { 16 | if (!is.null(css_rules)) { 17 | styles <- doRenderTags(tags$style(css_rules)) 18 | } else { 19 | styles <- NULL 20 | } 21 | htmlDependency( 22 | name = "winbox", 23 | version = packageVersion("shinywb"), 24 | src = list(file = "packer"), 25 | package = "shinywb", 26 | script = "WinBox.js", 27 | head = styles 28 | ) 29 | } 30 | 31 | 32 | 33 | #' @title WinBox 34 | #' 35 | #' @description A window manager with JavaScript library [WinBox.js](https://nextapps-de.github.io/winbox/). 36 | #' 37 | #' @param title Title for the window. 38 | #' @param ui Content of the window. 39 | #' @param options List of options, see [wbOptions()]. 40 | #' @param controls List of controls, see [wbControls()]. 41 | #' @param id An unique identifier for the window, if a window with the same `id` is already open, 42 | #' it will be closed before opening the new one. When closing windows, use `id = NULL` to close last one opened. 43 | #' @param padding Padding for the window content. 44 | #' @param auto_height Automatically set height of the window according to content. 45 | #' Note that if content does not have a fix height it may not work properly. 46 | #' @param session Shiny session. 47 | #' 48 | #' @return No value, a window is openned in the UI. 49 | #' 50 | #' @note You need to include [html_dependency_winbox()] in your UI definition for this function to work. 51 | #' 52 | #' @name WinBox 53 | #' @export 54 | #' 55 | #' @importFrom shiny getDefaultReactiveDomain 56 | #' @importFrom htmltools tags css 57 | #' 58 | #' @example inst/examples/default.R 59 | WinBox <- function(title, 60 | ui, 61 | options = wbOptions(), 62 | controls = wbControls(), 63 | id = NULL, 64 | padding = "5px 10px", 65 | auto_height = FALSE, 66 | session = shiny::getDefaultReactiveDomain()) { 67 | if (!is.null(padding)) 68 | ui <- tags$div(ui, style = css(padding = padding)) 69 | res <- utils::getFromNamespace("processDeps", "shiny")(ui, session) 70 | if (is.null(id)) 71 | id <- paste0("winbox-", genId()) 72 | options$id <- id 73 | options$title <- as.character(title) 74 | options$class <- controls 75 | session$sendCustomMessage("WinBox-show", list( 76 | html = res$html, 77 | deps = res$deps, 78 | options = options, 79 | auto_height = isTRUE(auto_height) 80 | )) 81 | } 82 | 83 | #' @rdname WinBox 84 | #' @export 85 | closeWinBox <- function(id, session = shiny::getDefaultReactiveDomain()) { 86 | session$sendCustomMessage("WinBox-close", dropNulls(list(id = id))) 87 | } 88 | 89 | 90 | 91 | #' WinBox Options 92 | #' 93 | #' @param width,height Set the initial width/height of the window (supports units "px" and "%"). 94 | #' @param minwidth,minheight Set the minimal width/height of the window (supports units "px" and "%"). 95 | #' @param x,y Set the initial position of the window (supports: "right" for x-axis, "bottom" for y-axis, 96 | #' "center" for both, units "px" and "%" for both). 97 | #' @param max,min Automatically toggles the window into maximized / minimized state when created. 98 | #' @param top,right,bottom,left Set or limit the viewport of the window's available area (supports units "px" and "%"). 99 | #' @param background Set the background of the window (supports all CSS styles which are also supported by the style-attribute "background", 100 | #' e.g. colors, transparent colors, hsl, gradients, background images). 101 | #' @param border Set the border width of the window (supports all css units, like px, %, em, rem, vh, vmax). 102 | #' @param modal Shows the window as modal. 103 | #' @param index Set the initial z-index of the window to this value (could be increased automatically when unfocused/focused). 104 | #' @param ... Other options, see https://github.com/nextapps-de/winbox?tab=readme-ov-file#options. 105 | #' 106 | #' @return A `list` of options to use in [WinBox()]. 107 | #' @export 108 | #' 109 | #' @example inst/examples/options.R 110 | wbOptions <- function(width = NULL, 111 | height = NULL, 112 | minwidth = NULL, 113 | minheight = NULL, 114 | x = NULL, 115 | y = NULL, 116 | max = NULL, 117 | min = NULL, 118 | top = NULL, 119 | right = NULL, 120 | bottom = NULL, 121 | left = NULL, 122 | background = NULL, 123 | border = NULL, 124 | modal = NULL, 125 | index = 1045, 126 | ...) { 127 | dropNulls(list( 128 | width = width, 129 | height = height, 130 | minwidth = minwidth, 131 | minheight = minheight, 132 | x = x, 133 | y = y, 134 | max = max, 135 | min = min, 136 | top = top, 137 | right = right, 138 | bottom = bottom, 139 | left = left, 140 | background = background, 141 | border = border, 142 | modal = modal, 143 | index = index, 144 | ... 145 | )) 146 | } 147 | 148 | 149 | #' WinBox controls 150 | #' 151 | #' @param animation If `FALSE`, disables the windows transition animation. 152 | #' @param shadow If `FALSE`, disables the windows drop shadow. 153 | #' @param header If `FALSE`, hide the window header incl. title and toolbar. 154 | #' @param min If `FALSE`, hide the minimize icon. 155 | #' @param max If `FALSE`, hide the maximize icon. 156 | #' @param full If `FALSE`, hide the fullscreen icon. 157 | #' @param close If `FALSE`, hide the close icon. 158 | #' @param resize If `FALSE`, disables the window resizing capability. 159 | #' @param move If `FALSE`, disables the window moving capability. 160 | #' 161 | #' @return A `list` of controls to use in [WinBox()]. 162 | #' @export 163 | #' 164 | #' @example inst/examples/controls.R 165 | wbControls <- function(animation = TRUE, 166 | shadow = TRUE, 167 | header = TRUE, 168 | min = TRUE, 169 | max = TRUE, 170 | full = FALSE, 171 | close = TRUE, 172 | resize = TRUE, 173 | move = TRUE) { 174 | classes <- c( 175 | animation = animation, 176 | shadow = shadow, 177 | header = header, 178 | min = min, 179 | max = max, 180 | full = full, 181 | close = close, 182 | resize = resize, 183 | move = move 184 | ) 185 | classes <- paste0("no-", names(classes)[!unname(classes)]) 186 | list1(classes) 187 | } 188 | -------------------------------------------------------------------------------- /inst/packer/WinBox.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var t={693:(t,i,e)=>{e.d(i,{Z:()=>x});var o=e(81),n=e.n(o),s=e(645),r=e.n(s),h=e(667),a=e.n(h),d=new URL(e(170),e.b),c=new URL(e(920),e.b),l=new URL(e(324),e.b),u=new URL(e(258),e.b),m=r()(n()),w=a()(d),p=a()(c),b=a()(l),f=a()(u);m.push([t.id,"@keyframes wb-fade-in{0%{opacity:0}to{opacity:.85}}.winbox{position:fixed;left:0;top:0;background:#0050ff;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);transition:width .3s,height .3s,left .3s,top .3s;transition-timing-function:cubic-bezier(.3,1,.3,1);contain:layout size;text-align:left;touch-action:none}.wb-body,.wb-header{position:absolute;left:0}.wb-header{top:0;width:100%;height:35px;line-height:35px;color:#fff;overflow:hidden;z-index:1}.wb-body{top:35px;right:0;bottom:0;overflow:auto;-webkit-overflow-scrolling:touch;overflow-scrolling:touch;will-change:contents;background:#fff;margin-top:0!important;contain:strict;z-index:0}.wb-control *,.wb-icon{background-repeat:no-repeat}.wb-drag{height:100%;padding-left:10px;cursor:move}.wb-title{font-family:Arial,sans-serif;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wb-icon{display:none;width:20px;height:100%;margin:-1px 8px 0-3px;float:left;background-size:100%;background-position:center}.wb-e,.wb-w{width:10px;top:0}.wb-n,.wb-s{left:0;height:10px;position:absolute}.wb-n{top:-5px;right:0;cursor:n-resize;z-index:2}.wb-e{position:absolute;right:-5px;bottom:0;cursor:w-resize;z-index:2}.wb-s{bottom:-5px;right:0;cursor:n-resize;z-index:2}.wb-nw,.wb-sw,.wb-w{left:-5px}.wb-w{position:absolute;bottom:0;cursor:w-resize;z-index:2}.wb-ne,.wb-nw,.wb-sw{width:15px;height:15px;z-index:2;position:absolute}.wb-nw{top:-5px;cursor:nw-resize}.wb-ne,.wb-sw{cursor:ne-resize}.wb-ne{top:-5px;right:-5px}.wb-se,.wb-sw{bottom:-5px}.wb-se{position:absolute;right:-5px;width:15px;height:15px;cursor:nw-resize;z-index:2}.wb-control{float:right;height:100%;max-width:100%;text-align:center}.wb-control *{display:inline-block;width:30px;height:100%;max-width:100%;background-position:center;cursor:pointer}.no-close .wb-close,.no-full .wb-full,.no-header .wb-header,.no-max .wb-max,.no-min .wb-min,.no-resize .wb-body~div,.wb-body .wb-hide,.wb-show,.winbox.hide,.winbox.min .wb-body>*,.winbox.min .wb-full,.winbox.min .wb-min,.winbox.modal .wb-full,.winbox.modal .wb-max,.winbox.modal .wb-min{display:none}.winbox.max .wb-drag,.winbox.min .wb-drag{cursor:default}.wb-min{background-image:url("+w+");background-size:14px auto;background-position:center calc(50% + 6px)}.wb-max{background-image:url("+p+");background-size:17px auto}.wb-close{background-image:url("+b+");background-size:15px auto;background-position:5px center}.wb-full{background-image:url("+f+');background-size:16px auto}.winbox.max .wb-body~div,.winbox.min .wb-body~div,.winbox.modal .wb-body~div,.winbox.modal .wb-drag,body.wb-lock iframe{pointer-events:none}.winbox.max{box-shadow:none}.winbox.max .wb-body{margin:0!important}.winbox iframe{position:absolute;width:100%;height:100%;border:0}body.wb-lock .winbox{will-change:left,top,width,height;transition:none}.winbox.modal:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:inherit;border-radius:inherit}.winbox.modal:after{content:"";position:absolute;top:-50vh;left:-50vw;right:-50vw;bottom:-50vh;background:#0d1117;animation:wb-fade-in .2s ease-out forwards;z-index:-1}.no-animation{transition:none}.no-shadow{box-shadow:none}.no-header .wb-body{top:0}.no-move:not(.min) .wb-title{pointer-events:none}.wb-body .wb-show{display:revert}',""]);const x=m},645:t=>{t.exports=function(t){var i=[];return i.toString=function(){return this.map((function(i){var e="",o=void 0!==i[5];return i[4]&&(e+="@supports (".concat(i[4],") {")),i[2]&&(e+="@media ".concat(i[2]," {")),o&&(e+="@layer".concat(i[5].length>0?" ".concat(i[5]):""," {")),e+=t(i),o&&(e+="}"),i[2]&&(e+="}"),i[4]&&(e+="}"),e})).join("")},i.i=function(t,e,o,n,s){"string"==typeof t&&(t=[[null,t,void 0]]);var r={};if(o)for(var h=0;h0?" ".concat(c[5]):""," {").concat(c[1],"}")),c[5]=s),e&&(c[2]?(c[1]="@media ".concat(c[2]," {").concat(c[1],"}"),c[2]=e):c[2]=e),n&&(c[4]?(c[1]="@supports (".concat(c[4],") {").concat(c[1],"}"),c[4]=n):c[4]="".concat(n)),i.push(c))}},i}},667:t=>{t.exports=function(t,i){return i||(i={}),t?(t=String(t.__esModule?t.default:t),/^['"].*['"]$/.test(t)&&(t=t.slice(1,-1)),i.hash&&(t+=i.hash),/["'() \t\n]|(%20)/.test(t)||i.needQuotes?'"'.concat(t.replace(/"/g,'\\"').replace(/\n/g,"\\n"),'"'):t):t}},81:t=>{t.exports=function(t){return t[1]}},379:t=>{var i=[];function e(t){for(var e=-1,o=0;o{var i={};t.exports=function(t,e){var o=function(t){if(void 0===i[t]){var e=document.querySelector(t);if(window.HTMLIFrameElement&&e instanceof window.HTMLIFrameElement)try{e=e.contentDocument.head}catch(t){e=null}i[t]=e}return i[t]}(t);if(!o)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");o.appendChild(e)}},216:t=>{t.exports=function(t){var i=document.createElement("style");return t.setAttributes(i,t.attributes),t.insert(i,t.options),i}},565:(t,i,e)=>{t.exports=function(t){var i=e.nc;i&&t.setAttribute("nonce",i)}},795:t=>{t.exports=function(t){var i=t.insertStyleElement(t);return{update:function(e){!function(t,i,e){var o="";e.supports&&(o+="@supports (".concat(e.supports,") {")),e.media&&(o+="@media ".concat(e.media," {"));var n=void 0!==e.layer;n&&(o+="@layer".concat(e.layer.length>0?" ".concat(e.layer):""," {")),o+=e.css,n&&(o+="}"),e.media&&(o+="}"),e.supports&&(o+="}");var s=e.sourceMap;s&&"undefined"!=typeof btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(s))))," */")),i.styleTagTransform(o,t,i.options)}(i,t,e)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(i)}}}},589:t=>{t.exports=function(t,i){if(i.styleSheet)i.styleSheet.cssText=t;else{for(;i.firstChild;)i.removeChild(i.firstChild);i.appendChild(document.createTextNode(t))}}},920:t=>{t.exports="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIHZpZXdCb3g9IjAgMCA5NiA5NiI+PHBhdGggZD0iTTIwIDcxLjMxMUMxNS4zNCA2OS42NyAxMiA2NS4yMyAxMiA2MFYyMGMwLTYuNjMgNS4zNy0xMiAxMi0xMmg0MGM1LjIzIDAgOS42NyAzLjM0IDExLjMxMSA4SDI0Yy0yLjIxIDAtNCAxLjc5LTQgNHY1MS4zMTF6Ii8+PHBhdGggZD0iTTkyIDc2VjM2YzAtNi42My01LjM3LTEyLTEyLTEySDQwYy02LjYzIDAtMTIgNS4zNy0xMiAxMnY0MGMwIDYuNjMgNS4zNyAxMiAxMiAxMmg0MGM2LjYzIDAgMTItNS4zNyAxMi0xMnptLTUyIDRjLTIuMjEgMC00LTEuNzktNC00VjM2YzAtMi4yMSAxLjc5LTQgNC00aDQwYzIuMjEgMCA0IDEuNzkgNCA0djQwYzAgMi4yMS0xLjc5IDQtNCA0SDQweiIvPjwvc3ZnPg=="},258:t=>{t.exports="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjIuNSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNOCAzSDVhMiAyIDAgMCAwLTIgMnYzbTE4IDBWNWEyIDIgMCAwIDAtMi0yaC0zbTAgMThoM2EyIDIgMCAwIDAgMi0ydi0zTTMgMTZ2M2EyIDIgMCAwIDAgMiAyaDMiLz48L3N2Zz4="},324:t=>{t.exports="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xIC0xIDE4IDE4Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJtMS42MTMuMjEuMDk0LjA4M0w4IDYuNTg1IDE0LjI5My4yOTNsLjA5NC0uMDgzYTEgMSAwIDAgMSAxLjQwMyAxLjQwM2wtLjA4My4wOTRMOS40MTUgOGw2LjI5MiA2LjI5M2ExIDEgMCAwIDEtMS4zMiAxLjQ5N2wtLjA5NC0uMDgzTDggOS40MTVsLTYuMjkzIDYuMjkyLS4wOTQuMDgzQTEgMSAwIDAgMSAuMjEgMTQuMzg3bC4wODMtLjA5NEw2LjU4NSA4IC4yOTMgMS43MDdBMSAxIDAgMCAxIDEuNjEzLjIxeiIvPjwvc3ZnPg=="},170:t=>{t.exports="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAyIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNOCAwaDdhMSAxIDAgMCAxIDAgMkgxYTEgMSAwIDAgMSAwLTJoN3oiLz48L3N2Zz4="}},i={};function e(o){var n=i[o];if(void 0!==n)return n.exports;var s=i[o]={id:o,exports:{}};return t[o](s,s.exports,e),s.exports}e.m=t,e.n=t=>{var i=t&&t.__esModule?()=>t.default:()=>t;return e.d(i,{a:i}),i},e.d=(t,i)=>{for(var o in i)e.o(i,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:i[o]})},e.o=(t,i)=>Object.prototype.hasOwnProperty.call(t,i),e.b=document.baseURI||self.location.href,(()=>{const t=jQuery;var i=e.n(t);Shiny;const o=document.createElement("div");function n(t,i,e,o){t&&t.addEventListener(i,e,o||!1)}function s(t,i,e,o){t&&t.removeEventListener(i,e,o||!1)}function r(t,i){t.stopPropagation(),i&&t.preventDefault()}function h(t,i){return t.getElementsByClassName(i)[0]}function a(t,i){t.classList.add(i)}function d(t,i){t.classList.remove(i)}function c(t,i,e){e=""+e,t["_s_"+i]!==e&&(t.style.setProperty(i,e),t["_s_"+i]=e)}o.innerHTML="
";const l=[],u=[],m={capture:!0,passive:!1},w={capture:!0,passive:!0};let p,b,f,x,g,y,v,M=0,z=10;function I(t,i){if(!(this instanceof I))return new I(t);let e,s,a,d,l,m,w,b,A,k,D,T,S,E,Z,H,P,O,Y,B,R,G,Q,U,F,W,_,q,J,V,X,$,K,tt,it,et,ot,nt,st,rt,ht,at,dt,ct;if(p||(p=document.body,p[f="requestFullscreen"]||p[f="msRequestFullscreen"]||p[f="webkitRequestFullscreen"]||p[f="mozRequestFullscreen"]||(f=""),x=f&&f.replace("request","exit").replace("mozRequest","mozCancel").replace("Request","Exit"),n(window,"resize",(function(){j(),L()})),n(p,"mousedown",(function(t){v=!1}),!0),n(p,"mousedown",(function(t){if(!v){const t=u.length;if(t)for(let i=t-1;i>=0;i--){const t=u[i];if(t.focused){t.blur();break}}}})),j()),t&&(i&&(l=t,t=i),"string"==typeof t?l=t:(e=t.id,s=t.index,a=t.root,d=t.template,l=l||t.title,m=t.icon,w=t.mount,b=t.html,A=t.url,k=t.width,D=t.height,T=t.minwidth,S=t.minheight,E=t.maxwidth,Z=t.maxheight,H=t.autosize,P=t.overflow,U=t.min,F=t.max,W=t.hidden,_=t.modal,O=t.x||(_?"center":0),Y=t.y||(_?"center":0),B=t.top,R=t.left,G=t.bottom,Q=t.right,q=t.background,J=t.border,V=t.header,X=t.class,$=t.oncreate,K=t.onclose,tt=t.onfocus,it=t.onblur,et=t.onmove,ot=t.onresize,nt=t.onfullscreen,st=t.onmaximize,rt=t.onminimize,ht=t.onrestore,at=t.onhide,dt=t.onshow,ct=t.onload)),this.dom=function(t){return(t||o).cloneNode(!0)}(d),this.dom.id=this.id=e||"winbox-"+ ++M,this.dom.className="winbox"+(X?" "+("string"==typeof X?X:X.join(" ")):"")+(_?" modal":""),this.dom.winbox=this,this.window=this.dom,this.body=h(this.dom,"wb-body"),this.header=V||35,u.push(this),q&&this.setBackground(q),J?c(this.body,"margin",J+(isNaN(J)?"":"px")):J=0,V){const t=h(this.dom,"wb-header");c(t,"height",V+"px"),c(t,"line-height",V+"px"),c(this.body,"top",V+"px")}l&&this.setTitle(l),m&&this.setIcon(m),w?this.mount(w):b?this.body.innerHTML=b:A&&this.setUrl(A,ct),B=B?C(B,y):0,G=G?C(G,y):0,R=R?C(R,g):0,Q=Q?C(Q,g):0;const lt=g-R-Q,ut=y-B-G;E=E?C(E,lt):lt,Z=Z?C(Z,ut):ut,T=T?C(T,E):150,S=S?C(S,Z):this.header,H?((a||p).appendChild(this.body),k=Math.max(Math.min(this.body.clientWidth+2*J+1,E),T),D=Math.max(Math.min(this.body.clientHeight+this.header+J+1,Z),S),this.dom.appendChild(this.body)):(k=k?C(k,E):0|Math.max(E/2,T),D=D?C(D,Z):0|Math.max(Z/2,S)),O=O?C(O,lt,k):R,Y=Y?C(Y,ut,D):B,this.x=O,this.y=Y,this.width=k,this.height=D,this.minwidth=T,this.minheight=S,this.maxwidth=E,this.maxheight=Z,this.top=B,this.right=Q,this.bottom=G,this.left=R,this.index=s,this.overflow=P,this.min=!1,this.max=!1,this.full=!1,this.hidden=!1,this.focused=!1,this.onclose=K,this.onfocus=tt,this.onblur=it,this.onmove=et,this.onresize=ot,this.onfullscreen=nt,this.onmaximize=st,this.onminimize=rt,this.onrestore=ht,this.onhide=at,this.onshow=dt,W?this.hide():this.focus(),(s||0===s)&&(this.index=s,c(this.dom,"z-index",s),s>z&&(z=s)),F?this.maximize():U?this.minimize():this.resize().move(),function(t){N(t,"drag"),N(t,"n"),N(t,"s"),N(t,"w"),N(t,"e"),N(t,"nw"),N(t,"ne"),N(t,"se"),N(t,"sw"),n(h(t.dom,"wb-min"),"click",(function(i){r(i),t.min?t.restore().focus():t.minimize()})),n(h(t.dom,"wb-max"),"click",(function(i){r(i),t.max?t.restore().focus():t.maximize().focus()})),f?n(h(t.dom,"wb-full"),"click",(function(i){r(i),t.fullscreen().focus()})):t.addClass("no-full"),n(h(t.dom,"wb-close"),"click",(function(i){r(i),t.close()||(t=null)})),n(t.dom,"mousedown",(function(t){v=!0}),!0),n(t.body,"mousedown",(function(i){t.focus()}),!0)}(this),(a||p).appendChild(this.dom),$&&$.call(this,t)}I.new=function(t){return new I(t)},I.stack=function(){return u};const A=I;function C(t,i,e){if("string"==typeof t)if("center"===t)t=(i-e)/2+.5|0;else if("right"===t||"bottom"===t)t=i-e;else{const e=parseFloat(t),o=""+e!==t&&t.substring((""+e).length);t="%"===o?i/100*e+.5|0:e}return t}function k(t){l.splice(l.indexOf(t),1),L(),t.removeClass("min"),t.min=!1,t.dom.title=""}function L(){const t=l.length,i={},e={};for(let o,n,s=0;sg/3*2?g-t.width-t.right:g/2-t.width/2)+h),t.x=Math.max(Math.min(t.x,t.overflow?g-30:g-t.width-t.right),t.overflow?30-t.width:t.left),f=t.x!==m),x&&(t.max&&(t.y=t.top+a),t.y=Math.max(Math.min(t.y,t.overflow?y-t.header:y-t.height-t.bottom),t.top),x=t.y!==w),(f||x)&&(t.max&&t.restore(),t.move()),(p||f)&&(c=n),(b||x)&&(l=s)}function x(t){r(t),d(p,"wb-lock"),o?(s(window,"touchmove",f,w),s(window,"touchend",x,w)):(s(window,"mousemove",f,w),s(window,"mouseup",x,w))}n(e,"mousedown",b,m),n(e,"touchstart",b,m)}function j(){const t=document.documentElement;g=t.clientWidth,y=t.clientHeight}function D(){const t=u.length;if(t)for(let i=t-1;i>=0;i--){const t=u[i];if(!t.min){t.focus();break}}}function T(){if(b.full=!1,document.fullscreen||document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement)return document[x](),!0}I.prototype.mount=function(t){return this.unmount(),t._backstore||(t._backstore=t.parentNode),this.body.textContent="",this.body.appendChild(t),this},I.prototype.unmount=function(t){const i=this.body.firstChild;if(i){const e=t||i._backstore;e&&e.appendChild(i),i._backstore=t}return this},I.prototype.setTitle=function(t){return function(t,i){const e=t.firstChild;e?e.nodeValue=i:t.textContent=i}(h(this.dom,"wb-title"),this.title=t),this},I.prototype.setIcon=function(t){const i=h(this.dom,"wb-icon");return c(i,"background-image","url("+t+")"),c(i,"display","inline-block"),this},I.prototype.setBackground=function(t){return c(this.dom,"background",t),this},I.prototype.setUrl=function(t,i){const e=this.body.firstChild;return e&&"iframe"===e.tagName.toLowerCase()?e.src=t:(this.body.innerHTML='',i&&(this.body.firstChild.onload=i)),this},I.prototype.focus=function(t){if(!1===t)return this.blur();if(!this.focused){const t=u.length;if(t>1)for(let i=1;i<=t;i++){const e=u[t-i];if(e.focused){e.blur(),u.push(u.splice(u.indexOf(this),1)[0]);break}}c(this.dom,"z-index",++z),this.index=z,this.addClass("focus"),this.focused=!0,this.onfocus&&this.onfocus()}return this},I.prototype.blur=function(t){return!1===t?this.focus():(this.focused&&(this.removeClass("focus"),this.focused=!1,this.onblur&&this.onblur()),this)},I.prototype.hide=function(t){return!1===t?this.show():this.hidden?void 0:(this.onhide&&this.onhide(),this.hidden=!0,this.addClass("hide"))},I.prototype.show=function(t){return!1===t?this.hide():this.hidden?(this.onshow&&this.onshow(),this.hidden=!1,this.removeClass("hide")):void 0},I.prototype.minimize=function(t){return!1===t?this.restore():(b&&T(),this.max&&(this.removeClass("max"),this.max=!1),this.min||(l.push(this),L(),this.dom.title=this.title,this.addClass("min"),this.min=!0,this.focused&&(this.blur(),D()),this.onminimize&&this.onminimize()),this)},I.prototype.restore=function(){return b&&T(),this.min&&(k(this),this.resize().move(),this.onrestore&&this.onrestore()),this.max&&(this.max=!1,this.removeClass("max").resize().move(),this.onrestore&&this.onrestore()),this},I.prototype.maximize=function(t){return!1===t?this.restore():(b&&T(),this.min&&k(this),this.max||(this.addClass("max").resize(g-this.left-this.right,y-this.top-this.bottom,!0).move(this.left,this.top,!0),this.max=!0,this.onmaximize&&this.onmaximize()),this)},I.prototype.fullscreen=function(t){if(this.min&&(k(this),this.resize().move()),b&&T()){if(!1===t)return this.restore()}else this.body[f](),b=this,this.full=!0,this.onfullscreen&&this.onfullscreen();return this},I.prototype.close=function(t){if(this.onclose&&this.onclose(t))return!0;this.min&&k(this),u.splice(u.indexOf(this),1),this.unmount(),this.dom.remove(),this.dom.textContent="",this.dom.winbox=null,this.body=null,this.dom=null,this.focused&&D()},I.prototype.move=function(t,i,e){return t||0===t?e||(this.x=t?t=C(t,g-this.left-this.right,this.width):0,this.y=i?i=C(i,y-this.top-this.bottom,this.height):0):(t=this.x,i=this.y),c(this.dom,"left",t+"px"),c(this.dom,"top",i+"px"),this.onmove&&this.onmove(t,i),this},I.prototype.resize=function(t,i,e){return t||0===t?e||(this.width=t?t=C(t,this.maxwidth):0,this.height=i?i=C(i,this.maxheight):0,t=Math.max(t,this.minwidth),i=Math.max(i,this.minheight)):(t=this.width,i=this.height),c(this.dom,"width",t+"px"),c(this.dom,"height",i+"px"),this.onresize&&this.onresize(t,i),this},I.prototype.addControl=function(t){const i=t.class,e=t.image,o=t.click,n=t.index,s=document.createElement("span"),r=h(this.dom,"wb-control"),a=this;return i&&(s.className=i),e&&c(s,"background-image","url("+e+")"),o&&(s.onclick=function(t){o.call(this,t,a)}),r.insertBefore(s,r.childNodes[n||0]),this},I.prototype.removeControl=function(t){return(t=h(this.dom,t))&&t.remove(),this},I.prototype.addClass=function(t){return a(this.dom,t),this},I.prototype.removeClass=function(t){return d(this.dom,t),this},I.prototype.hasClass=function(t){return function(t,i){return t.classList.contains(i)}(this.dom,t)},I.prototype.toggleClass=function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)};var S=e(379),E=e.n(S),Z=e(795),H=e.n(Z),P=e(569),O=e.n(P),Y=e(565),B=e.n(Y),R=e(216),G=e.n(R),Q=e(589),U=e.n(Q),F=e(693),W={};W.styleTagTransform=U(),W.setAttributes=B(),W.insert=O().bind(null,"head"),W.domAPI=H(),W.insertStyleElement=G(),E()(F.Z,W),F.Z&&F.Z.locals&&F.Z.locals;let _={};Shiny.addCustomMessageHandler("WinBox-show",(t=>{var e=t.options;e.html=`
`,e.onclose=function(){Shiny.unbindAll(n),delete _[e.id]},e.onresize=function(t,o){i()("#shiny-winbox-"+e.id).find(".html-widget, .shiny-plot-output").trigger("resize")},_.hasOwnProperty(e.id)&&_[e.id].close();var o=new A(e),n=i()("#shiny-winbox-"+e.id);Shiny.renderContent(n,{html:t.html,deps:t.deps}),!e.hasOwnProperty("height")&&t.auto_height&&setTimeout((function(){o.height=n.height()+45,o.resize()}),100),_[o.id]=o})),Shiny.addCustomMessageHandler("WinBox-close",(t=>{if(t.hasOwnProperty("id"))_.hasOwnProperty(t.id)&&(_[t.id].close(),delete _[t.id]);else{var i=Object.keys(_),e=i.length-1;e>-1&&(_[i[e]].close(),delete _[i[e]])}}))})()})(); --------------------------------------------------------------------------------