├── .github ├── .gitignore ├── workflows │ ├── jest.yaml │ ├── lint.yaml │ ├── test-coverage.yaml │ ├── docker-image.yaml │ ├── pkgdown.yaml │ └── R-CMD-check.yaml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── vignettes ├── .gitignore ├── templates.Rmd └── designer.Rmd ├── inst ├── utils │ ├── WORDLIST │ └── golem-config.yml ├── app │ └── www │ │ ├── avatar.png │ │ └── favicon.ico ├── rstudio │ └── addins.dcf ├── golem-config.yml └── srcjs │ ├── sortable │ └── LICENSE │ └── bs-custom-file-input │ └── bs-custom-file-input.min.js ├── LICENSE ├── _pkgdown.yml ├── man ├── figures │ ├── logo.png │ ├── example_app.gif │ ├── save_template.png │ └── example_app_filled.jpeg ├── component.Rd ├── designer-package.Rd ├── sidebarItem.Rd ├── designApp.Rd └── component_setting.Rd ├── R ├── _disable_autoload.R ├── designer.R ├── mod_options_utils.R ├── mod_canvas_srv.R ├── mod_canvas_utils.R ├── app_server.R ├── run_app.R ├── app_config.R ├── mod_options_ui.R ├── mod_settings_ui.R ├── mod_settings_srv.R ├── cache.R ├── mod_settings_utils.R ├── mod_canvas_ui.R ├── cicerone_guide.R ├── ui_utils.R ├── mod_template_ui.R ├── mod_code_srv.R ├── mod_code_ui.R ├── mod_sidebar_utils.R ├── mod_sidebar_srv.R ├── app_ui.R ├── mod_template_utils.R ├── json_to_rscript.R └── mod_template_srv.R ├── .dockerignore ├── tests ├── testthat.R └── testthat │ ├── test-template_utils.R │ ├── test-ui-modules.R │ ├── test-template_server.R │ ├── test-golem-recommended.R │ ├── test-cache.R │ ├── test-template.R │ ├── test-shinytest2.R │ └── test-json-to-r.R ├── srcjs ├── .babelrc ├── page │ ├── __tests__ │ │ ├── Page.test.js │ │ ├── FillPage.test.js │ │ ├── BasicPage.test.js │ │ ├── FixedPage.test.js │ │ ├── FluidPage.test.js │ │ ├── NavbarPage.test.js │ │ ├── BootstrapPage.test.js │ │ └── DashboardPage.test.js │ ├── BasicPage.js │ ├── FillPage.js │ ├── BootstrapPage.js │ ├── FixedPage.js │ ├── FluidPage.js │ ├── init.js │ ├── NavbarPage.js │ ├── Page.js │ ├── DashboardPage.js │ └── utils.js ├── component │ ├── __tests__ │ │ ├── Box.test.js │ │ ├── Row.test.js │ │ ├── Tab.test.js │ │ ├── Text.test.js │ │ ├── Button.test.js │ │ ├── Column.test.js │ │ ├── Header.test.js │ │ ├── Input.test.js │ │ ├── Output.test.js │ │ ├── Tabset.test.js │ │ ├── Callout.test.js │ │ ├── InfoBox.test.js │ │ ├── UserBox.test.js │ │ ├── ValueBox.test.js │ │ ├── DateInput.test.js │ │ ├── FileInput.test.js │ │ ├── BlockQuote.test.js │ │ ├── Checkbox.test.js │ │ ├── InputPanel.test.js │ │ ├── SelectInput.test.js │ │ ├── SliderInput.test.js │ │ ├── CheckboxGroup.test.js │ │ ├── utils.test.js │ │ └── Component.test.js │ ├── Header.js │ ├── Row.js │ ├── BlockQuote.js │ ├── InputPanel.js │ ├── Text.js │ ├── init.js │ ├── Column.js │ ├── Callout.js │ ├── Checkbox.js │ ├── SelectInput.js │ ├── FileInput.js │ ├── Input.js │ ├── ValueBox.js │ ├── InfoBox.js │ ├── Box.js │ ├── utils.js │ ├── CheckboxGroup.js │ ├── UserBox.js │ ├── Button.js │ ├── Component.js │ ├── DateInput.js │ ├── Output.js │ ├── SliderInput.js │ ├── Tab.js │ └── Tabset.js ├── .eslintrc.yml ├── build.js ├── build_dev.js ├── app │ ├── screenshot.js │ ├── index.js │ └── settings.js ├── input │ ├── canvas-page-input.js │ ├── utils.js │ └── canvas-canvas-input.js ├── README.md └── package.json ├── NAMESPACE ├── app.R ├── .gitignore ├── .Rbuildignore ├── designer.Rproj ├── rsconnect └── shinyapps.io │ └── ashbaldry │ └── designer.dcf ├── .lintr ├── NEWS.md ├── DESCRIPTION ├── Dockerfile └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /inst/utils/WORDLIST: -------------------------------------------------------------------------------- 1 | Lifecycle 2 | UI 3 | golem 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022 2 | COPYRIGHT HOLDER: Ashley Baldry 3 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://ashbaldry.github.io/designer/ 2 | template: 3 | bootstrap: 5 4 | 5 | -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashbaldry/designer/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /inst/app/www/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashbaldry/designer/HEAD/inst/app/www/avatar.png -------------------------------------------------------------------------------- /inst/app/www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashbaldry/designer/HEAD/inst/app/www/favicon.ico -------------------------------------------------------------------------------- /R/_disable_autoload.R: -------------------------------------------------------------------------------- 1 | # Disabling shiny autoload 2 | 3 | # See ?shiny::loadSupport for more information 4 | -------------------------------------------------------------------------------- /man/figures/example_app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashbaldry/designer/HEAD/man/figures/example_app.gif -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .RData 2 | .Rhistory 3 | .git 4 | .gitignore 5 | manifest.json 6 | rsconnect/ 7 | .Rproj.user 8 | -------------------------------------------------------------------------------- /man/figures/save_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashbaldry/designer/HEAD/man/figures/save_template.png -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(shinytest2) 3 | library(designer) 4 | 5 | test_check("designer") 6 | -------------------------------------------------------------------------------- /man/figures/example_app_filled.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashbaldry/designer/HEAD/man/figures/example_app_filled.jpeg -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Shiny UI Builder 2 | Description: Design your shiny UI. 3 | Binding: designApp 4 | Interactive: true 5 | -------------------------------------------------------------------------------- /tests/testthat/test-template_utils.R: -------------------------------------------------------------------------------- 1 | test_that("create_random_id creates valid random ID", { 2 | expect_match(create_random_id(), "^[a-z]{10}$") 3 | }) 4 | -------------------------------------------------------------------------------- /srcjs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /inst/utils/golem-config.yml: -------------------------------------------------------------------------------- 1 | default: 2 | golem_name: designer 3 | golem_version: 0.1.0 4 | app_prod: no 5 | production: 6 | app_prod: yes 7 | dev: 8 | golem_wd: !expr here::here() 9 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/Page.test.js: -------------------------------------------------------------------------------- 1 | import { Page } from '../Page' 2 | 3 | test('sanity test - Page constructs successfully', () => { 4 | const page = new Page() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Box.test.js: -------------------------------------------------------------------------------- 1 | import { Box } from '../Box' 2 | 3 | test('sanity test - box constructs successfully', () => { 4 | const component = new Box() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Row.test.js: -------------------------------------------------------------------------------- 1 | import { Row } from '../Row' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Row() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Tab.test.js: -------------------------------------------------------------------------------- 1 | import { Tab } from '../Tab' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Tab() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /inst/golem-config.yml: -------------------------------------------------------------------------------- 1 | default: 2 | golem_name: shinyexample 3 | golem_version: 0.0.0.9000 4 | app_prod: no 5 | 6 | production: 7 | app_prod: yes 8 | 9 | dev: 10 | golem_wd: !expr golem::pkg_path() 11 | 12 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Text.test.js: -------------------------------------------------------------------------------- 1 | import { Text } from '../Text' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Text() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/FillPage.test.js: -------------------------------------------------------------------------------- 1 | import { FillPage } from '../FillPage' 2 | 3 | test('sanity test - FillPage constructs successfully', () => { 4 | const page = new FillPage() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Button.test.js: -------------------------------------------------------------------------------- 1 | import { Button } from '../Button' 2 | 3 | test('sanity test - button constructs successfully', () => { 4 | const component = new Button() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Column.test.js: -------------------------------------------------------------------------------- 1 | import { Column } from '../Column' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Column() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Header.test.js: -------------------------------------------------------------------------------- 1 | import { Header } from '../Header' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Header() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Input.test.js: -------------------------------------------------------------------------------- 1 | import { Input } from '../Input' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Input() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Output.test.js: -------------------------------------------------------------------------------- 1 | import { Output } from '../Output' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Output() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Tabset.test.js: -------------------------------------------------------------------------------- 1 | import { Tabset } from '../Tabset' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new Tabset() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/BasicPage.test.js: -------------------------------------------------------------------------------- 1 | import { BasicPage } from '../BasicPage' 2 | 3 | test('sanity test - BasicPage constructs successfully', () => { 4 | const page = new BasicPage() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/FixedPage.test.js: -------------------------------------------------------------------------------- 1 | import { FixedPage } from '../FixedPage' 2 | 3 | test('sanity test - FixedPage constructs successfully', () => { 4 | const page = new FixedPage() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/FluidPage.test.js: -------------------------------------------------------------------------------- 1 | import { FluidPage } from '../FluidPage' 2 | 3 | test('sanity test - FluidPage constructs successfully', () => { 4 | const page = new FluidPage() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Callout.test.js: -------------------------------------------------------------------------------- 1 | import { Callout } from '../Callout' 2 | 3 | test('sanity test - callout constructs successfully', () => { 4 | const component = new Callout() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/InfoBox.test.js: -------------------------------------------------------------------------------- 1 | import { InfoBox } from '../InfoBox' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new InfoBox() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/UserBox.test.js: -------------------------------------------------------------------------------- 1 | import { UserBox } from '../UserBox' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new UserBox() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/NavbarPage.test.js: -------------------------------------------------------------------------------- 1 | import { NavbarPage } from '../NavbarPage' 2 | 3 | test('sanity test - NavbarPage constructs successfully', () => { 4 | const page = new NavbarPage() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /R/designer.R: -------------------------------------------------------------------------------- 1 | #' @description 2 | #' To learn more about \code{designer}, have a read of the vignette: \code{vignette("designer")} 3 | #' 4 | #' @import shiny 5 | #' @importFrom stats setNames 6 | #' 7 | #' @keywords internal 8 | "_PACKAGE" 9 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/ValueBox.test.js: -------------------------------------------------------------------------------- 1 | import { ValueBox } from '../ValueBox' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new ValueBox() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/DateInput.test.js: -------------------------------------------------------------------------------- 1 | import { DateInput } from '../DateInput' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new DateInput() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/FileInput.test.js: -------------------------------------------------------------------------------- 1 | import { FileInput } from '../FileInput' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new FileInput() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/BlockQuote.test.js: -------------------------------------------------------------------------------- 1 | import { BlockQuote } from '../BlockQuote' 2 | 3 | test('sanity test - block quote constructs successfully', () => { 4 | const component = new BlockQuote() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/Checkbox.test.js: -------------------------------------------------------------------------------- 1 | import { CheckboxInput } from '../Checkbox' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new CheckboxInput() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/InputPanel.test.js: -------------------------------------------------------------------------------- 1 | import { InputPanel } from '../InputPanel' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new InputPanel() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/SelectInput.test.js: -------------------------------------------------------------------------------- 1 | import { SelectInput } from '../SelectInput' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new SelectInput() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/SliderInput.test.js: -------------------------------------------------------------------------------- 1 | import { SliderInput } from '../SliderInput' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new SliderInput() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/BootstrapPage.test.js: -------------------------------------------------------------------------------- 1 | import { BootstrapPage } from '../BootstrapPage' 2 | 3 | test('sanity test - BootstrapPage constructs successfully', () => { 4 | const page = new BootstrapPage() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/__tests__/DashboardPage.test.js: -------------------------------------------------------------------------------- 1 | import { DashboardPage } from '../DashboardPage' 2 | 3 | test('sanity test - DashboardPage constructs successfully', () => { 4 | const page = new DashboardPage() 5 | expect(page).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /srcjs/page/BasicPage.js: -------------------------------------------------------------------------------- 1 | import { Page } from './Page' 2 | 3 | export class BasicPage extends Page { 4 | name = 'basicPage' 5 | enable_on_load = false 6 | page_html = '
' 7 | }; 8 | -------------------------------------------------------------------------------- /srcjs/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es2021: true 4 | extends: standard 5 | overrides: [] 6 | parserOptions: 7 | ecmaVersion: latest 8 | sourceType: module 9 | rules: { 10 | "no-undef": "off", 11 | "camelcase": "off" 12 | } 13 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/CheckboxGroup.test.js: -------------------------------------------------------------------------------- 1 | import { CheckboxGroupInput } from '../CheckboxGroup' 2 | 3 | test('sanity test - checkbox constructs successfully', () => { 4 | const component = new CheckboxGroupInput() 5 | expect(component).toBeDefined() 6 | }) 7 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(designApp) 4 | import(shiny) 5 | importFrom(stats,setNames) 6 | importFrom(utils,packageVersion) 7 | importFrom(utils,read.csv) 8 | importFrom(utils,write.csv) 9 | importFrom(utils,write.table) 10 | -------------------------------------------------------------------------------- /srcjs/build.js: -------------------------------------------------------------------------------- 1 | import { build } from 'esbuild' 2 | 3 | build({ 4 | entryPoints: ['app/index.js'], 5 | bundle: true, 6 | sourcemap: true, 7 | outfile: '../inst/app/www/designer.min.js', 8 | platform: 'node', 9 | minify: true 10 | }).catch( 11 | () => process.exit(1) 12 | ) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-ui-modules.R: -------------------------------------------------------------------------------- 1 | test_that("All Module UI generates with no errors", { 2 | module_ui_funcs <- ls(getNamespace("designer"), pattern = "ModUI") 3 | for (mod_ui_func in module_ui_funcs) { 4 | mod_ui <- get(mod_ui_func)(NULL) 5 | expect_type(mod_ui, "list") 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /srcjs/build_dev.js: -------------------------------------------------------------------------------- 1 | import { build } from 'esbuild' 2 | 3 | build({ 4 | entryPoints: ['app/index.js'], 5 | bundle: true, 6 | sourcemap: true, 7 | outfile: '../inst/app/www/designer.min.js', 8 | platform: 'node', 9 | minify: false 10 | }).catch( 11 | () => process.exit(1) 12 | ) 13 | -------------------------------------------------------------------------------- /app.R: -------------------------------------------------------------------------------- 1 | # Launch the ShinyApp (Do not remove this comment) 2 | # To deploy, run: rsconnect::deployApp() 3 | # Or use the blue button on top of this file 4 | 5 | pkgload::load_all( 6 | export_all = FALSE, 7 | helpers = FALSE, 8 | attach_testthat = FALSE 9 | ) 10 | 11 | options("golem.app.prod" = TRUE) 12 | designApp() 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .Rdata 6 | .httr-oauth 7 | .DS_Store 8 | dev 9 | # {shinytest2}: Ignore new debug snapshots for `$expect_values()` 10 | *_.new.png 11 | inst/doc 12 | /doc/ 13 | /Meta/ 14 | docs 15 | srcjs/node_modules 16 | shiny_bookmarks 17 | cran-comments.md 18 | CRAN-SUBMISSION 19 | srcjs/coverage 20 | -------------------------------------------------------------------------------- /R/mod_options_utils.R: -------------------------------------------------------------------------------- 1 | CSSFileInput <- function(id, label) { 2 | div( 3 | class = "form-group setting-input", 4 | tags$label(label), 5 | div( 6 | class = "custom-file", 7 | tags$input(id = id, type = "file", accept = ".css"), 8 | tags$label(class = "custom-file-label", `for` = id, "Choose file") 9 | ) 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/test-template_server.R: -------------------------------------------------------------------------------- 1 | test_that("TemplateModuleServer loads successfully", { 2 | shiny::testServer( 3 | TemplateModuleServer, 4 | args = list( 5 | html = reactiveVal(), 6 | page = reactive("fluidPage") 7 | ), 8 | expr = { 9 | expect_error(session$setInputs(save_button = 1L), NA) 10 | } 11 | ) 12 | }) 13 | -------------------------------------------------------------------------------- /srcjs/page/FillPage.js: -------------------------------------------------------------------------------- 1 | import { Page } from './Page' 2 | 3 | export class FillPage extends Page { 4 | name = 'fillPage' 5 | page_html = ` 6 | 9 | ` 10 | }; 11 | -------------------------------------------------------------------------------- /srcjs/page/BootstrapPage.js: -------------------------------------------------------------------------------- 1 | import { Page } from './Page' 2 | 3 | export class BootstrapPage extends Page { 4 | name = 'bootstrapPage' 5 | page_html = ` 6 | 8 | ` 9 | }; 10 | -------------------------------------------------------------------------------- /srcjs/page/FixedPage.js: -------------------------------------------------------------------------------- 1 | import { Page } from './Page' 2 | 3 | export class FixedPage extends Page { 4 | name = 'fixedPage' 5 | page_html = ` 6 | 9 | ` 10 | }; 11 | -------------------------------------------------------------------------------- /srcjs/page/FluidPage.js: -------------------------------------------------------------------------------- 1 | import { Page } from './Page' 2 | 3 | export class FluidPage extends Page { 4 | name = 'fluidPage' 5 | page_html = ` 6 | 9 | ` 10 | }; 11 | -------------------------------------------------------------------------------- /.github/workflows/jest.yaml: -------------------------------------------------------------------------------- 1 | name: Run JS Tests (Jest) 2 | on: 3 | push: 4 | branches: [dev, main, master] 5 | pull_request: 6 | branches: [dev, main, master] 7 | defaults: 8 | run: 9 | working-directory: srcjs 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Install modules 16 | run: npm install 17 | - name: Run tests 18 | run: npm run test 19 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^renv$ 2 | ^renv\.lock$ 3 | ^.*\.Rproj$ 4 | ^\.Rproj\.user$ 5 | ^dev$ 6 | ^\.github$ 7 | ^app\.R$ 8 | ^rsconnect$ 9 | _\.new\.png$ 10 | ^R/_disable_autoload\.R$ 11 | ^man/figures/example_app\.gif$ 12 | ^doc$ 13 | ^Meta$ 14 | ^_pkgdown\.yml$ 15 | ^docs$ 16 | ^pkgdown$ 17 | ^CRAN-RELEASE$ 18 | ^cran-comments\.md$ 19 | ^srcjs$ 20 | ^Dockerfile$ 21 | ^\.lintr$ 22 | ^shiny_bookmarks$ 23 | ^CRAN-SUBMISSION$ 24 | ^\.dockerignore$ 25 | ^manifest\.json$ 26 | -------------------------------------------------------------------------------- /srcjs/page/init.js: -------------------------------------------------------------------------------- 1 | import { selectPage, changePageCheck, createPage, updateTitle, revertPageSelection } from './utils' 2 | 3 | export function initPage () { 4 | createPage() 5 | $('.canvas-page-choice').on('click', selectPage) 6 | $('#settings-page_type').on('change', changePageCheck) 7 | $('#cancel_reset').on('click', revertPageSelection) 8 | $('#confirm_reset').on('click', createPage) 9 | 10 | $('#app_name').on('change keyup', updateTitle) 11 | }; 12 | -------------------------------------------------------------------------------- /srcjs/component/__tests__/utils.test.js: -------------------------------------------------------------------------------- 1 | import { getComponent } from '../utils' 2 | import { Header } from '../Header' 3 | import { Box } from '../Box' 4 | 5 | test('getComponent by default returns a header', () => { 6 | expect(getComponent()).toBeInstanceOf(Header) 7 | }) 8 | 9 | test('getComponent returns correctly specified component', () => { 10 | expect(getComponent('header')).toBeInstanceOf(Header) 11 | expect(getComponent('box')).toBeInstanceOf(Box) 12 | }) 13 | -------------------------------------------------------------------------------- /designer.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace,vignette 22 | -------------------------------------------------------------------------------- /rsconnect/shinyapps.io/ashbaldry/designer.dcf: -------------------------------------------------------------------------------- 1 | name: designer 2 | title: designer 3 | username: 4 | account: ashbaldry 5 | server: shinyapps.io 6 | hostUrl: https://api.shinyapps.io/v1 7 | appId: 6009870 8 | bundleId: 6715028 9 | url: https://ashbaldry.shinyapps.io/designer/ 10 | when: 1673300635.97561 11 | lastSyncTime: 1673300635.97562 12 | asMultiple: FALSE 13 | asStatic: FALSE 14 | ignoredFiles: .lintr|.Rbuildignore|_pkgdown.yml|Dockerfile|NEWS.md|README.md|.github|dev|shiny_bookmarks|srcjs|tests|vignettes 15 | -------------------------------------------------------------------------------- /srcjs/component/Header.js: -------------------------------------------------------------------------------- 1 | import { Component } from './Component' 2 | 3 | export class Header extends Component { 4 | html = '<$tag$ class="designer-element" data-shinyfunction="$tag$">$value$$tag$>' 5 | 6 | constructor () { 7 | super() 8 | this.updateComponent(true) 9 | } 10 | 11 | createComponent () { 12 | const tag = $('#sidebar-header-tag').val() 13 | const value = $('#sidebar-header-text').val() 14 | return this.replaceHTMLPlaceholders(this.html, { tag, value }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /R/mod_canvas_srv.R: -------------------------------------------------------------------------------- 1 | #' Canvas Server Functions 2 | #' 3 | #' @importFrom utils packageVersion 4 | #' @noRd 5 | CanvasModuleServer <- function(id, selected_template) { 6 | moduleServer(id, function(input, output, session) { 7 | setBookmarkExclude(c("html", "canvas", "screenshot")) 8 | 9 | observeEvent(selected_template(), { 10 | session$sendInputMessage("html", selected_template()) 11 | }) 12 | 13 | return(list( 14 | ui_code = reactive(input$canvas), 15 | html = reactive(input$html) 16 | )) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /tests/testthat/test-golem-recommended.R: -------------------------------------------------------------------------------- 1 | test_that("app ui", { 2 | ui <- appUI() 3 | golem::expect_shinytaglist(ui) 4 | # Check that formals have not been removed 5 | fmls <- formals(appUI) 6 | for (i in "request") { 7 | expect_true(i %in% names(fmls)) 8 | } 9 | }) 10 | 11 | test_that("app server", { 12 | server <- appServer 13 | expect_type(server, "closure") 14 | # Check that formals have not been removed 15 | fmls <- formals(appServer) 16 | for (i in c("input", "output", "session")) { 17 | expect_true(i %in% names(fmls)) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /srcjs/app/screenshot.js: -------------------------------------------------------------------------------- 1 | export function screenshotSettings () { 2 | Shiny.addCustomMessageHandler('prepare_canvas_screenshot', (message) => { 3 | $('.designer-page-template').addClass('hidden-after-label') 4 | $('.designer-page-template').addClass('hidden-colour') 5 | $('.designer-page-template').addClass('hidden-borders') 6 | }) 7 | 8 | Shiny.addCustomMessageHandler('revert_canvas_screenshot', (message) => { 9 | $('#remove_label').trigger('change') 10 | $('#remove_colour').trigger('change') 11 | $('#remove_border').trigger('change') 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /srcjs/component/Row.js: -------------------------------------------------------------------------------- 1 | import { Component } from './Component' 2 | 3 | export class Row extends Component { 4 | updatable = false 5 | html = '' 6 | 7 | constructor (update_component = true) { 8 | super() 9 | 10 | if (update_component) { 11 | this.updateComponent(true) 12 | } 13 | } 14 | 15 | sortable_settings = { 16 | group: { 17 | name: 'shared', 18 | put: function (_to, _from, clone) { 19 | return clone.classList.contains('col-sm') 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/testthat/test-cache.R: -------------------------------------------------------------------------------- 1 | test_that("finding the cache directory creates one if necessary", { 2 | cache_dir <- find_cache_dir() 3 | expect_true(dir.exists(cache_dir)) 4 | }) 5 | 6 | test_that("cache dir can use custom directory", { 7 | temp_dir <- tempdir() 8 | good_dir <- file.path(temp_dir, "good_dir") 9 | dir.create(good_dir) 10 | Sys.setenv(R_DESIGNER_CACHE = good_dir) 11 | 12 | on.exit({ 13 | Sys.setenv(R_DESIGNER_CACHE = "") 14 | unlink(temp_dir, recursive = TRUE, force = TRUE) 15 | }) 16 | 17 | cache_dir <- find_cache_dir() 18 | expect_identical(cache_dir, good_dir) 19 | }) 20 | -------------------------------------------------------------------------------- /srcjs/component/BlockQuote.js: -------------------------------------------------------------------------------- 1 | import { Component } from './Component' 2 | 3 | export class BlockQuote extends Component { 4 | html = '$value$' 5 | 6 | constructor () { 7 | super() 8 | this.updateComponent(true) 9 | } 10 | 11 | createComponent () { 12 | const colour = $('#sidebar-quote-colour').val() 13 | const value = $('#sidebar-quote-textarea').val() 14 | 15 | return this.replaceHTMLPlaceholders(this.html, { colour, value }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /srcjs/input/canvas-page-input.js: -------------------------------------------------------------------------------- 1 | import { htmlToJSON } from './utils' 2 | 3 | export const canvasPageBinding = new Shiny.InputBinding() 4 | 5 | $.extend(canvasPageBinding, { 6 | find: function (scope) { 7 | return $(scope).find('.page-canvas') 8 | }, 9 | getValue: function (el) { 10 | return htmlToJSON(document.getElementById('canvas-page')) 11 | }, 12 | subscribe: function (el, callback) { 13 | const observer = new MutationObserver(function () { callback() }) 14 | observer.observe(el, { subtree: true, childList: true, attributes: true }) 15 | }, 16 | unsubscribe: function (el) { 17 | $(el).off('.page-canvas') 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /srcjs/component/InputPanel.js: -------------------------------------------------------------------------------- 1 | import { Component } from './Component' 2 | 3 | export class InputPanel extends Component { 4 | updatable = false 5 | html = '' 6 | 7 | constructor (update_component = true) { 8 | super() 9 | 10 | if (update_component) { 11 | this.updateComponent(true) 12 | } 13 | } 14 | 15 | sortable_settings = { 16 | group: { 17 | name: 'shared', 18 | put: function (_to, _from, clone) { 19 | return clone.classList.contains('form-group') || clone.classList.contains('btn') 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /R/mod_canvas_utils.R: -------------------------------------------------------------------------------- 1 | safari_circle <- function(cx, fill, stroke) { 2 | tags$circle( 3 | cx = cx, 4 | cy = "5", 5 | r = "6", 6 | fill = fill, 7 | stroke = stroke, 8 | `stroke-width` = ".5" 9 | ) 10 | } 11 | 12 | createPageItem <- function(page_index) { 13 | page_type <- names(PAGE_TYPES)[page_index] 14 | page_id <- unname(PAGE_TYPES[page_index]) 15 | page_desc <- PAGE_DESCRIPTIONS[page_type] 16 | 17 | tags$button( 18 | class = "btn btn-secondary canvas-page-choice", 19 | type = "button", 20 | `data-page` = page_id, 21 | tags$h4(class = "canvas-choice-header", page_type), 22 | tags$p(class = "canvas-choice-content", page_desc) 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /R/app_server.R: -------------------------------------------------------------------------------- 1 | #' The application server-side 2 | #' 3 | #' @param input,output,session Internal parameters for {shiny}. 4 | #' 5 | #' @noRd 6 | appServer <- function(input, output, session) { 7 | setBookmarkExclude(c( 8 | "app_name", "remove_border", "remove_label", "remove_colour", "help", "css_style", "screenshot" 9 | )) 10 | 11 | observeEvent(input$help, guide$init()$start()) 12 | 13 | template_html <- reactiveVal() 14 | 15 | page_html <- CanvasModuleServer("canvas", selected_template = template_html) 16 | 17 | selected_template <- SettingsModuleServer("settings", ui_code = page_html) 18 | 19 | observeEvent(selected_template(), template_html(selected_template())) 20 | 21 | SidebarModuleServer("sidebar") 22 | } 23 | -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults( 2 | cyclocomp_linter = NULL, 3 | line_length_linter(120), 4 | implicit_integer_linter(), 5 | object_name_linter(c("snake_case", "camelCase", "CamelCase", "SNAKE_CASE", "symbols")), 6 | any_duplicated_linter(), 7 | any_is_na_linter(), 8 | class_equals_linter(), 9 | condition_message_linter(), 10 | conjunct_test_linter(), 11 | duplicate_argument_linter(), 12 | expect_comparison_linter(), 13 | expect_length_linter(), 14 | expect_length_linter(), 15 | expect_not_linter(), 16 | expect_null_linter(), 17 | redundant_ifelse_linter(), 18 | undesirable_operator_linter(), 19 | unneeded_concatenation_linter(), 20 | unreachable_code_linter(), 21 | yoda_test_linter() 22 | ) 23 | -------------------------------------------------------------------------------- /man/component.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mod_sidebar_components.R 3 | \name{component} 4 | \alias{component} 5 | \alias{componentTab} 6 | \title{Component Settings Shell} 7 | \usage{ 8 | component(id, ...) 9 | 10 | componentTab(id) 11 | } 12 | \arguments{ 13 | \item{id}{The ID of the component input} 14 | 15 | \item{...}{Shiny tags to include inside the component} 16 | } 17 | \value{ 18 | A shiny.tag of the component settings 19 | } 20 | \description{ 21 | A container for the specified component input 22 | } 23 | \details{ 24 | The tab component contains a selection of specific inputs related to adding a new tab, as 25 | the events to create it in the UI are different to the other components 26 | } 27 | -------------------------------------------------------------------------------- /srcjs/component/Text.js: -------------------------------------------------------------------------------- 1 | import { Component } from './Component' 2 | 3 | export class Text extends Component { 4 | html = '<$tag$ class="designer-element" data-shinyfunction="tags$$tag$">$value$$tag$>' 5 | 6 | constructor () { 7 | super() 8 | this.updateComponent(true) 9 | } 10 | 11 | createComponent () { 12 | const tag = $('#sidebar-text-tag').val() 13 | const value = $('#sidebar-text-textarea').val() 14 | const contents = tag === 'p' ? value.replace(/\n/g, ' ') : this.createListItems(value) 15 | 16 | return this.replaceHTMLPlaceholders(this.html, { tag, value: contents }) 17 | } 18 | 19 | createListItems (text) { 20 | return text.split('\n').map(x => '
12 | $label$ 13 |
14 |
9 |
10 | `{designer}` is intended to make the initial generation of a UI wireframe of a shiny application as quick and simple as possible.
11 |
12 | The package contains a `shiny` application that enables the user to build the UI of a `shiny` application by drag and dropping several `shiny` components - such as inputs, outputs and buttons - into one of the available pages in the `{shiny}` package. Once finalised, the R code used to generate the UI can be copied or downloaded to a `ui.R` file, and then the rest of the application like the server logic and styling can be built by the developer.
13 |
14 | The drag-and-drop nature of the application means that it is easy for both R and non-R users to collaborate in designing the UI of their shiny application. Comments can be added to any component so that it is simple to remember what should be included for each input/output.
15 |
16 | ## Installation
17 |
18 | Install from [CRAN](https://cran.r-project.org/package=designer) with:
19 |
20 | ``` r
21 | install.packages("designer")
22 | ```
23 |
24 | Or install the development version from [GitHub](https://github.com/ashbaldry/designer) with:
25 |
26 | ``` r
27 | devtools::install_github("ashbaldry/designer")
28 | ```
29 |
30 | The application is also available on-line through [shinyapps.io](https://ashbaldry.shinyapps.io/designer).
31 |
32 | ## Usage
33 |
34 | To open the `{designer}` application and create your own UI, run the following code:
35 |
36 | ``` r
37 | designer::designApp()
38 | ```
39 |
40 | Alternatively, you can launch the addin via the RStudio menu.
41 |
42 | 
43 |
44 | Once opened, create the application as required until you are happy with the layout of the application, then copy the code to the relevant R file
45 |
46 | 
47 |
48 | ``` r
49 | # ui.R
50 |
51 | bootstrapPage(
52 | title = "Shiny Application",
53 | theme = bslib::bs_theme(4),
54 | h1(
55 | "My shiny application"
56 | ),
57 | inputPanel(
58 | selectInput(
59 | inputId = "dropdown_gxc2o1ekgb",
60 | label = "Label",
61 | choices = "..."
62 | ),
63 | selectInput(
64 | inputId = "dropdown_azset57v65",
65 | label = "Label",
66 | choices = "..."
67 | ),
68 | selectInput(
69 | inputId = "dropdown_itgcle8yze",
70 | label = "Label",
71 | choices = "..."
72 | )
73 | ),
74 | fluidRow(
75 | column(
76 | width = 6,
77 | # Bar plot
78 | plotOutput(
79 | outputId = "plot_zvu8c9upbu",
80 | height = "200px"
81 | )
82 | ),
83 | column(
84 | width = 6,
85 | # Line chart
86 | plotOutput(
87 | outputId = "plot_qsmfr0lp57",
88 | height = "200px"
89 | )
90 | )
91 | )
92 | )
93 | ```
94 |
95 | ## Docker
96 |
97 | Optionally, you can also build a Dockerized version of the app:
98 |
99 | ```
100 | sudo docker build -t designer -f Dockerfile .
101 | ```
102 |
103 | After building the docker image (which should take a while) use the command:
104 |
105 | ```
106 | docker run -p 80:80 designer
107 | ```
108 |
109 | Depending on your Docker set-up, the exposed application may be available under: http://localhost:80
110 |
111 | ## Sharing Designs
112 |
113 | Once you are ready with your initial design, you can share it with others using Code -> Share. This will generate a URL that when opened by another person (or yourself in the future) will show the saved state of the design and then can be added onto and saved again - this will generate a new URL to share.
114 |
115 | **NB** For bookmarking to work, the server the `{designer}` application sits on must be configured to allow sessions to be saved on the disk. As the saved state only saves the HTML and a couple of inputs, each saved state is generally only a couple of kilobytes.
116 |
117 | ## Notes
118 |
119 | Certain inputs will only include default values and not fully customisable; this is intentional as they are likely to change throughout development and therefore not something that is required at this time of the development process.
120 |
--------------------------------------------------------------------------------
/vignettes/designer.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Prototyping Your UI with `designer`"
3 | date: "`r Sys.Date()`"
4 | output: rmarkdown::html_vignette
5 | vignette: >
6 | %\VignetteIndexEntry{Prototyping Your UI with `designer`}
7 | %\VignetteEngine{knitr::rmarkdown}
8 | %\VignetteEncoding{UTF-8}
9 | ---
10 |
11 | `designer` is intended to make the initial generation of a UI wireframe of a shiny application as quick and simple as possible.
12 |
13 | The package contains a `shiny` application that enables the user to build the UI of a `shiny` application by drag and dropping several `shiny` components - such as inputs, outputs and buttons - into one of the available pages in the `{shiny}` package. Once finalised, the R code used to generate the UI can be copied or downloaded to a `ui.R` file, and then the rest of the application like the server logic and styling can be built by the developer.
14 |
15 | The drag-and-drop nature of the application means that it is easy for both R and non-R users to collaborate in designing the UI of their shiny application. Comments can be added to any component so that it is simple to remember what should be included for each input/output.
16 |
17 | To run the application, use `designer::designApp()` or select "Shiny UI Builder" in the Addins.
18 |
19 | ---
20 |
21 | ## Application
22 |
23 | There are several steps in creating the desired application UI:
24 |
25 | ### Page
26 |
27 | First is the choice of page.
28 |
29 | - __Standard Page__ is the most commonly used page in shiny applications.
30 | - __Navigation Bar Page__ is useful when creating multi-page applications.
31 | - __Dashboard Page__ can be used to replicate the `{bs4Dash}` dashboard page.
32 | - __Fluid Page__ takes advantage of the rows and columns to align content.
33 | - __Fill Page__ and __Fixed Page__ are included for cases where the developer has more broad knowledge of HTML and CSS and will adapt the application more once the wireframe is created.
34 |
35 | ### Components
36 |
37 | Next is adding the components to the page. A list of available components mentioned below. When creating components the `{shiny}` function parameters that can affect the look/layout of the UI (e.g. width and labels) are available to customise, but the more server-logic related parameters (e.g. dropdown choices) are left to the application developer later on.
38 |
39 | | Component | `{shiny}` Function | Description |
40 | | -------------- | --------------------------- | ---------------------------------------------- |
41 | | Tab | `tabPanel` | (`navbarPage` only) Adding/Removing a tab |
42 | | Header | `h1` to `h6` | |
43 | | Row | `fluidRow` | |
44 | | Column | `column` | |
45 | | Text | `p`, `ol`, `ul` | Adding text or a list to a page |
46 | | Input Panel | `inputPanel` | |
47 | | Dropdown | `selectInput` | |
48 | | Input | `textInput`, `numericInput`, `textAreaInput`, `passwordInput` | |
49 | | Slider | `sliderInput` | |
50 | | File Input | `fileInput` | |
51 | | Calendar | `dateInput`, `dateRangeInput` | |
52 | | Checkbox | `checkboxInput` | |
53 | | Radio Buttons | `checkboxInput` | |
54 | | Button | `actionButton` | |
55 | | Output | `textOutput`, `verbatimTextOutput`, `plotOutput`, `imageOutput`, `DTOutput`, `uiOutput` | |
56 |
57 | #### `bs4Dash` Components
58 |
59 | | Component | `{bs4Dash}` Function | Description |
60 | | -------------- | --------------------------- | ---------------------------------------------- |
61 | | Tab | `tabItem`, `bs4TabItem` | Adding/Removing a tab |
62 | | Box/Card | `box`, `bs4Card` | |
63 | | User Box/Card | `userBox`, `bs4UserCard` | |
64 | | Info Box | `infoBox`, `bs4InfoBox` | |
65 | | Value Box | `valueBox`, `bs4ValueBox` | |
66 | | Block Quote | `blockQuote` | |
67 | | Callout | `callout` | |
68 |
69 | #### Notes
70 |
71 | A few layout rules have been implemented into the application to try and match the Bootstrap UI framework that aren't always checked in `{shiny}`.
72 |
73 | - Columns can only be added to rows. This matches the [grid system](https://getbootstrap.com/docs/4.6/layout/grid/) Bootstrap have used (which is based off flexbox).
74 | - For a similar reason rows are the only component that can be directly added to columns. Anything can be added into a row (even more columns)
75 | - The only components that are allowed in input panels are inputs and buttons.
76 |
77 | ### Saving
78 |
79 | Once the wireframe is complete, then there is the ability to save the code, either by downloading the file or copying the code. There is also the opportunity to take a screenshot to annotate further if required.
80 |
81 | ## Sharing Designs
82 |
83 | Alternatively you can share it with others using Templates -> Save -> Share. This will generate a URL that when opened by another person (or yourself in the future) will show the saved state of the design and then can be added onto and saved again - this will generate a new URL to share.
84 |
85 | ### Extras
86 |
87 | There are some development tools that have been enabled upon start-up that can be removed to preview the UI as the end user would see the application, such as borders around all components, colouring some empty components like columns and rows, and removing component names from the UI.
88 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards
42 | of acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies
54 | when an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail
56 | address, posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at [INSERT CONTACT
63 | METHOD]. All complaints will be reviewed and investigated promptly and fairly.
64 |
65 | All community leaders are obligated to respect the privacy and security of the
66 | reporter of any incident.
67 |
68 | ## Enforcement Guidelines
69 |
70 | Community leaders will follow these Community Impact Guidelines in determining
71 | the consequences for any action they deem in violation of this Code of Conduct:
72 |
73 | ### 1. Correction
74 |
75 | **Community Impact**: Use of inappropriate language or other behavior deemed
76 | unprofessional or unwelcome in the community.
77 |
78 | **Consequence**: A private, written warning from community leaders, providing
79 | clarity around the nature of the violation and an explanation of why the
80 | behavior was inappropriate. A public apology may be requested.
81 |
82 | ### 2. Warning
83 |
84 | **Community Impact**: A violation through a single incident or series of
85 | actions.
86 |
87 | **Consequence**: A warning with consequences for continued behavior. No
88 | interaction with the people involved, including unsolicited interaction with
89 | those enforcing the Code of Conduct, for a specified period of time. This
90 | includes avoiding interactions in community spaces as well as external channels
91 | like social media. Violating these terms may lead to a temporary or permanent
92 | ban.
93 |
94 | ### 3. Temporary Ban
95 |
96 | **Community Impact**: A serious violation of community standards, including
97 | sustained inappropriate behavior.
98 |
99 | **Consequence**: A temporary ban from any sort of interaction or public
100 | communication with the community for a specified period of time. No public or
101 | private interaction with the people involved, including unsolicited interaction
102 | with those enforcing the Code of Conduct, is allowed during this period.
103 | Violating these terms may lead to a permanent ban.
104 |
105 | ### 4. Permanent Ban
106 |
107 | **Community Impact**: Demonstrating a pattern of violation of community
108 | standards, including sustained inappropriate behavior, harassment of an
109 | individual, or aggression toward or disparagement of classes of individuals.
110 |
111 | **Consequence**: A permanent ban from any sort of public interaction within the
112 | community.
113 |
114 | ## Attribution
115 |
116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
117 | version 2.0,
118 | available at https://www.contributor-covenant.org/version/2/0/
119 | code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at https://
128 | www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/R/mod_template_srv.R:
--------------------------------------------------------------------------------
1 | #' @noRd
2 | TemplateModuleServer <- function(id, html, page) {
3 | moduleServer(id, function(input, output, session) {
4 | ns <- session$ns
5 | shared_template_id <- reactiveVal()
6 |
7 | #### Bookmarking ####
8 | setBookmarkExclude(c(
9 | "title", "description", "author",
10 | "save", "save_share",
11 | "overwrite", "overwrite_share",
12 | "existing_template", "save_button", "select",
13 | "delete", "confirm_delete", "search"
14 | ))
15 |
16 | onBookmark(function(state) {
17 | state$values$template <- shared_template_id()
18 | })
19 | onRestore(function(state) {
20 | session$sendCustomMessage(
21 | "runjs",
22 | list(
23 | script = paste0(
24 | "document.querySelector(\".template-option[data-value='", state$values$template, "']\").click()"
25 | )
26 | )
27 | )
28 | })
29 |
30 | #### Modal ####
31 | observeEvent(input$save_button, {
32 | existing_templates <- get_template_index()
33 |
34 | showModal(
35 | modalDialog(
36 | tags$form(
37 | tags$fieldset(
38 | tags$legend("Save Template"),
39 | textInput(ns("title"), "Title", width = "100%"),
40 | textInput(ns("author"), "Author", width = "100%"),
41 | textAreaInput(ns("description"), "Description (optional)", rows = 2L, width = "100%"),
42 | tags$button(
43 | type = "button",
44 | class = "btn btn-secondary",
45 | `data-dismiss` = "modal",
46 | `data-bs-dismiss` = "modal",
47 | "Cancel"
48 | ),
49 | tags$button(
50 | id = ns("save"),
51 | type = "button",
52 | class = "btn btn-primary action-button",
53 | `data-dismiss` = "modal",
54 | `data-bs-dismiss` = "modal",
55 | "Save"
56 | ),
57 | tags$button(
58 | id = ns("save_share"),
59 | type = "button",
60 | class = "btn btn-primary action-button",
61 | `data-dismiss` = "modal",
62 | `data-bs-dismiss` = "modal",
63 | "Share",
64 | shiny::icon("share")
65 | )
66 | ),
67 | if (nrow(existing_templates) > 0L) {
68 | tagList(
69 | tags$hr(),
70 | tags$fieldset(
71 | tags$legend("Overwrite Existing Template"),
72 | selectInput(
73 | ns("existing_template"),
74 | "Template",
75 | choices = setNames(
76 | existing_templates$id,
77 | paste(existing_templates$title, "-", existing_templates$user)
78 | ),
79 | selected = NULL,
80 | width = "100%"
81 | ),
82 | tags$button(
83 | type = "button",
84 | class = "btn btn-secondary",
85 | `data-dismiss` = "modal",
86 | `data-bs-dismiss` = "modal",
87 | "Cancel"
88 | ),
89 | tags$button(
90 | id = ns("overwrite"),
91 | type = "button",
92 | class = "btn btn-primary action-button",
93 | `data-dismiss` = "modal",
94 | `data-bs-dismiss` = "modal",
95 | "Overwrite"
96 | ),
97 | tags$button(
98 | id = ns("overwrite_share"),
99 | type = "button",
100 | class = "btn btn-primary action-button",
101 | `data-dismiss` = "modal",
102 | `data-bs-dismiss` = "modal",
103 | "Share",
104 | shiny::icon("share")
105 | )
106 | )
107 | )
108 | }
109 | ),
110 | title = NULL,
111 | footer = NULL,
112 | easyClose = TRUE
113 | )
114 | )
115 | })
116 |
117 | #### Saving ####
118 | saved_template_id <- reactiveVal()
119 |
120 | observe({
121 | req(input$title, input$author)
122 |
123 | id <- save_template(
124 | html = html(),
125 | page = page(),
126 | title = input$title,
127 | desc = input$description,
128 | user = input$author
129 | )
130 | saved_template_id(id)
131 |
132 | insertUI(
133 | selector = paste0("#", ns("select")),
134 | where = "beforeEnd",
135 | ui = createTemplateSelection(
136 | list(
137 | id = id,
138 | page = page(),
139 | user = input$author,
140 | title = input$title,
141 | description = input$description
142 | )
143 | )
144 | )
145 | }) |>
146 | bindEvent(
147 | input$save,
148 | input$save_share,
149 | ignoreInit = TRUE
150 | )
151 |
152 | observe({
153 | shared_template_id(saved_template_id())
154 | session$doBookmark()
155 | }) |>
156 | bindEvent(
157 | input$save_share,
158 | ignoreInit = TRUE
159 | )
160 |
161 | #### Updating ####
162 | observe({
163 | req(input$overwrite + input$overwrite_share > 0L)
164 | update_template(
165 | html = html(),
166 | id = input$existing_template
167 | )
168 | }) |>
169 | bindEvent(
170 | input$overwrite,
171 | input$overwrite_share,
172 | ignoreInit = TRUE
173 | )
174 |
175 | observe({
176 | shared_template_id(input$existing_template)
177 | session$doBookmark()
178 | }) |>
179 | bindEvent(
180 | input$overwrite_share,
181 | ignoreInit = TRUE
182 | )
183 |
184 | #### Deleting ####
185 | observe({
186 | req(input$delete)
187 | showModal(
188 | modalDialog(
189 | p("Deleting this template will remove for all users. Do you wish to continue?"),
190 | title = "Warning!",
191 | footer = tagList(
192 | tags$button(
193 | type = "button",
194 | class = "btn btn-secondary",
195 | `data-dismiss` = "modal",
196 | `data-bs-dismiss` = "modal",
197 | shiny::icon("xmark"),
198 | "No"
199 | ),
200 | tags$button(
201 | id = ns("confirm_delete"),
202 | type = "button",
203 | class = "btn btn-danger action-button",
204 | `data-dismiss` = "modal",
205 | `data-bs-dismiss` = "modal",
206 | shiny::icon("check"),
207 | "Yes"
208 | )
209 | )
210 | )
211 | )
212 | }) |>
213 | bindEvent(
214 | input$select,
215 | input$delete,
216 | ignoreInit = TRUE
217 | )
218 |
219 | observe({
220 | delete_template(input$select)
221 | removeUI(selector = paste0(".template-option[data-value='", input$select, "']"))
222 | }) |>
223 | bindEvent(
224 | input$confirm_delete,
225 | ignoreInit = TRUE
226 | )
227 |
228 | #### UI Updating ####
229 | selected_template <- reactive({
230 | req(!input$delete)
231 | read_template(input$select)
232 | }) |>
233 | bindEvent(
234 | input$select,
235 | input$delete,
236 | ignoreInit = TRUE
237 | )
238 |
239 | return(selected_template)
240 | })
241 | }
242 |
--------------------------------------------------------------------------------
/srcjs/component/Tab.js:
--------------------------------------------------------------------------------
1 | import { Component } from './Component'
2 |
3 | export class Tab extends Component {
4 | _item = 1
5 |
6 | updateComponent () {};
7 |
8 | getPageType () {
9 | return $('#settings-page_type input:radio:checked').val()
10 | };
11 |
12 | addPage () {
13 | const page_type = this.getPageType()
14 |
15 | const tab_name = $('#sidebar-tab-name').val()
16 | let tab_value = $('#sidebar-tab-value').val()
17 | if (tab_value === '') {
18 | tab_value = this.createID('tab')
19 | } else if (this.checkDuplicateIDs(tab_value, page_type)) {
20 | return
21 | }
22 |
23 | $('#sidebar-tab-alert div').alert('close')
24 |
25 | if (page_type === 'dashboardPage') {
26 | this.addMenuItem(tab_name, tab_value)
27 | } else {
28 | this.addTab(tab_name, tab_value)
29 | }
30 | };
31 |
32 | addTab (tab_name, tab_value) {
33 | const nav_panel = $('ul.navbar-nav')
34 | const nav_id = nav_panel.data('tabsetid')
35 |
36 | const tab_panel = $('.tab-content')
37 | const active_class = tab_panel.html() === '' ? 'active' : ''
38 |
39 | const tab_icon = $('#sidebar-tab-icon').val()
40 | const icon_r = tab_icon === '' ? '' : `, icon = icon("${tab_icon}")`
41 | const icon_class = tab_icon === '' ? '' : $('#sidebar-tab-icon option').html().includes('fab') ? 'fab' : 'fa'
42 | const icon_html = tab_icon === '' ? '' : ``
43 |
44 | nav_panel.append(`
45 | ${tab_name}
80 | 81 |