├── .Rbuildignore ├── .devcontainer └── devcontainer.json ├── .eslintrc.json ├── .gitattributes ├── .github ├── .gitignore └── workflows │ ├── Build-Assets.yaml │ ├── Javascript-Tests.yaml │ ├── Publish-VSCode-Extension.yaml │ ├── R-CMD-check.yaml │ ├── deploy.yml │ └── update-visual-snapshots.yml ├── .gitignore ├── .hintrc ├── .lintr ├── .vscode ├── convert_ui_node.code-snippets ├── launch.json ├── settings.json └── tasks.json ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R.Rproj ├── R ├── AppPreviewClass.R ├── Callbacks.R ├── FileChangeWatcher.R ├── check_for_url_issues.R ├── create_output_subscribers.R ├── get_app_file_type.R ├── launch_editor.R ├── launch_editor_addin.R ├── rstudioapi_utils.R ├── utils.R ├── validate_app_loc.R └── watch_for_app_close.R ├── README.md ├── _pkgdown.yml ├── inst ├── communication-types │ ├── package.json │ ├── src │ │ ├── AppInfo.ts │ │ ├── AppTemplates.ts │ │ ├── BackendConnection.ts │ │ ├── MessageToBackend.ts │ │ ├── MessageToClient.ts │ │ ├── MessageUnion.ts │ │ ├── globals.d.ts │ │ ├── index.ts │ │ └── isRecord.ts │ └── tsconfig.json ├── editor-component-lib │ ├── dist │ │ ├── index.js │ │ └── style.css │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ │ ├── globals.d.ts │ │ └── index.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.cts ├── editor │ ├── .env │ ├── .github │ │ └── workflows │ │ │ └── playwright.yml │ ├── .gitignore │ ├── .storybook │ │ ├── main.js │ │ ├── preview-head.html │ │ └── preview.js │ ├── .vscode │ │ └── launch.json │ ├── build │ │ ├── assets │ │ │ ├── index-f1413a39.js │ │ │ └── index-faf2b339.css │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── robots.txt │ │ └── tree-sitter.wasm │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── playwright │ │ ├── IdArgumentInput.spec.ts │ │ ├── NamedListInput.spec.ts │ │ ├── backend-server.spec.ts │ │ ├── component-tests │ │ │ └── bsicon-chooser.spec.tsx │ │ ├── deleteOutputServerBindings.spec.ts │ │ ├── drag-and-drop.spec.ts │ │ ├── error-boundaries.spec.ts │ │ ├── gridlayout-editing.spec.ts │ │ ├── index.html │ │ ├── index.tsx │ │ ├── navbarPage.spec.ts │ │ ├── nested-grids.spec.ts │ │ ├── openAppScriptModal.ts │ │ ├── template-chooser.spec.ts │ │ ├── tour-mode.spec.ts │ │ ├── user-actions.spec.ts │ │ ├── utils │ │ │ ├── dragDrop.ts │ │ │ ├── dragInDir.ts │ │ │ ├── generate_docs_screenshots.mts │ │ │ ├── mockBackend.ts │ │ │ └── start_uieditor_backend.ts │ │ ├── visual-bslib-cards.spec.ts │ │ ├── visual-bslib-cards.spec.ts-snapshots │ │ │ ├── Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-chromium-darwin.png │ │ │ ├── Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-chromium-linux.png │ │ │ ├── Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-firefox-darwin.png │ │ │ ├── Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-firefox-linux.png │ │ │ ├── Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-webkit-darwin.png │ │ │ └── Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-webkit-linux.png │ │ ├── visual-regression.spec.ts │ │ └── visual-regression.spec.ts-snapshots │ │ │ ├── Landing-page-visual-regression-1-chromium-darwin.png │ │ │ ├── Landing-page-visual-regression-1-chromium-linux.png │ │ │ ├── Landing-page-visual-regression-1-firefox-darwin.png │ │ │ ├── Landing-page-visual-regression-1-firefox-linux.png │ │ │ ├── Landing-page-visual-regression-1-webkit-darwin.png │ │ │ ├── Landing-page-visual-regression-1-webkit-linux.png │ │ │ ├── Template-Chooser-visual-regression-1-chromium-darwin.png │ │ │ ├── Template-Chooser-visual-regression-1-chromium-linux.png │ │ │ ├── Template-Chooser-visual-regression-1-firefox-darwin.png │ │ │ ├── Template-Chooser-visual-regression-1-firefox-linux.png │ │ │ ├── Template-Chooser-visual-regression-1-webkit-darwin.png │ │ │ └── Template-Chooser-visual-regression-1-webkit-linux.png │ ├── postcss.config.js │ ├── public │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── robots.txt │ │ └── tree-sitter.wasm │ ├── src │ │ ├── App.css │ │ ├── AppTour │ │ │ ├── PropertiesPanelAbout.tsx │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── DragAndDropHelpers │ │ │ ├── DragAndDropHelpers.tsx │ │ │ ├── DropWatcherPanel.module.css │ │ │ ├── DropWatcherPanel.tsx │ │ │ ├── useFilteredDrop.tsx │ │ │ └── useMakeDraggable.tsx │ │ ├── EditorContainer │ │ │ ├── App_Layout_Sizes.ts │ │ │ ├── DialogPopover.tsx │ │ │ ├── EditorContainer.module.css │ │ │ ├── EditorContainer.tsx │ │ │ ├── EditorView.tsx │ │ │ ├── HeaderView.module.css │ │ │ ├── HeaderView.tsx │ │ │ ├── HistoryGoBackwardProvider.tsx │ │ │ ├── LanguageModeBadge.module.css │ │ │ ├── LanguageModeBadge.tsx │ │ │ ├── LostConnectionPopup.tsx │ │ │ ├── MessageForUser.module.css │ │ │ ├── MessageForUser.tsx │ │ │ ├── OpenSideBySideWindowButton.tsx │ │ │ ├── TSParserProvider.tsx │ │ │ └── getAllInputOutputIdsInApp.tsx │ │ ├── EditorLayout │ │ │ ├── EditorLayout.module.css │ │ │ ├── EditorLayout.tsx │ │ │ └── PanelHeader.tsx │ │ ├── ElementsPalette │ │ │ ├── ElementsPalette.stories.tsx │ │ │ ├── UiElementIcon.tsx │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── HistoryNavigation │ │ │ ├── StateHistory.test.ts │ │ │ ├── StateHistory.ts │ │ │ ├── useUndoRedo.test.tsx │ │ │ └── useUndoRedo.tsx │ │ ├── SUE.module.css │ │ ├── SUE.stories.tsx │ │ ├── SUE.tsx │ │ ├── SettingsPanel │ │ │ ├── FormBuilder.stories.tsx │ │ │ ├── FormBuilder.tsx │ │ │ ├── GoToSourceBtns.tsx │ │ │ ├── LabeledInputCategory.tsx │ │ │ ├── NodeDeleteButton.tsx │ │ │ ├── PathBreadcrumb.module.css │ │ │ ├── PathBreadcrumb.tsx │ │ │ ├── PathBreadcrumbLinear.module.css │ │ │ ├── PathBreadcrumbLinear.tsx │ │ │ ├── SettingsInput │ │ │ │ ├── IdInput.tsx │ │ │ │ ├── SettingsInput.scss │ │ │ │ ├── SettingsInput.stories.tsx │ │ │ │ ├── SettingsInput.test.tsx │ │ │ │ ├── SettingsInput.tsx │ │ │ │ ├── SettingsInputElement.tsx │ │ │ │ ├── StringInput.tsx │ │ │ │ ├── updateServerWithNewId.test.ts │ │ │ │ ├── updateServerWithNewId.ts │ │ │ │ └── valueIsType.tsx │ │ │ ├── SettingsPanel.tsx │ │ │ ├── UnknownArgumentsRender.tsx │ │ │ ├── buildStaticSettingsInfo.test.ts │ │ │ ├── buildStaticSettingsInfo.ts │ │ │ ├── styles.scss │ │ │ ├── useGetNodeServerBindingInfo.tsx │ │ │ ├── useUpToDateServerLocations.tsx │ │ │ └── useUpdateSettings.tsx │ │ ├── Shiny-Ui-Elements │ │ │ ├── Bslib │ │ │ │ ├── BslibCard.module.css │ │ │ │ ├── BslibCard.tsx │ │ │ │ ├── BslibCardBodyFill.tsx │ │ │ │ ├── BslibCardContainer.tsx │ │ │ │ ├── BslibCardFooter.tsx │ │ │ │ ├── BslibCardHeader.tsx │ │ │ │ ├── NavPanel.tsx │ │ │ │ ├── README.md │ │ │ │ ├── Sidebar.module.css │ │ │ │ ├── Sidebar.tsx │ │ │ │ ├── SidebarDropWatcherPanel.tsx │ │ │ │ ├── Utils │ │ │ │ │ ├── CardElements.tsx │ │ │ │ │ ├── CardUtils.module.css │ │ │ │ │ └── render_card_elements.tsx │ │ │ │ ├── ValueBox │ │ │ │ │ ├── BsIcon.tsx │ │ │ │ │ ├── IconSelector.stories.tsx │ │ │ │ │ ├── IconSelector.tsx │ │ │ │ │ ├── ValueBox.tsx │ │ │ │ │ └── gather_icon_data.R │ │ │ │ └── index.tsx │ │ │ ├── ChildrenWithDropNodes.module.css │ │ │ ├── ChildrenWithDropNodes.tsx │ │ │ ├── DtDtOutput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ ├── Gridlayout │ │ │ │ ├── GridlayoutCard │ │ │ │ │ └── index.tsx │ │ │ │ ├── GridlayoutCardText.tsx │ │ │ │ ├── GridlayoutGridCardPlot │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.module.css │ │ │ │ ├── GridlayoutGridContainer │ │ │ │ │ └── index.tsx │ │ │ │ ├── GridlayoutGridPage │ │ │ │ │ └── index.tsx │ │ │ │ ├── Utils │ │ │ │ │ ├── AreaOverlay.module.css │ │ │ │ │ ├── AreaOverlay.tsx │ │ │ │ │ ├── BsCard.tsx │ │ │ │ │ ├── EditableGridContainer │ │ │ │ │ │ ├── EditableGridContainer.stories.tsx │ │ │ │ │ │ ├── EditableGridContainer.tsx │ │ │ │ │ │ ├── TractInfoDisplay.module.css │ │ │ │ │ │ ├── TractInfoDisplay.tsx │ │ │ │ │ │ ├── TractSizer.module.css │ │ │ │ │ │ ├── TractSizer.tsx │ │ │ │ │ │ ├── dragToResizeHelpers.tsx │ │ │ │ │ │ ├── resizableGrid.module.css │ │ │ │ │ │ ├── tractUpdatingFunctions.tsx │ │ │ │ │ │ ├── useDragToResizeGrid.ts │ │ │ │ │ │ ├── utils.test.ts │ │ │ │ │ │ └── utils.tsx │ │ │ │ │ ├── GridContainerElement │ │ │ │ │ │ ├── GridContainerElement.tsx │ │ │ │ │ │ ├── ensureProperBoxedGridLayoutArgs.ts │ │ │ │ │ │ ├── gridLayoutReducer.tsx │ │ │ │ │ │ └── styles.module.css │ │ │ │ │ ├── NameNewPanelModal.tsx │ │ │ │ │ ├── getTractExtents.ts │ │ │ │ │ ├── swapping.module.css │ │ │ │ │ ├── useGridItemSwapping.tsx │ │ │ │ │ ├── useResizeOnDrag.ts │ │ │ │ │ ├── useSetLayout.tsx │ │ │ │ │ ├── useUpdateUiArguments.tsx │ │ │ │ │ └── watchAndReactToGridAreaUpdatesupdate.ts │ │ │ │ └── index.ts │ │ │ ├── PlotlyPlotlyOutput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ ├── ShinyActionButton │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinyCheckboxGroupInput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinyCheckboxInput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinyLayoutSidebar │ │ │ │ ├── LayoutSidebar.tsx │ │ │ │ └── ShinyLayoutSidebar.module.css │ │ │ ├── ShinyMarkdown │ │ │ │ └── markdown.tsx │ │ │ ├── ShinyNavbarPage │ │ │ │ └── index.tsx │ │ │ ├── ShinyNumericInput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinyPlotOutput │ │ │ │ ├── StaticPlotPlaceholder.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinyRadioButtons │ │ │ │ └── ShinyRadioButtons.tsx │ │ │ ├── ShinySelectInput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinySliderInput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinyTabsetPanel │ │ │ │ └── index.tsx │ │ │ ├── ShinyTextInput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ShinyTextOutput │ │ │ │ └── ShinyTextOutput.tsx │ │ │ ├── ShinyUiOutput │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── TextNode │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── UiElementsShowcase.module.css │ │ │ ├── UiElementsShowcase.stories.tsx │ │ │ ├── UnknownUiFunction │ │ │ │ ├── formatFunctionText.test.ts │ │ │ │ ├── formatFunctionText.ts │ │ │ │ └── index.tsx │ │ │ ├── __TestingErrorNode │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── registered_ui_nodes.ts │ │ │ └── utils │ │ │ │ ├── InputOutputTitle.tsx │ │ │ │ ├── RenderUiChildren.tsx │ │ │ │ └── add_editor_info_to_ui_node.tsx │ │ ├── assets │ │ │ ├── app-templates │ │ │ │ ├── app_templates.ts │ │ │ │ └── templates │ │ │ │ │ ├── chickWeightsGrid.ts │ │ │ │ │ ├── chickWeightsNavbar.ts │ │ │ │ │ ├── gridGeyser.ts │ │ │ │ │ └── sidebarPlotGrid.ts │ │ │ ├── bsicons │ │ │ │ └── all-bsicon-names.json │ │ │ ├── favicon.svg │ │ │ ├── icons │ │ │ │ ├── ShinyDynamicUIOutput.png │ │ │ │ ├── alignItem.png │ │ │ │ ├── alignItemBottom.png │ │ │ │ ├── alignItemMiddle.png │ │ │ │ ├── alignItemTop.png │ │ │ │ ├── alignTextCenter.png │ │ │ │ ├── alignTextLeft.png │ │ │ │ ├── alignTextRight.png │ │ │ │ ├── redo.png │ │ │ │ ├── shinyButton.png │ │ │ │ ├── shinyCheckgroup.png │ │ │ │ ├── shinyContainer.png │ │ │ │ ├── shinyDatatable.png │ │ │ │ ├── shinyDateinput.png │ │ │ │ ├── shinyDaterange.png │ │ │ │ ├── shinyFileinput.png │ │ │ │ ├── shinyGridContainer.png │ │ │ │ ├── shinyImage.png │ │ │ │ ├── shinyMarkdown.png │ │ │ │ ├── shinyNumericinput.png │ │ │ │ ├── shinyPassword.png │ │ │ │ ├── shinyPlot.png │ │ │ │ ├── shinyRadioButtons.png │ │ │ │ ├── shinySelectbox.png │ │ │ │ ├── shinySlider.png │ │ │ │ ├── shinyTab.png │ │ │ │ ├── shinyTable.png │ │ │ │ ├── shinyTabsetPanel.png │ │ │ │ ├── shinyText.png │ │ │ │ ├── shinyTextBox.png │ │ │ │ ├── shinyTextOutput.png │ │ │ │ ├── shinyTextinput.png │ │ │ │ ├── shinyValueBox.png │ │ │ │ ├── shinycheckbox.png │ │ │ │ ├── tabPanel.png │ │ │ │ ├── tabsetPanel.png │ │ │ │ ├── tour.png │ │ │ │ └── undo.png │ │ │ ├── shiny-logo.svg │ │ │ └── svg-icons │ │ │ │ ├── alignBottom.svg │ │ │ │ ├── alignCenter.svg │ │ │ │ ├── alignHCenter.svg │ │ │ │ ├── alignHSpread.svg │ │ │ │ ├── alignLeft.svg │ │ │ │ ├── alignRight.svg │ │ │ │ ├── alignSpread.svg │ │ │ │ ├── alignTop.svg │ │ │ │ ├── alignVCenter.svg │ │ │ │ ├── alignVSpread.svg │ │ │ │ ├── down-spinner-button.svg │ │ │ │ ├── redo.svg │ │ │ │ ├── trash.svg │ │ │ │ ├── undo.svg │ │ │ │ └── up-spinner-button.svg │ │ ├── backendCommunication │ │ │ ├── getClientsideOnlyTree.tsx │ │ │ ├── messageDispatcher.test.ts │ │ │ ├── staticBackend.ts │ │ │ ├── useBackendMessageCallbacks.tsx │ │ │ ├── useSyncUiWithBackend.tsx │ │ │ └── websocketBackend.ts │ │ ├── components │ │ │ ├── AppPreview │ │ │ │ ├── AppPreview.module.css │ │ │ │ ├── LogsViewer.module.css │ │ │ │ ├── LogsViewer.tsx │ │ │ │ ├── ShinyLivePreviewExperiment.tsx │ │ │ │ ├── ShowAppText.module.css │ │ │ │ ├── ShowAppText.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── python_app_to_shinylive_url.tsx │ │ │ │ ├── useCommunicateWithBackend.tsx │ │ │ │ └── usePreviewScale.tsx │ │ │ ├── CategoryDivider │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ErrorCatcher │ │ │ │ ├── GeneralErrorView.module.css │ │ │ │ └── GeneralErrorView.tsx │ │ │ ├── Icons │ │ │ │ ├── PngIcon.tsx │ │ │ │ ├── ShinyLogo.tsx │ │ │ │ ├── generated │ │ │ │ │ ├── AlignBottom.tsx │ │ │ │ │ ├── AlignCenter.tsx │ │ │ │ │ ├── AlignHCenter.tsx │ │ │ │ │ ├── AlignHSpread.tsx │ │ │ │ │ ├── AlignLeft.tsx │ │ │ │ │ ├── AlignRight.tsx │ │ │ │ │ ├── AlignSpread.tsx │ │ │ │ │ ├── AlignTop.tsx │ │ │ │ │ ├── AlignVCenter.tsx │ │ │ │ │ ├── AlignVSpread.tsx │ │ │ │ │ ├── DownSpinnerButton.tsx │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Redo.tsx │ │ │ │ │ ├── Trash.tsx │ │ │ │ │ ├── Undo.tsx │ │ │ │ │ ├── UpSpinnerButton.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── Inputs │ │ │ │ ├── BooleanInput │ │ │ │ │ ├── BooleanInputSimple.tsx │ │ │ │ │ └── styles.module.css │ │ │ │ ├── Button │ │ │ │ │ ├── Button.module.css │ │ │ │ │ └── Button.tsx │ │ │ │ ├── CSSUnitInput │ │ │ │ │ ├── CSSMeasure.ts │ │ │ │ │ ├── CSSUnitChooser.tsx │ │ │ │ │ ├── CSSUnitInfo.module.css │ │ │ │ │ ├── CSSUnitInfo.tsx │ │ │ │ │ ├── CSSUnitInput.module.css │ │ │ │ │ ├── CSSUnitInput.test.tsx │ │ │ │ │ ├── CSSUnitInput.tsx │ │ │ │ │ └── css-helpers.test.ts │ │ │ │ ├── ListInput │ │ │ │ │ ├── KeyInput.tsx │ │ │ │ │ ├── NamedListInput.tsx │ │ │ │ │ ├── namedListUtils.tsx │ │ │ │ │ └── useListState.tsx │ │ │ │ ├── NumberInput │ │ │ │ │ ├── NumberInput.scss │ │ │ │ │ ├── NumberInput.stories.tsx │ │ │ │ │ ├── NumberInput.test.tsx │ │ │ │ │ └── NumberInput.tsx │ │ │ │ ├── OptionsDropdown │ │ │ │ │ ├── DropdownSelect.tsx │ │ │ │ │ ├── OptionsDropdown.stories.tsx │ │ │ │ │ └── styles.scss │ │ │ │ ├── PopoverButton.tsx │ │ │ │ └── RadioInputs │ │ │ │ │ ├── RadioInput.stories.tsx │ │ │ │ │ ├── RadioInputs.module.css │ │ │ │ │ └── RadioInputsSimple.tsx │ │ │ ├── PlotPlaceholder │ │ │ │ ├── PlotPlaceholder.stories.tsx │ │ │ │ ├── PlotPlaceholder.tsx │ │ │ │ └── styles.scss │ │ │ ├── PopoverEl │ │ │ │ ├── ControlledPopup.tsx │ │ │ │ ├── FloatingPopover.tsx │ │ │ │ └── styles.module.css │ │ │ ├── PortalModal │ │ │ │ ├── Portal.tsx │ │ │ │ ├── PortalModal.module.css │ │ │ │ └── PortalModal.tsx │ │ │ ├── Tabs │ │ │ │ ├── TabPanel │ │ │ │ │ └── TabPanel.tsx │ │ │ │ └── Tabset │ │ │ │ │ ├── Tab.tsx │ │ │ │ │ ├── TabDropDetector.tsx │ │ │ │ │ ├── Tabset.module.css │ │ │ │ │ ├── Tabset.stories.tsx │ │ │ │ │ ├── Tabset.tsx │ │ │ │ │ └── useActiveTab.tsx │ │ │ ├── TemplatePreviews │ │ │ │ ├── AppTemplatePreview.tsx │ │ │ │ ├── TemplateChooserView.tsx │ │ │ │ ├── TemplateFiltersForm.tsx │ │ │ │ ├── TemplatePreviewCard.tsx │ │ │ │ ├── TemplatePreviewGrid.tsx │ │ │ │ ├── TemplatePreviews.stories.tsx │ │ │ │ ├── filterTemplates.ts │ │ │ │ ├── styles.scss │ │ │ │ └── useRequestTemplate.ts │ │ │ ├── UiNode │ │ │ │ ├── NodeWraper.tsx │ │ │ │ ├── UiNode.tsx │ │ │ │ ├── UiNodeErrorView.tsx │ │ │ │ ├── useMakeWrapperProps.tsx │ │ │ │ └── usePathInformation.tsx │ │ │ └── UndoRedoButtons │ │ │ │ ├── UndoRedoButtons.module.css │ │ │ │ └── UndoRedoButtons.tsx │ │ ├── devModeApp.tsx │ │ ├── env_variables.ts │ │ ├── globals.d.ts │ │ ├── index.tsx │ │ ├── main.ts │ │ ├── parsing │ │ │ ├── ParsedAppInfo.ts │ │ │ ├── Primatives.ts │ │ │ ├── nodesToLocations.ts │ │ │ └── parsedRAppToAppInfo.ts │ │ ├── python-parsing │ │ │ ├── NodeTypes │ │ │ │ ├── BooleanNode.ts │ │ │ │ ├── KeywordArgNode.ts │ │ │ │ ├── NumberNode.ts │ │ │ │ └── StringNode.ts │ │ │ ├── assets │ │ │ │ ├── tree-sitter-python.wasm │ │ │ │ └── tree-sitter.wasm │ │ │ ├── example_app_scripts.ts │ │ │ ├── generate_app_script_template.test.ts │ │ │ ├── generate_app_script_template.ts │ │ │ ├── generate_output_binding.test.ts │ │ │ ├── generate_output_binding.ts │ │ │ ├── getKnownPythonInputs.ts │ │ │ ├── getKnownPythonOutputs.ts │ │ │ ├── get_imported_pkgs.ts │ │ │ ├── get_server_positions.test.ts │ │ │ ├── parsePythonApp.tsx │ │ │ ├── pythonTreesitterToUiTree.test.ts │ │ │ ├── pythonTreesitterToUiTree.ts │ │ │ └── scratch.ts │ │ ├── r-parsing │ │ │ ├── NodeTypes │ │ │ │ ├── ArrayNode.ts │ │ │ │ ├── BooleanNode.ts │ │ │ │ ├── CallNode.ts │ │ │ │ ├── KeywordArgNode.ts │ │ │ │ ├── ListNode.ts │ │ │ │ ├── NumberNode.ts │ │ │ │ ├── StringNode.ts │ │ │ │ ├── TextNode.ts │ │ │ │ ├── array_nodes.test.ts │ │ │ │ └── text_nodes.test.ts │ │ │ ├── generate_app_script_template.test.ts │ │ │ ├── generate_app_script_template.ts │ │ │ ├── generate_output_binding.test.ts │ │ │ ├── generate_output_binding.ts │ │ │ ├── getKnownRInputs.ts │ │ │ ├── getKnownROutputs.ts │ │ │ ├── get_libraries.test.ts │ │ │ ├── get_name_of_accessed_property.ts │ │ │ ├── get_r_info_if_known.ts │ │ │ ├── get_r_packages_in_script.ts │ │ │ ├── get_server_positions.test.ts │ │ │ ├── parseRApp.ts │ │ │ ├── parseRScript.ts │ │ │ ├── rTreesitterToUiTree.test.ts │ │ │ ├── rTreesitterToUiTree.ts │ │ │ └── scratch.ts │ │ ├── runSUE.tsx │ │ ├── scratch.ts │ │ ├── setupTests.js │ │ ├── state │ │ │ ├── ReduxProvider.tsx │ │ │ ├── app_info.ts │ │ │ ├── connectedToServer.ts │ │ │ ├── create_subscriber_getter.ts │ │ │ ├── currentlyDraggedNode.ts │ │ │ ├── languageMode.ts │ │ │ ├── metaData.ts │ │ │ ├── middleware │ │ │ │ ├── listenForDeleteMiddleware.ts │ │ │ │ ├── listenForNodeAddMiddleware.ts │ │ │ │ └── resetSelectionInTemplateChooser.ts │ │ │ ├── selectedPath.ts │ │ │ ├── store.ts │ │ │ ├── uiTreesAreSame.tsx │ │ │ ├── useDeleteNode.tsx │ │ │ ├── usePlaceNode.ts │ │ │ └── useUpdateServerCode.ts │ │ ├── ui-node-definitions │ │ │ ├── Bslib │ │ │ │ ├── card.ts │ │ │ │ ├── card_body.ts │ │ │ │ ├── card_footer.ts │ │ │ │ ├── card_header.ts │ │ │ │ ├── nav_panel.ts │ │ │ │ ├── page_navbar.ts │ │ │ │ ├── sidebar.ts │ │ │ │ └── value_box.ts │ │ │ ├── DT │ │ │ │ └── output_dt.ts │ │ │ ├── NodePath.ts │ │ │ ├── Shiny │ │ │ │ ├── input_action_button.ts │ │ │ │ ├── input_checkbox.ts │ │ │ │ ├── input_checkbox_group.ts │ │ │ │ ├── input_numeric.ts │ │ │ │ ├── input_radio_buttons.ts │ │ │ │ ├── input_select.ts │ │ │ │ ├── input_slider.ts │ │ │ │ ├── input_text.ts │ │ │ │ ├── layout_sidebar.ts │ │ │ │ ├── markdown.ts │ │ │ │ ├── output_plot.ts │ │ │ │ ├── output_text.ts │ │ │ │ ├── output_ui.ts │ │ │ │ ├── panel_main.ts │ │ │ │ └── tabset_panel.ts │ │ │ ├── ShinyUiNode.ts │ │ │ ├── TreeManipulation │ │ │ │ ├── aIsParentOfB.test.ts │ │ │ │ ├── aIsParentOfB.ts │ │ │ │ ├── addNodeMutating.ts │ │ │ │ ├── getIsValidMove.test.ts │ │ │ │ ├── getIsValidMove.ts │ │ │ │ ├── getNamedPath.ts │ │ │ │ ├── getNewSelectionPathAfterDelete.test.ts │ │ │ │ ├── getNewSelectionPathAfterDeletion.ts │ │ │ │ ├── getNode.test.ts │ │ │ │ ├── getNode.ts │ │ │ │ ├── getParentPath.ts │ │ │ │ ├── getPathAfterMove.test.ts │ │ │ │ ├── getPathAfterMove.ts │ │ │ │ ├── moveNode.test.ts │ │ │ │ ├── moveNodeMutating.ts │ │ │ │ ├── nodeDepth.ts │ │ │ │ ├── nodesAreSiblings.test.ts │ │ │ │ ├── nodesAreSiblings.ts │ │ │ │ ├── nodesShareCommonParent.test.ts │ │ │ │ ├── nodesShareCommonParent.ts │ │ │ │ ├── pathsSameAtDepth.ts │ │ │ │ ├── placeNode.ts │ │ │ │ ├── removeNode.ts │ │ │ │ ├── samePath.ts │ │ │ │ ├── treeManipulation.test.ts │ │ │ │ ├── updateNode.ts │ │ │ │ └── wrapInNode.ts │ │ │ ├── buildStaticSettingsInfo.ts │ │ │ ├── code_generation │ │ │ │ ├── generate_full_app_script.ts │ │ │ │ ├── generate_ui_script.test.ts │ │ │ │ ├── generate_ui_script.ts │ │ │ │ ├── get_ordered_positional_args.ts │ │ │ │ ├── printPrimative.ts │ │ │ │ ├── print_internal_ui_nodes.ts │ │ │ │ ├── print_named_list.ts │ │ │ │ ├── ui_node_to_R_code.test.ts │ │ │ │ ├── ui_node_to_R_code.ts │ │ │ │ ├── ui_node_to_code.ts │ │ │ │ ├── ui_node_to_python_code.test.ts │ │ │ │ ├── ui_node_to_python_code.ts │ │ │ │ └── utils.ts │ │ │ ├── get_ordered_positional_args.ts │ │ │ ├── gridlayout │ │ │ │ ├── GridLayoutArgs.ts │ │ │ │ ├── gridTemplates │ │ │ │ │ ├── TemplatedGridProps.ts │ │ │ │ │ ├── addItem.test.ts │ │ │ │ │ ├── addItem.ts │ │ │ │ │ ├── addTract.ts │ │ │ │ │ ├── addTracts.test.ts │ │ │ │ │ ├── areasOfChildren.ts │ │ │ │ │ ├── availableMoves.ts │ │ │ │ │ ├── findAvailableTracts.test.ts │ │ │ │ │ ├── findAvailableTracts.ts │ │ │ │ │ ├── findItemLocation.ts │ │ │ │ │ ├── findItemLocations.test.ts │ │ │ │ │ ├── gridTemplateManipulation.test.ts │ │ │ │ │ ├── helpers.ts │ │ │ │ │ ├── itemBoundsInDir.ts │ │ │ │ │ ├── itemLocations.ts │ │ │ │ │ ├── layoutParsing.test.ts │ │ │ │ │ ├── layoutParsing.ts │ │ │ │ │ ├── parseGridTemplateAreas.test.ts │ │ │ │ │ ├── parseGridTemplateAreas.ts │ │ │ │ │ ├── removeItem.test.ts │ │ │ │ │ ├── removeItem.ts │ │ │ │ │ ├── removeTract.test.ts │ │ │ │ │ ├── removeTract.ts │ │ │ │ │ ├── renameItem.ts │ │ │ │ │ ├── resizeTract.ts │ │ │ │ │ ├── swapItems.test.ts │ │ │ │ │ ├── swapItems.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── grid_card.ts │ │ │ │ ├── grid_card_plot.ts │ │ │ │ ├── grid_card_text.ts │ │ │ │ ├── grid_container.ts │ │ │ │ ├── grid_page.ts │ │ │ │ ├── isValidGridItem.ts │ │ │ │ └── makeGridFriendlyNode.ts │ │ │ ├── index.ts │ │ │ ├── inputFieldTypes.ts │ │ │ ├── internal │ │ │ │ ├── testing_error_node.ts │ │ │ │ ├── text_node.ts │ │ │ │ └── unknown_code.ts │ │ │ ├── isShinyUiNode.ts │ │ │ ├── make_unknown_ui_function.ts │ │ │ ├── nodeInfoFactory.ts │ │ │ ├── nodePathUtils.ts │ │ │ ├── plotly │ │ │ │ └── output_plotly.ts │ │ │ ├── sample_ui_trees │ │ │ │ ├── basicGridPage.tsx │ │ │ │ ├── basicNavbarPage.tsx │ │ │ │ ├── bslibCards.tsx │ │ │ │ ├── errorTesting.ts │ │ │ │ ├── minimalPage.tsx │ │ │ │ └── pythonSidebarAndTabs.tsx │ │ │ ├── uiNodeTypes.ts │ │ │ └── walkUiNode.ts │ │ └── utils │ │ │ ├── code_position_utils.ts │ │ │ ├── generate_issue_reports.ts │ │ │ ├── mergeClasses.test.ts │ │ │ ├── mergeClasses.ts │ │ │ ├── onMac.tsx │ │ │ └── useKeyboardShortcuts.tsx │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── r-package-build-tools │ └── package.json ├── rstudio │ └── addins.dcf ├── shared-configs │ ├── package.json │ ├── postcss.config.js │ ├── tailwind.config.js │ └── tsconfig.json ├── treesitter-parsers │ ├── build.mjs │ ├── dist │ │ ├── CallNode.d.ts │ │ ├── get_assignment_nodes.d.ts │ │ ├── get_node_positions.d.ts │ │ ├── get_ui_assignment.d.ts │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── setup_language_parsers.d.ts │ │ ├── setup_python_parser.d.ts │ │ └── setup_r_parser.d.ts │ ├── package.json │ ├── src │ │ ├── CallNode.ts │ │ ├── assets │ │ │ ├── tree-sitter-python.wasm │ │ │ ├── tree-sitter-r.wasm │ │ │ └── tree-sitter.wasm │ │ ├── get_assignment_nodes.ts │ │ ├── get_node_positions.ts │ │ ├── get_ui_assignment.ts │ │ ├── index.ts │ │ └── setup_language_parsers.ts │ └── tsconfig.json ├── util-functions │ ├── package.json │ ├── src │ │ ├── TypescriptUtils.ts │ │ ├── arrays.test.ts │ │ ├── arrays.ts │ │ ├── convertMapToObject.ts │ │ ├── equalityCheckers.test.ts │ │ ├── equalityCheckers.ts │ │ ├── index.ts │ │ ├── is_object.ts │ │ ├── matrix-helpers.test.ts │ │ ├── matrix-helpers.ts │ │ ├── numbers.ts │ │ ├── strings.ts │ │ ├── sum_booleans.tsx │ │ └── within.ts │ ├── tsconfig.json │ └── vite.config.ts ├── vscode-extension-client │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ │ ├── globals.d.ts │ │ └── index.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.cts ├── vscode-extension │ ├── .eslintignore │ ├── .vscode │ │ ├── extensions.json │ │ ├── settings.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── LICENSE.md │ ├── README.md │ ├── assets │ │ ├── extension-with-code-open.png │ │ ├── launch-editor-cmd.png │ │ ├── run-sue-btn.png │ │ ├── shinyuieditor-hex.png │ │ ├── shinyuieditor-icon-dark.svg │ │ └── shinyuieditor-icon-light.svg │ ├── build.mts │ ├── build │ │ ├── extension.js │ │ └── tree-sitter.wasm │ ├── documentation │ │ └── extension-running.png │ ├── exampleFiles │ │ ├── app.r │ │ ├── empty_app.r │ │ ├── other_app.r │ │ ├── python │ │ │ └── app.py │ │ └── testing │ │ │ ├── assets │ │ │ └── a_text_file.txt │ │ │ └── myOtherScript.R │ ├── jest.config.js │ ├── media │ │ ├── build │ │ │ ├── extension-editor.js │ │ │ └── style.css │ │ └── tree-sitter.wasm │ ├── package.json │ ├── shinyuieditor-0.4.3.vsix │ ├── shinyuieditor-0.5.0.vsix │ ├── shinyuieditor-0.5.1.vsix │ ├── src │ │ ├── App_Parser.ts │ │ ├── Python-Utils │ │ │ ├── get_path_to_python.ts │ │ │ └── start_python_process.ts │ │ ├── R-Utils │ │ │ ├── checkIfPkgAvailable.ts │ │ │ ├── getPathToR.ts │ │ │ ├── getRPathFromConfig.ts │ │ │ ├── getRpathFromSystem.ts │ │ │ ├── runRCommand.ts │ │ │ └── startBackgroundRProcess.ts │ │ ├── app-preview │ │ │ ├── getFreePort.ts │ │ │ ├── getRemoteSafeUrl.ts │ │ │ ├── get_app_startup_info.ts │ │ │ ├── get_python_app_startup_info.ts │ │ │ ├── get_r_app_startup_info.ts │ │ │ └── startPreviewApp.ts │ │ ├── appScriptStatus.ts │ │ ├── commands │ │ │ ├── appFileUtils.test.ts │ │ │ ├── appFileUtils.ts │ │ │ ├── launchEditor.ts │ │ │ └── startEditorOnActiveFile.ts │ │ ├── editorLogic.ts │ │ ├── extension-api-utils │ │ │ ├── clearAppFile.ts │ │ │ ├── dispose.ts │ │ │ ├── insert_code_snippet.ts │ │ │ ├── openCodeCompanionEditor.ts │ │ │ ├── runShellCommand.ts │ │ │ ├── selectMultupleLocations.ts │ │ │ └── update_app_file.ts │ │ ├── extension.ts │ │ ├── globals.d.ts │ │ ├── make_cached_info_getter.ts │ │ ├── selectServerReferences.ts │ │ ├── shinyuieditor_extension.ts │ │ ├── startProcess.ts │ │ └── ui_tree_has_changed.ts │ └── tsconfig.json └── website │ ├── .gitignore │ ├── .vscode │ ├── extensions.json │ └── launch.json │ ├── README.md │ ├── astro.config.mjs │ ├── components.json │ ├── package.json │ ├── playwright.config.ts │ ├── public │ ├── favicon.svg │ ├── how-to-videos │ │ ├── add-element.webm │ │ ├── add-tract.webm │ │ ├── delete-an-element.webm │ │ ├── delete-tract.webm │ │ ├── move-an-element.webm │ │ ├── resize-with-drag.webm │ │ ├── resize-with-widget.webm │ │ ├── select-an-element.webm │ │ ├── show-size-widget.webm │ │ ├── undo-redo.webm │ │ └── update-an-element.webm │ ├── layout-editing.mp4 │ ├── layout-editing.webm │ └── live-demo │ │ └── tree-sitter.wasm │ ├── src │ ├── assets │ │ ├── markdown │ │ │ └── landing.mdx │ │ ├── screenshots │ │ │ ├── launch-editor-cmd.png │ │ │ ├── run-sue-btn.png │ │ │ ├── template-chooser.png │ │ │ └── unknown-arguments-display.png │ │ └── shinyuieditor-hex.png │ ├── components │ │ ├── Card.astro │ │ ├── CardSection.astro │ │ ├── Header.astro │ │ ├── Hero.astro │ │ ├── InternalLink.astro │ │ ├── LinkButton.astro │ │ ├── LogoLink.astro │ │ ├── MarkdownContainer.astro │ │ ├── TOC.astro │ │ ├── VideoEmbed.astro │ │ ├── icons │ │ │ ├── DiscordIcon.astro │ │ │ ├── GettingStartedIcon.astro │ │ │ ├── GithubIcon.astro │ │ │ └── LiveDemoIcon.astro │ │ └── ui │ │ │ ├── button.tsx │ │ │ └── card.tsx │ ├── env.d.ts │ ├── layouts │ │ ├── ArticleLayout.astro │ │ ├── FullPageLayout.astro │ │ └── Layout.astro │ ├── lib │ │ ├── sections.ts │ │ └── utils.ts │ ├── pages │ │ ├── FAQs.mdx │ │ ├── change-log.astro │ │ ├── getting-started.mdx │ │ ├── how-to.mdx │ │ ├── index.astro │ │ └── live-demo.astro │ └── styles │ │ └── globals.css │ ├── tailwind.config.js │ ├── tests │ └── check-live-demo.spec.ts │ └── tsconfig.json ├── man ├── check_for_app_file.Rd ├── create_output_subscribers.Rd ├── insert_server_code.Rd ├── launch_editor.Rd ├── select_server_code.Rd └── watch_for_app_close.Rd ├── package.json ├── scratch ├── a-brand-new-app │ └── app.R ├── a-brand-new-app3 │ └── app.R ├── app-w-unknown-code │ └── app.R ├── ast_generation_scratch.R ├── broken_ui │ ├── server.R │ └── ui.R ├── empty_directory │ └── utility_fns.R ├── just_server │ └── server.R ├── just_ui │ └── ui.R ├── navbarpage │ └── app.R ├── python │ └── app.py ├── reactive-val │ └── app.R ├── simple-multi-file │ ├── server.R │ └── ui.R ├── single-file-app │ └── app.R ├── single-file-app5 │ └── app.R ├── start_editor.R ├── start_editor_non_interactive.R ├── unknown-args │ ├── server.R │ └── ui.R └── webapp │ ├── server.R │ ├── ui.R │ └── ui_backup.R ├── shinyuieditor-hex.png ├── tests ├── testthat.R └── testthat │ └── test-app-location-validation.R ├── turbo.json ├── vignettes ├── .gitignore ├── demo-app │ ├── assets │ │ ├── index-79d01c90.css │ │ └── index-da5243a7.js │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── tree-sitter.wasm ├── faqs.Rmd ├── how-to-videos │ ├── add-element.webm │ ├── add-tract.webm │ ├── delete-an-element.webm │ ├── delete-tract.webm │ ├── move-an-element.webm │ ├── resize-with-drag.webm │ ├── resize-with-widget.webm │ ├── select-an-element.webm │ ├── show-size-widget.webm │ ├── undo-redo.webm │ └── update-an-element.webm ├── how-to.Rmd ├── quick-start.Rmd ├── screenshots │ └── template-chooser.png ├── ui-editor-live-demo.Rmd └── unknown-arguments-display.png └── yarn.lock /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^shinyuieditor-hex\.png$ 4 | ^scratch$ 5 | ^LICENSE\.md$ 6 | ^_pkgdown\.yml$ 7 | ^docs$ 8 | ^doc$ 9 | ^venv$ 10 | ^pkgdown$ 11 | ^\.github$ 12 | ^\.devcontainer$ 13 | ^vignettes/ui-editor-live-demo\.Rmd$ 14 | ^\.hintrc$ 15 | ^\.yarnrc\.yml$ 16 | ^\.lintr$ 17 | ^\.vscode$ 18 | ^\.eslintrc.json$ 19 | ^package\.json$ 20 | ^turbo\.json$ 21 | ^yarn\.lock$ 22 | ^node_modules$ 23 | ^inst\/editor\/(?!build).+$ 24 | ^inst\/editor\/playwright.* 25 | ^inst\/vscode-extension-client 26 | ^inst\/vscode-extension 27 | ^inst\/communication-types 28 | ^inst\/r-ast-parsing 29 | ^inst\/util-functions 30 | ^inst\/build-utils 31 | ^inst\/r-package-build-tools 32 | ^.*\.bin$ 33 | ^.*\.turbo$ 34 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "features": { 4 | "ghcr.io/rocker-org/devcontainer-features/r-rig:latest": { 5 | "installDevTools": "true" 6 | } 7 | }, 8 | "remoteEnv": { 9 | "PKG_SYSREQS": "true" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Activate these filters so that build assets are ignored locally but go through properly on CI 2 | # Run the following in the shell 3 | # git config --global filter.buildfilter.clean "cat" 4 | # git config --global filter.buildfilter.smudge "cat" 5 | inst/editor/build/ filter=buildfilter 6 | inst/vscode-extension/build/ filter=buildfilter 7 | inst/vscode-extension/media/build/ filter=buildfilter 8 | inst/vscode-extension/*.vsix filter=buildfilter -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Run package checks for R package 2 | # Workflow derived from https://github.com/rstudio/shiny-workflows 3 | # 4 | # NOTE: This Shiny team GHA workflow is overkill for most R packages. 5 | # For most R packages it is better to use https://github.com/r-lib/actions 6 | on: 7 | push: 8 | branches: [main, rc-**, dev] 9 | pull_request: 10 | branches: [main, dev] 11 | schedule: 12 | - cron: "0 13 * * 1" # every monday 13 | 14 | concurrency: 15 | group: ci-pkg-checks-${{ github.ref }}-1 16 | cancel-in-progress: true 17 | 18 | name: Package checks 19 | 20 | jobs: 21 | # routine: 22 | # uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1 23 | R-CMD-check: 24 | uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1 25 | with: 26 | minimum-r-version: "3.6" 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/.Rhistory 3 | 4 | *.local 5 | .Rproj.user 6 | # dist 7 | dist-ssr 8 | inst/doc 9 | docs 10 | 11 | **/how-to-videos-source/ 12 | 13 | # These always appear when I accidentally run yarn commands in the wrong directory 14 | .yarn/ 15 | .yarnrc.yml 16 | node_modules/ 17 | 18 | *.js.map 19 | *.css.map 20 | 21 | inst/*/.turbo/ 22 | **/storybook-static/* 23 | inst/inst/abstract_snake_tree/src/ 24 | inst/inst/abstract-snake-tree/src/scratch.js 25 | 26 | # Sometimes the debug process spits out compiled js files to here. 27 | inst/inst/ 28 | 29 | # Ignore build targets locally 30 | # inst/editor/build/ 31 | # inst/vscode-extension/build/ 32 | # inst/vscode-extension/media/build/ 33 | # inst/vscode-extension/*.vsix 34 | 35 | 36 | # Ignore virtual environment directory 37 | venv/ 38 | 39 | # Ignore cache files from python apps 40 | **/__pycache__/ -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ], 5 | "hints": { 6 | "compat-api/css": [ 7 | "default", 8 | { 9 | "ignore": [ 10 | "width: fit-content", 11 | "backdrop-filter" 12 | ] 13 | } 14 | ], 15 | "axe/aria": [ 16 | "default", 17 | { 18 | "aria-valid-attr-value": "off", 19 | "aria-required-parent": "off", 20 | "aria-required-children": "off" 21 | } 22 | ], 23 | "no-inline-styles": "off" 24 | } 25 | } -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults(trailing_whitespace_linter = NULL, cyclocomp_linter = NULL) 2 | encoding: "UTF-8" 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "[python]": { 4 | "editor.defaultFormatter": "ms-python.black-formatter" 5 | }, 6 | "python.formatting.provider": "none" 7 | } 8 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(launch_editor) 4 | -------------------------------------------------------------------------------- /R.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 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 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /R/validate_app_loc.R: -------------------------------------------------------------------------------- 1 | # Validate that we're pointing to a directory. If the user has supplied a 2 | # direct file. E.g. a app.R or app.py file we should back up the app loc to 3 | # the parent location 4 | validate_app_loc <- function(loc) { 5 | 6 | # If the file ends in app.R or ui.R or server.R we should back up the 7 | # location to the parent directory 8 | if (fs::path_ext(loc) %in% "R") { 9 | loc <- fs::path_dir(loc) 10 | } 11 | 12 | loc 13 | } 14 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://rstudio.github.io/shinyuieditor/ 2 | 3 | template: 4 | package: quillt 5 | params: 6 | footer: shinyuieditor is an R package developed by RStudio 7 | 8 | home: 9 | strip_header: false 10 | 11 | navbar: 12 | title: ~ 13 | type: default 14 | structure: 15 | left: [intro, quick-start, demo, articles, reference, news] 16 | right: [search, github] 17 | components: 18 | home: ~ 19 | demo: 20 | text: Live Demo 21 | href: articles/ui-editor-live-demo.html 22 | quick-start: 23 | text: Quick Start 24 | href: articles/quick-start.html 25 | github: 26 | icon: fab fa-github fa-lg 27 | href: https://github.com/rstudio/shinyuieditor 28 | 29 | reference: 30 | - title: Main launcher function 31 | desc: > 32 | Run the `shinyuieditor`. The only function you'll probably use. 33 | - contents: 34 | - launch_editor 35 | -------------------------------------------------------------------------------- /inst/communication-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "communication-types", 3 | "main": "./src/index.ts", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "editor": "*", 8 | "shared-configs": "*", 9 | "typescript": "^5.0.4" 10 | }, 11 | "scripts": { 12 | "type-check": "tsc -p ./ --noEmit", 13 | "lint": "eslint src --fix" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /inst/communication-types/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.module.css"; 4 | declare module "*.png"; 5 | -------------------------------------------------------------------------------- /inst/communication-types/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { BackendConnection, MessageDispatcher } from "./BackendConnection"; 2 | export { makeMessageDispatcherGeneric } from "./BackendConnection"; 3 | export type { MessageToClient, MessageToClientByPath } from "./MessageToClient"; 4 | export type { 5 | MessageToBackend, 6 | MessageToBackendByPath, 7 | } from "./MessageToBackend"; 8 | export type { TemplateSelection } from "./AppTemplates"; 9 | export type { InputSourceRequest } from "./MessageToBackend"; 10 | -------------------------------------------------------------------------------- /inst/communication-types/src/isRecord.ts: -------------------------------------------------------------------------------- 1 | export function isRecord(value: unknown): value is Record { 2 | return typeof value === "object" && value !== null; 3 | } 4 | -------------------------------------------------------------------------------- /inst/communication-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "shared-configs/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /inst/editor-component-lib/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("../shared-configs/postcss.config"); 2 | -------------------------------------------------------------------------------- /inst/editor-component-lib/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.module.css"; 2 | declare module "*.png"; 3 | -------------------------------------------------------------------------------- /inst/editor-component-lib/src/index.ts: -------------------------------------------------------------------------------- 1 | export { SUE } from "editor"; 2 | -------------------------------------------------------------------------------- /inst/editor-component-lib/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import twConfig from "../shared-configs/tailwind.config"; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | ...twConfig, 6 | content: ["../editor/index.html", "../editor/src/**/*.{ts,tsx}"], 7 | }; 8 | -------------------------------------------------------------------------------- /inst/editor-component-lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "shared-configs/tsconfig.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "esModuleInterop": true 6 | }, 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /inst/editor/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | VITE_SHOW_FAKE_PREVIEW=true 3 | VITE_PREBUILT_TREE=false 4 | -------------------------------------------------------------------------------- /inst/editor/.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [ main, master ] 5 | pull_request: 6 | branches: [ main, master ] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: '14.x' 16 | - name: Install dependencies 17 | run: yarn 18 | - name: Install Playwright Browsers 19 | run: npx playwright install --with-deps 20 | - name: Run Playwright tests 21 | run: yarn playwright test 22 | - uses: actions/upload-artifact@v2 23 | if: always() 24 | with: 25 | name: playwright-report 26 | path: playwright-report/ 27 | retention-days: 30 28 | -------------------------------------------------------------------------------- /inst/editor/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /playwright/.cache/ 5 | /test-results/ 6 | /playwright-report/ 7 | /playwright/.cache/ 8 | /.yarn/ 9 | /test-results/ 10 | /playwright-report/ 11 | /playwright/.cache/ 12 | -------------------------------------------------------------------------------- /inst/editor/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 3 | "addons": ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"], 4 | "framework": { 5 | name: "@storybook/react-vite", 6 | options: {} 7 | }, 8 | "features": { 9 | "storyStoreV7": true 10 | }, 11 | docs: { 12 | autodocs: true 13 | } 14 | }; -------------------------------------------------------------------------------- /inst/editor/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /inst/editor/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import "../src/App.css"; 2 | 3 | export const parameters = { 4 | actions: { argTypesRegex: "^on[A-Z].*" }, 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /inst/editor/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/build/favicon.ico -------------------------------------------------------------------------------- /inst/editor/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/build/logo192.png -------------------------------------------------------------------------------- /inst/editor/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/build/logo512.png -------------------------------------------------------------------------------- /inst/editor/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /inst/editor/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /inst/editor/build/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/build/tree-sitter.wasm -------------------------------------------------------------------------------- /inst/editor/playwright/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Testing Page 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /inst/editor/playwright/index.tsx: -------------------------------------------------------------------------------- 1 | // Import styles, initialize component theme here. 2 | // import '../src/common.css'; 3 | import "../src/App.css"; 4 | -------------------------------------------------------------------------------- /inst/editor/playwright/openAppScriptModal.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "@playwright/test"; 2 | import { expect } from "@playwright/test"; 3 | 4 | export async function openAppScriptModal(page: Page) { 5 | // Click the "Get app script" button to open the app script modal 6 | await page.click("text=Get app script"); 7 | 8 | // Wait for the modal to open 9 | await expect(page.getByRole("dialog")).toBeVisible(); 10 | } 11 | export async function closeAppScriptModal(page: Page) { 12 | // Close modal 13 | await page.click("text=Okay"); 14 | } 15 | -------------------------------------------------------------------------------- /inst/editor/playwright/utils/dragDrop.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "@playwright/test"; 2 | 3 | export async function dragDrop( 4 | page: Page, 5 | originSelector: string, 6 | destinationSelector: string 7 | ) { 8 | const originElement = await page.waitForSelector(originSelector); 9 | const destinationElement = await page.waitForSelector(destinationSelector); 10 | 11 | await originElement.hover(); 12 | await page.mouse.down(); 13 | const originBox = (await originElement.boundingBox())!; 14 | 15 | // Move mouse a tiny bit to drigger drag event 16 | await page.mouse.move(originBox.x + 5, originBox.y + 5); 17 | 18 | const destinationBox = (await destinationElement.boundingBox())!; 19 | 20 | await page.mouse.move( 21 | destinationBox.x + destinationBox.width / 2, 22 | destinationBox.y + destinationBox.height / 2 23 | ); 24 | // await destinationElement.hover(); 25 | await page.mouse.up(); 26 | } 27 | -------------------------------------------------------------------------------- /inst/editor/playwright/utils/dragInDir.ts: -------------------------------------------------------------------------------- 1 | import type { Locator, Page } from "@playwright/test"; 2 | 3 | export async function dragInDir( 4 | page: Page, 5 | toDragLocator: Locator, 6 | { x_px = 0, y_px = 0 }: { x_px?: number; y_px?: number } 7 | ) { 8 | const originBox = (await toDragLocator.boundingBox())!; 9 | 10 | await toDragLocator.hover(); 11 | await page.mouse.down(); 12 | await page.mouse.move(originBox.x + x_px, originBox.y + y_px); 13 | await page.mouse.up(); 14 | } 15 | -------------------------------------------------------------------------------- /inst/editor/playwright/utils/generate_docs_screenshots.mts: -------------------------------------------------------------------------------- 1 | import { chromium } from "playwright"; 2 | 3 | const screenshots_root = "../../vignettes/screenshots/"; 4 | 5 | (async () => { 6 | // Run with try and catch error with message telling user they need to run the dev server before running this script 7 | 8 | const browser = await chromium.launch(); 9 | const page = await browser.newPage(); 10 | try { 11 | await page.setViewportSize({ width: 1260, height: 800 }); 12 | await page.goto("localhost:3000"); 13 | 14 | await page.screenshot({ path: screenshots_root + "template-chooser.png" }); 15 | console.log("✅ Template chooser"); 16 | } catch (error) { 17 | console.error(`Failed to generate screenshots for docs. 18 | Make sure you have run the devYou need to run the dev server before running this script.`); 19 | } 20 | 21 | await browser.close(); 22 | })(); 23 | -------------------------------------------------------------------------------- /inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-chromium-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-chromium-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-chromium-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-firefox-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-firefox-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-firefox-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-webkit-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-webkit-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-webkit-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-bslib-cards.spec.ts-snapshots/Make-sure-cards-with-too-much-content-don-t-overflow-visually-1-webkit-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-chromium-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-chromium-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-chromium-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-firefox-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-firefox-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-firefox-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-webkit-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-webkit-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-webkit-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Landing-page-visual-regression-1-webkit-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-chromium-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-chromium-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-chromium-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-firefox-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-firefox-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-firefox-linux.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-webkit-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-webkit-darwin.png -------------------------------------------------------------------------------- /inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-webkit-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/playwright/visual-regression.spec.ts-snapshots/Template-Chooser-visual-regression-1-webkit-linux.png -------------------------------------------------------------------------------- /inst/editor/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../shared-configs/postcss.config"); 2 | -------------------------------------------------------------------------------- /inst/editor/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/public/favicon.ico -------------------------------------------------------------------------------- /inst/editor/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/public/logo192.png -------------------------------------------------------------------------------- /inst/editor/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/public/logo512.png -------------------------------------------------------------------------------- /inst/editor/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /inst/editor/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /inst/editor/public/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/public/tree-sitter.wasm -------------------------------------------------------------------------------- /inst/editor/src/AppTour/PropertiesPanelAbout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const PropertiesPanelAbout = ( 4 |
5 |

6 | After selecting an element in your app, you can adjust the settings for 7 | that element in the properties pane. 8 |

9 |

10 | Changes made will be automatically applied to your element both in the app 11 | view and your code so there's no need to save or submit these changes. 12 |

13 |
14 | ); 15 | -------------------------------------------------------------------------------- /inst/editor/src/AppTour/styles.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /inst/editor/src/DragAndDropHelpers/DragAndDropHelpers.tsx: -------------------------------------------------------------------------------- 1 | import type { NodePath } from "../ui-node-definitions/NodePath"; 2 | import type { ShinyUiNode } from "../ui-node-definitions/ShinyUiNode"; 3 | 4 | export type DraggedNodeInfo = { node: ShinyUiNode; currentPath?: NodePath }; 5 | -------------------------------------------------------------------------------- /inst/editor/src/EditorContainer/App_Layout_Sizes.ts: -------------------------------------------------------------------------------- 1 | import type React from "react"; 2 | 3 | export const PROPERTIES_PANEL_WIDTH_PX = 236; 4 | const ELEMENTS_PALETTE_WIDTH_PX = 174; 5 | const HEADER_HEIGHT_PX = 31; 6 | 7 | export const sizes_inline_styles = { 8 | "--elements-palette-width": `${ELEMENTS_PALETTE_WIDTH_PX}px`, 9 | "--header-height": `${HEADER_HEIGHT_PX}px`, 10 | "--properties-panel-width": `${PROPERTIES_PANEL_WIDTH_PX}px`, 11 | } as React.CSSProperties; 12 | -------------------------------------------------------------------------------- /inst/editor/src/EditorContainer/EditorContainer.module.css: -------------------------------------------------------------------------------- 1 | .EditorContainer { 2 | --padding: var(--horizontal-spacing); 3 | background-color: var(--background-grey, #edf2f7); 4 | 5 | height: 100%; 6 | width: 100%; 7 | position: relative; 8 | } 9 | -------------------------------------------------------------------------------- /inst/editor/src/EditorContainer/LanguageModeBadge.module.css: -------------------------------------------------------------------------------- 1 | .language_badge { 2 | width: var(--size-lg); 3 | color: var(--rstudio-blue); 4 | transform: translateY(-1px); 5 | } 6 | 7 | .language_badge > svg { 8 | height: 100%; 9 | width: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /inst/editor/src/EditorContainer/LostConnectionPopup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { useSelector } from "react-redux"; 4 | 5 | import PortalModal from "../components/PortalModal/PortalModal"; 6 | import type { RootState } from "../state/store"; 7 | 8 | export function LostConnectionPopup() { 9 | const connectedToServer = useSelector( 10 | (state: RootState) => state.connected_to_server 11 | ); 12 | 13 | if (connectedToServer) return null; 14 | 15 | return ( 16 | {}} onCancel={() => {}}> 17 |

18 | Lost connection to backend. Check console where editor was launched for 19 | details. 20 |

21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /inst/editor/src/EditorContainer/MessageForUser.module.css: -------------------------------------------------------------------------------- 1 | .message_for_user { 2 | height: 100%; 3 | display: grid; 4 | place-content: center; 5 | } 6 | .message_container { 7 | background-color: var(--rstudio-white); 8 | border-radius: var(--corner-radius); 9 | border: var(--outline); 10 | width: 600px; 11 | max-width: 95%; 12 | padding: 25px; 13 | } 14 | 15 | .message_container > h2 { 16 | font-size: 24px; 17 | margin-block-end: 18px; 18 | } 19 | 20 | .message_container > p { 21 | padding: 0; 22 | } 23 | 24 | .error_msg { 25 | color: var(--red); 26 | font-family: var(--mono-fonts); 27 | } 28 | -------------------------------------------------------------------------------- /inst/editor/src/EditorContainer/MessageForUser.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styles from "./MessageForUser.module.css"; 4 | 5 | export function MessageForUser({ children }: { children: React.ReactNode }) { 6 | return ( 7 |
8 |
{children}
9 |
10 | ); 11 | } 12 | export function ErrorMessagePrinter({ msg }: { msg: string }) { 13 | const msg_lines = msg.split("\n"); 14 | 15 | return ( 16 | <> 17 | {msg_lines.map((line) => ( 18 |

{line}

19 | ))} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /inst/editor/src/EditorContainer/getAllInputOutputIdsInApp.tsx: -------------------------------------------------------------------------------- 1 | import type { ShinyUiNode } from "../ui-node-definitions/ShinyUiNode"; 2 | import { walkUiNode } from "../ui-node-definitions/walkUiNode"; 3 | 4 | export function getAllInputOutputIdsInApp(ui_tree: ShinyUiNode): string[] { 5 | const ids: string[] = []; 6 | 7 | walkUiNode(ui_tree, (node) => { 8 | const namedArgs = node.namedArgs; 9 | 10 | const idField = namedArgs.id || namedArgs.outputId || namedArgs.inputId; 11 | 12 | if (typeof idField === "string") { 13 | ids.push(idField); 14 | } 15 | }); 16 | 17 | return ids; 18 | } 19 | -------------------------------------------------------------------------------- /inst/editor/src/EditorLayout/PanelHeader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { mergeClasses } from "../utils/mergeClasses"; 4 | 5 | import styles from "./EditorLayout.module.css"; 6 | 7 | export function PanelHeader({ 8 | children, 9 | className = "", 10 | }: { 11 | children: React.ReactNode; 12 | className?: string; 13 | }) { 14 | return ( 15 |

{children}

16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /inst/editor/src/ElementsPalette/ElementsPalette.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import ReduxProvider from "../state/ReduxProvider"; 4 | 5 | import ElementsPalette from "."; 6 | 7 | export default { 8 | title: "Elements Palette", 9 | component: ElementsPalette, 10 | }; 11 | 12 | // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args 13 | export const Primary = () => ( 14 |
22 | 23 | 24 | 25 |
26 | ); 27 | -------------------------------------------------------------------------------- /inst/editor/src/SUE.module.css: -------------------------------------------------------------------------------- 1 | .error_fallback_container { 2 | display: grid; 3 | place-content: center; 4 | height: 100%; 5 | } 6 | .error_fallback { 7 | filter: var(--simple-drop-shadow-filter); 8 | padding: var(--size-lg); 9 | rotate: 3deg; 10 | } 11 | -------------------------------------------------------------------------------- /inst/editor/src/SUE.stories.tsx: -------------------------------------------------------------------------------- 1 | import { staticDispatchFromTree } from "./backendCommunication/staticBackend"; 2 | import { basicNavbarPage } from "./python-parsing/pythonTreesitterToUiTree.test"; 3 | import { SUE } from "./SUE"; 4 | import { basicGridPage } from "./ui-node-definitions/sample_ui_trees/basicGridPage"; 5 | import { bslibCards } from "./ui-node-definitions/sample_ui_trees/bslibCards"; 6 | 7 | export const SueShowcase = (args: Parameters[0]) => { 8 | return ; 9 | }; 10 | 11 | export default { 12 | title: "Full App", 13 | component: SUE, 14 | args: {}, 15 | }; 16 | 17 | export const GridApp = () => ( 18 | 19 | ); 20 | export const BslibCard = () => ( 21 | 22 | ); 23 | export const NavbarPage = () => ( 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /inst/editor/src/SettingsPanel/LabeledInputCategory.tsx: -------------------------------------------------------------------------------- 1 | export function LabeledInputCategory({ 2 | label, 3 | children, 4 | }: { 5 | label: string; 6 | children: React.ReactNode; 7 | }) { 8 | return ( 9 |
10 |
11 | 12 |
13 | 14 |
{children}
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /inst/editor/src/SettingsPanel/PathBreadcrumbLinear.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | --spacing: 14px; 3 | padding-block: var(--vertical-spacing); 4 | display: flex; 5 | flex-direction: row; 6 | gap: var(--spacing); 7 | position: relative; 8 | width: 100%; 9 | max-width: 100%; 10 | overflow: auto; 11 | } 12 | 13 | .node { 14 | width: fit-content; 15 | max-width: 300px; 16 | white-space: nowrap; 17 | position: relative; 18 | cursor: pointer; 19 | } 20 | 21 | .node:hover { 22 | text-decoration: underline; 23 | } 24 | 25 | .node[data-disable-click="true"] { 26 | cursor: unset; 27 | pointer-events: none; 28 | } 29 | 30 | .node:last-child { 31 | color: var(--rstudio-blue); 32 | text-decoration: underline; 33 | } 34 | 35 | .node:not(:last-child)::after { 36 | content: "›"; 37 | font-size: 1.2rem; 38 | line-height: 0.8rem; 39 | text-align: center; 40 | position: absolute; 41 | width: var(--spacing); 42 | top: 2px; 43 | opacity: 0.5; 44 | } 45 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Bslib/BslibCard.tsx: -------------------------------------------------------------------------------- 1 | import icon from "../../assets/icons/shinyContainer.png"; 2 | import { bslib_card } from "../../ui-node-definitions/Bslib/card"; 3 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 4 | 5 | import { BslibCardContainer } from "./BslibCardContainer"; 6 | import { renderCardElements } from "./Utils/render_card_elements"; 7 | 8 | export const bslibCardInfo = addEditorInfoToUiNode(bslib_card, { 9 | iconSrc: icon, 10 | UiComponent: ({ namedArgs, children = [], path, wrapperProps }) => { 11 | return ( 12 | 13 | {renderCardElements(children, path)} 14 | 15 | ); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Bslib/BslibCardBodyFill.tsx: -------------------------------------------------------------------------------- 1 | import { card_body } from "../../ui-node-definitions/Bslib/card_body"; 2 | import { ChildrenWithDropNodes } from "../ChildrenWithDropNodes"; 3 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 4 | 5 | import { CardBody } from "./Utils/CardElements"; 6 | 7 | export const bslibCardBodyInfo = addEditorInfoToUiNode(card_body, { 8 | UiComponent: ({ namedArgs, children = [], path, wrapperProps }) => { 9 | return ( 10 | 11 | 17 | 18 | ); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Bslib/BslibCardFooter.tsx: -------------------------------------------------------------------------------- 1 | import { card_footer } from "../../ui-node-definitions/Bslib/card_footer"; 2 | import { ChildrenWithDropNodes } from "../ChildrenWithDropNodes"; 3 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 4 | 5 | import { CardFooter } from "./Utils/CardElements"; 6 | 7 | export const bslibCardFooterInfo = addEditorInfoToUiNode(card_footer, { 8 | UiComponent: ({ namedArgs, children = [], path, wrapperProps }) => { 9 | return ( 10 | 11 | 17 | 18 | ); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Bslib/BslibCardHeader.tsx: -------------------------------------------------------------------------------- 1 | import { card_header } from "../../ui-node-definitions/Bslib/card_header"; 2 | import { ChildrenWithDropNodes } from "../ChildrenWithDropNodes"; 3 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 4 | 5 | import { CardHeader } from "./Utils/CardElements"; 6 | 7 | export const bslibCardHeaderInfo = addEditorInfoToUiNode(card_header, { 8 | UiComponent: (node) => { 9 | const { children, path, wrapperProps } = node; 10 | 11 | return ( 12 | 13 | 19 | 20 | ); 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Bslib/SidebarDropWatcherPanel.tsx: -------------------------------------------------------------------------------- 1 | import { DropWatcherPanel } from "../../DragAndDropHelpers/DropWatcherPanel"; 2 | import type { NodePath } from "../../ui-node-definitions/NodePath"; 3 | 4 | import classes from "./Sidebar.module.css"; 5 | 6 | export function SidebarDropWatcherPanel({ path }: { path: NodePath }) { 7 | return ( 8 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Bslib/ValueBox/IconSelector.stories.tsx: -------------------------------------------------------------------------------- 1 | // A simple storybook story for the IconSelector component 2 | 3 | import { IconSelector } from "./IconSelector"; 4 | 5 | export default { 6 | title: "Icon Selector", 7 | component: IconSelector, 8 | }; 9 | 10 | export const Primary = () => ( 11 |
25 | console.log(icon)} 28 | /> 29 |
30 | ); 31 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Bslib/index.tsx: -------------------------------------------------------------------------------- 1 | export { bslibCardInfo } from "./BslibCard"; 2 | export { bslibCardBodyInfo } from "./BslibCardBodyFill"; 3 | export { bslibCardFooterInfo } from "./BslibCardFooter"; 4 | export { bslibCardHeaderInfo } from "./BslibCardHeader"; 5 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ChildrenWithDropNodes.module.css: -------------------------------------------------------------------------------- 1 | @value can_accept_drop from "../DragAndDropHelpers/DropWatcherPanel.module.css"; 2 | 3 | /* The final panel should grow as large as it needs to fill end of card */ 4 | .drop_watcher:last-of-type { 5 | flex: 1; 6 | height: unset; 7 | } 8 | 9 | .drop_watcher:global(.can_accept_drop) { 10 | /* Add subtle transition to ease jarring layout transition */ 11 | transition-property: height flex-grow flex; 12 | transition-duration: 0.1s; 13 | transition-timing-function: ease-in; 14 | } 15 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/GridlayoutGridCardPlot/styles.module.css: -------------------------------------------------------------------------------- 1 | .gridCardPlot { 2 | background-color: var(--rstudio-white); 3 | width: 100%; 4 | height: 100%; 5 | max-width: 100%; 6 | max-height: 100%; 7 | position: relative; 8 | } 9 | 10 | .gridCardPlot > h1 { 11 | font-size: 2rem; 12 | } 13 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/GridlayoutGridPage/index.tsx: -------------------------------------------------------------------------------- 1 | import { grid_page } from "../../../ui-node-definitions/gridlayout/grid_page"; 2 | import { addEditorInfoToUiNode } from "../../utils/add_editor_info_to_ui_node"; 3 | import { GridContainerElement } from "../Utils/GridContainerElement/GridContainerElement"; 4 | import { 5 | removeDeletedGridAreaFromLayout, 6 | updateGridLayoutAreaOnItemAreaChange, 7 | } from "../Utils/watchAndReactToGridAreaUpdatesupdate"; 8 | 9 | export const gridlayoutGridPageInfo = addEditorInfoToUiNode(grid_page, { 10 | UiComponent: (args) => { 11 | return ; 12 | }, 13 | stateUpdateSubscribers: { 14 | UPDATE_NODE: updateGridLayoutAreaOnItemAreaChange, 15 | DELETE_NODE: removeDeletedGridAreaFromLayout, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/Utils/BsCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface BsCardProps extends React.ComponentPropsWithoutRef<"div"> {} 4 | 5 | const BsCard = React.forwardRef( 6 | ({ className = "", children, ...props }, ref) => { 7 | const combinedClasses = className + " card"; 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | } 14 | ); 15 | 16 | export { BsCard }; 17 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/Utils/EditableGridContainer/TractSizer.tsx: -------------------------------------------------------------------------------- 1 | import type { TractDirection } from "util-functions/src/matrix-helpers"; 2 | 3 | import classes from "./TractSizer.module.css"; 4 | import type { TractEventListener } from "./useDragToResizeGrid"; 5 | 6 | export function TractSizerHandle({ 7 | dir, 8 | index, 9 | onStartDrag, 10 | }: { 11 | dir: TractDirection; 12 | index: number; 13 | onStartDrag: TractEventListener; 14 | }) { 15 | return ( 16 |
onStartDrag({ e, dir, index })} 22 | style={{ [dir === "rows" ? "gridRow" : "gridColumn"]: index }} 23 | /> 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/Utils/EditableGridContainer/resizableGrid.module.css: -------------------------------------------------------------------------------- 1 | .ResizableGrid { 2 | --grid-gap: 5px; 3 | 4 | --grid-pad: var(--pad, 10px); 5 | 6 | height: 100%; 7 | width: 100%; 8 | min-height: 80px; 9 | min-width: 400px; 10 | display: grid; 11 | padding: var(--grid-pad); 12 | gap: var(--grid-gap); 13 | position: relative; 14 | /* Setup z-index stack so we can ensure our tract controls sit below the items */ 15 | isolation: isolate; 16 | } 17 | 18 | .ResizableGrid > * { 19 | /* By putting explicit min values on sizes of the items we stop them from 20 | blowing out the grid by staying too big. See 21 | https://css-tricks.com/preventing-a-grid-blowout/ for more details */ 22 | min-width: 0; 23 | min-height: 0; 24 | } 25 | 26 | div#size-detection-cell { 27 | width: 100%; 28 | height: 100%; 29 | 30 | /* One of these will get over-ridden by inline css */ 31 | grid-row: 1/-1; 32 | grid-column: 1/-1; 33 | } 34 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/Utils/EditableGridContainer/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { getAreaMatrixFromStyleDeclaration } from "./utils"; 2 | 3 | describe("Get layout areas from style string", () => { 4 | test("Handles non-filled area cells", () => { 5 | const areaString = `"a b f" ". . f" "g g f" "c d f" "e d f"`; 6 | 7 | expect(getAreaMatrixFromStyleDeclaration(areaString)).toEqual([ 8 | ["a", "b", "f"], 9 | [".", ".", "f"], 10 | ["g", "g", "f"], 11 | ["c", "d", "f"], 12 | ["e", "d", "f"], 13 | ]); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/Utils/GridContainerElement/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | /* background-color: var(--bg-color); */ 4 | outline: var(--outline); 5 | position: relative; 6 | height: 100%; 7 | width: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/Utils/useSetLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import type { GridLayoutAction } from "./GridContainerElement/gridLayoutReducer"; 4 | 5 | export const LayoutDispatchContext = 6 | React.createContext | null>(null); 7 | 8 | export function useSetLayout() { 9 | const setLayout = React.useContext(LayoutDispatchContext); 10 | 11 | return setLayout; 12 | } 13 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/Utils/useUpdateUiArguments.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useDispatch } from "react-redux"; 4 | 5 | import { UPDATE_NODE } from "../../../state/app_info"; 6 | import type { GridLayoutArgs } from "../../../ui-node-definitions/gridlayout/GridLayoutArgs"; 7 | import type { NodePath } from "../../../ui-node-definitions/NodePath"; 8 | 9 | export function useUpdateNamedArgs(path: NodePath) { 10 | const dispatch = useDispatch(); 11 | 12 | const updateArguments = React.useCallback( 13 | (newArguments: GridLayoutArgs) => { 14 | dispatch( 15 | UPDATE_NODE({ 16 | path: path, 17 | node: { 18 | namedArgs: newArguments, 19 | }, 20 | }) 21 | ); 22 | }, 23 | [dispatch, path] 24 | ); 25 | 26 | return updateArguments; 27 | } 28 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/Gridlayout/index.ts: -------------------------------------------------------------------------------- 1 | export { gridlayoutCardInfo } from "./GridlayoutCard"; 2 | export { gridlayoutGridCardPlotInfo } from "./GridlayoutGridCardPlot"; 3 | export { gridlayoutTextPanelInfo } from "./GridlayoutCardText"; 4 | export { gridlayoutGridContainerInfo } from "./GridlayoutGridContainer"; 5 | export { gridlayoutGridPageInfo } from "./GridlayoutGridPage"; 6 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/PlotlyPlotlyOutput/styles.scss: -------------------------------------------------------------------------------- 1 | .plotlyPlotlyOutput { 2 | position: relative; 3 | height: 100%; 4 | width: 100%; 5 | 6 | .title-bar { 7 | display: flex; 8 | flex-wrap: wrap; 9 | justify-content: space-between; 10 | } 11 | 12 | .plotly-name { 13 | color: var(--rstudio-blue); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyActionButton/index.tsx: -------------------------------------------------------------------------------- 1 | import buttonIcon from "../../assets/icons/shinyButton.png"; 2 | import Button from "../../components/Inputs/Button/Button"; 3 | import { input_action_button } from "../../ui-node-definitions/Shiny/input_action_button"; 4 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 5 | 6 | import classes from "./styles.module.css"; 7 | 8 | export const shinyActionButtonInfo = addEditorInfoToUiNode( 9 | input_action_button, 10 | { 11 | UiComponent: ({ namedArgs, wrapperProps }) => { 12 | const { label = "My Action Button", width } = namedArgs; 13 | 14 | return ( 15 |
16 | 17 |
18 | ); 19 | }, 20 | iconSrc: buttonIcon, 21 | } 22 | ); 23 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyActionButton/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | grid-template-rows: 1fr; 4 | grid-template-columns: 1fr; 5 | place-content: center; 6 | padding: 5px; 7 | max-height: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyCheckboxGroupInput/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | padding: 4px; 4 | } 5 | 6 | .container > input { 7 | width: 100%; 8 | } 9 | 10 | .container > label { 11 | font-weight: 700; 12 | } 13 | 14 | .checkbox { 15 | /* outline: 1px solid salmon; */ 16 | display: flex; 17 | align-items: center; 18 | gap: 4px; 19 | } 20 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyCheckboxInput/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | padding: 4px; 4 | } 5 | 6 | .container > input { 7 | width: 100%; 8 | } 9 | 10 | .label { 11 | margin-left: 5px; 12 | } 13 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyLayoutSidebar/ShinyLayoutSidebar.module.css: -------------------------------------------------------------------------------- 1 | .noTabsMessage { 2 | padding: 5px; 3 | } 4 | 5 | .container { 6 | /* background-color: salmon; */ 7 | display: grid; 8 | grid-template-columns: auto 1fr; 9 | } 10 | 11 | .dropWatcherPanel { 12 | background-color: forestgreen; 13 | } 14 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyMarkdown/markdown.tsx: -------------------------------------------------------------------------------- 1 | import ReactMarkdown from "react-markdown"; 2 | 3 | import icon from "../../assets/icons/shinyMarkdown.png"; 4 | import { markdown_node } from "../../ui-node-definitions/Shiny/markdown"; 5 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 6 | 7 | export const markdownNodeInfo = addEditorInfoToUiNode(markdown_node, { 8 | iconSrc: icon, 9 | UiComponent: ({ namedArgs: { mds }, wrapperProps }) => { 10 | return ( 11 |
12 | 13 |
14 | ); 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyNumericInput/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | padding: 4px; 4 | } 5 | 6 | .container > input { 7 | width: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyPlotOutput/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-height: 100%; 3 | } 4 | 5 | .plotPlaceholder { 6 | --pad: 15px; 7 | --label-height: 30px; 8 | --plot-offset: calc(2 * var(--pad) + var(--label-height)); 9 | padding: var(--pad); 10 | height: 100%; 11 | max-height: 100%; 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: space-evenly; 15 | align-items: center; 16 | background-color: var(--light-grey); 17 | } 18 | 19 | .plotPlaceholder .label { 20 | height: var(--label-height); 21 | line-height: var(--label-height); 22 | } 23 | 24 | .plotPlaceholder > svg { 25 | margin-inline: auto; 26 | } 27 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinySelectInput/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | padding: 4px; 4 | } 5 | 6 | .container > input { 7 | width: 100%; 8 | } 9 | 10 | .container > label { 11 | font-weight: 700; 12 | } 13 | 14 | .container > select { 15 | display: block; 16 | width: 100%; 17 | height: 40px; 18 | } 19 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyTextInput/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | padding: 4px; 4 | } 5 | 6 | .container > input { 7 | width: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyTextOutput/ShinyTextOutput.tsx: -------------------------------------------------------------------------------- 1 | import uiIcon from "../../assets/icons/shinyTextOutput.png"; 2 | import { NodeWrapper } from "../../components/UiNode/NodeWraper"; 3 | import { output_text } from "../../ui-node-definitions/Shiny/output_text"; 4 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 5 | 6 | export const shinyTextOutputInfo = addEditorInfoToUiNode(output_text, { 7 | iconSrc: uiIcon, 8 | UiComponent: ({ namedArgs, wrapperProps }) => { 9 | return ( 10 | 14 | Dynamic text from output${namedArgs.outputId} 15 | 16 | ); 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyUiOutput/index.tsx: -------------------------------------------------------------------------------- 1 | import uiIcon from "../../assets/icons/shinyImage.png"; 2 | import { output_ui } from "../../ui-node-definitions/Shiny/output_ui"; 3 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 4 | 5 | import classes from "./styles.module.css"; 6 | 7 | export const shinyUiOutputInfo = addEditorInfoToUiNode(output_ui, { 8 | iconSrc: uiIcon, 9 | UiComponent: ({ namedArgs, wrapperProps }) => { 10 | const { outputId = "shiny-ui-output" } = namedArgs; 11 | 12 | return ( 13 |
14 |
15 | This is a a dynamic UI Output {outputId}! 16 |
17 |
18 | ); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/ShinyUiOutput/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | grid-template-rows: 1fr; 4 | grid-template-columns: 1fr; 5 | place-content: center; 6 | padding: 1rem; 7 | max-height: 100%; 8 | min-height: 200px; 9 | background-color: var(--light-grey); 10 | border-radius: var(--corner-radius); 11 | } 12 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/TextNode/index.tsx: -------------------------------------------------------------------------------- 1 | import icon from "../../assets/icons/shinyText.png"; 2 | import { sizeNameToTag } from "../../r-parsing/NodeTypes/TextNode"; 3 | import { text_node } from "../../ui-node-definitions/internal/text_node"; 4 | import { addEditorInfoToUiNode } from "../utils/add_editor_info_to_ui_node"; 5 | 6 | import styles from "./styles.module.css"; 7 | 8 | export const textNodeInfo = addEditorInfoToUiNode(text_node, { 9 | iconSrc: icon, 10 | UiComponent: ({ 11 | namedArgs: { contents, decoration, size = "default" }, 12 | wrapperProps, 13 | }) => { 14 | const WrapperComp = sizeNameToTag[size]; 15 | 16 | return ( 17 | 22 | {contents} 23 | 24 | ); 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/TextNode/styles.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: relative; 3 | display: inline-block; 4 | color: var(--font-color); 5 | } 6 | 7 | .wrapper[data-decoration="italic"] { 8 | font-style: italic; 9 | } 10 | 11 | .wrapper[data-decoration="bold"] { 12 | font-weight: bold; 13 | } 14 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/UnknownUiFunction/formatFunctionText.test.ts: -------------------------------------------------------------------------------- 1 | import { formatFunctionText } from "./formatFunctionText"; 2 | 3 | describe("Can modify text to a decent format", () => { 4 | test("no-arg function", () => { 5 | expect(formatFunctionText(`simple_fn()`)).toEqual(`simple_fn()`); 6 | }); 7 | test("single-arg function", () => { 8 | expect(formatFunctionText(`gt::gt_output("stockTable")`)).toEqual( 9 | `gt::gt_output( 10 | "stockTable" 11 | )` 12 | ); 13 | }); 14 | test("double-arg function", () => { 15 | expect(formatFunctionText(`my_custom_fn(foo="bar", arg2=false)`)).toEqual( 16 | `my_custom_fn( 17 | foo="bar", 18 | arg2=false 19 | )` 20 | ); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/__TestingErrorNode/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | height: 100%; 4 | width: 100%; 5 | display: grid; 6 | place-content: center; 7 | } 8 | 9 | .container > button { 10 | width: fit-content; 11 | font-size: 3rem; 12 | justify-self: center; 13 | } 14 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/utils/InputOutputTitle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const InputOutputTitle = ({ 4 | type, 5 | name, 6 | className, 7 | }: { 8 | type: "input" | "output"; 9 | name: string; 10 | className?: string; 11 | }) => { 12 | return ( 13 | 14 | {type}$ 15 | {name} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /inst/editor/src/Shiny-Ui-Elements/utils/RenderUiChildren.tsx: -------------------------------------------------------------------------------- 1 | import UiNode from "../../components/UiNode/UiNode"; 2 | import type { NodePath } from "../../ui-node-definitions/NodePath"; 3 | import { 4 | makeChildPath, 5 | pathToString, 6 | } from "../../ui-node-definitions/nodePathUtils"; 7 | import type { ShinyUiNode } from "../../ui-node-definitions/ShinyUiNode"; 8 | 9 | /** 10 | * Render basic Ui children 11 | * @param args 12 | * @returns 13 | */ 14 | export function RenderUiChildren({ 15 | children, 16 | parentPath, 17 | }: { 18 | children: Array; 19 | parentPath: NodePath; 20 | }) { 21 | return ( 22 | <> 23 | {children.map((childNode, i) => { 24 | const nodePath = makeChildPath(parentPath, i); 25 | return ( 26 | 31 | ); 32 | })} 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /inst/editor/src/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | SVE -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/ShinyDynamicUIOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/ShinyDynamicUIOutput.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/alignItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/alignItem.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/alignItemBottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/alignItemBottom.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/alignItemMiddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/alignItemMiddle.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/alignItemTop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/alignItemTop.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/alignTextCenter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/alignTextCenter.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/alignTextLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/alignTextLeft.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/alignTextRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/alignTextRight.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/redo.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyButton.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyCheckgroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyCheckgroup.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyContainer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyContainer.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyDatatable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyDatatable.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyDateinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyDateinput.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyDaterange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyDaterange.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyFileinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyFileinput.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyGridContainer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyGridContainer.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyImage.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyMarkdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyMarkdown.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyNumericinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyNumericinput.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyPassword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyPassword.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyPlot.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyRadioButtons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyRadioButtons.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinySelectbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinySelectbox.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinySlider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinySlider.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyTab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyTab.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyTable.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyTabsetPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyTabsetPanel.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyText.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyTextBox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyTextBox.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyTextOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyTextOutput.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyTextinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyTextinput.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinyValueBox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinyValueBox.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/shinycheckbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/shinycheckbox.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/tabPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/tabPanel.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/tabsetPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/tabsetPanel.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/tour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/tour.png -------------------------------------------------------------------------------- /inst/editor/src/assets/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/assets/icons/undo.png -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignBottom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignCenter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignHCenter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignHSpread.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignLeft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignSpread.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignTop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignVCenter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/alignVSpread.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/down-spinner-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/assets/svg-icons/up-spinner-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/editor/src/components/AppPreview/python_app_to_shinylive_url.tsx: -------------------------------------------------------------------------------- 1 | import LZString from "lz-string"; 2 | 3 | /** 4 | * Create a ShinyLive editor Url from a given app script 5 | */ 6 | 7 | export function pythonAppToShinyliveUrl( 8 | app_text: string, 9 | mode: "app" | "editor" 10 | ): string { 11 | const encoded_app = LZString.compressToEncodedURIComponent( 12 | JSON.stringify([ 13 | { 14 | name: "app.py", 15 | content: app_text, 16 | type: "text", 17 | }, 18 | ]) 19 | ); 20 | 21 | const url_prefix = mode === "app" ? appUrlPrefix : editorUrlPrefix; 22 | return url_prefix + "#code=" + encoded_app; 23 | } 24 | const editorUrlPrefix = "https://shinylive.io/py/editor/"; 25 | const appUrlPrefix = "https://shinylive.io/py/app/"; 26 | -------------------------------------------------------------------------------- /inst/editor/src/components/CategoryDivider/index.tsx: -------------------------------------------------------------------------------- 1 | import classes from "./styles.module.css"; 2 | 3 | function CategoryDivider({ children }: { children: React.ReactNode }) { 4 | return
{children}
; 5 | } 6 | 7 | export default CategoryDivider; 8 | -------------------------------------------------------------------------------- /inst/editor/src/components/CategoryDivider/styles.module.css: -------------------------------------------------------------------------------- 1 | .categoryDivider { 2 | display: block; 3 | position: relative; 4 | isolation: isolate; 5 | height: var(--vertical-spacing); 6 | display: flex; 7 | align-items: center; 8 | } 9 | 10 | .categoryDivider > * { 11 | /* text-transform: capitalize; */ 12 | background-color: var(--light-grey); 13 | } 14 | 15 | .categoryDivider::before { 16 | content: ""; 17 | position: absolute; 18 | top: 50%; 19 | left: 0; 20 | width: 100%; 21 | height: 1px; 22 | background-color: var(--divider-color); 23 | z-index: -1; 24 | opacity: 0.5; 25 | } 26 | -------------------------------------------------------------------------------- /inst/editor/src/components/ErrorCatcher/GeneralErrorView.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | --inset: var(--size-md); 3 | height: calc(100% - var(--inset) * 2); 4 | width: calc(100% - var(--inset) * 2); 5 | margin: var(--inset); 6 | padding: var(--size-md); 7 | 8 | display: flex; 9 | flex-direction: column; 10 | gap: var(--size-md); 11 | overflow: auto; 12 | } 13 | 14 | .header { 15 | color: var(--red); 16 | font-size: 1.5rem; 17 | } 18 | 19 | .information { 20 | margin-block: var(--size-sm); 21 | font-style: italic; 22 | } 23 | 24 | .error_msg { 25 | display: block; 26 | background-color: var(--light-grey); 27 | color: var(--red); 28 | font-family: monospace; 29 | padding: var(--size-sm); 30 | } 31 | 32 | .actions { 33 | margin-block-start: auto; 34 | display: flex; 35 | justify-content: center; 36 | gap: var(--size-sm); 37 | flex-wrap: wrap; 38 | } 39 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignBottom.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignBottom = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignBottom; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignCenter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignCenter = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignCenter; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignHCenter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignHCenter = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignHCenter; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignHSpread.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignHSpread = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignHSpread; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignLeft.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignLeft = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignLeft; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignRight.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignRight = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignRight; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignSpread.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignSpread = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignSpread; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignTop.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignTop = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignTop; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignVCenter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignVCenter = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignVCenter; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/AlignVSpread.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgAlignVSpread = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgAlignVSpread; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/DownSpinnerButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgDownSpinnerButton = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgDownSpinnerButton; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/README.md: -------------------------------------------------------------------------------- 1 | The components in this folder are automatically generated using the library `svgr`. 2 | 3 | To add a new icon or update existing run the following command 4 | 5 | ``` 6 | yarn build-icons 7 | 8 | yarn lint 9 | ``` 10 | 11 | The second `lint` command is needed to update the type imports to fit with the eslint rules 12 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/Redo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgRedo = (props: SVGProps) => ( 5 | 13 | 18 | 19 | ); 20 | 21 | export default SvgRedo; 22 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/Trash.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgTrash = (props: SVGProps) => ( 5 | 13 | 19 | 24 | 30 | 31 | ); 32 | 33 | export default SvgTrash; 34 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/Undo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgUndo = (props: SVGProps) => ( 5 | 13 | 18 | 19 | ); 20 | 21 | export default SvgUndo; 22 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/UpSpinnerButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import type { SVGProps } from "react"; 3 | 4 | const SvgUpSpinnerButton = (props: SVGProps) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | export default SvgUpSpinnerButton; 18 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/generated/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AlignBottom } from "./AlignBottom"; 2 | export { default as AlignCenter } from "./AlignCenter"; 3 | export { default as AlignHCenter } from "./AlignHCenter"; 4 | export { default as AlignHSpread } from "./AlignHSpread"; 5 | export { default as AlignLeft } from "./AlignLeft"; 6 | export { default as AlignRight } from "./AlignRight"; 7 | export { default as AlignSpread } from "./AlignSpread"; 8 | export { default as AlignTop } from "./AlignTop"; 9 | export { default as AlignVCenter } from "./AlignVCenter"; 10 | export { default as AlignVSpread } from "./AlignVSpread"; 11 | export { default as DownSpinnerButton } from "./DownSpinnerButton"; 12 | export { default as Redo } from "./Redo"; 13 | export { default as Trash } from "./Trash"; 14 | export { default as Undo } from "./Undo"; 15 | export { default as UpSpinnerButton } from "./UpSpinnerButton"; 16 | -------------------------------------------------------------------------------- /inst/editor/src/components/Icons/styles.module.css: -------------------------------------------------------------------------------- 1 | img.icon { 2 | height: 30px; 3 | /* outline: 2px solid green; */ 4 | display: block; 5 | } 6 | -------------------------------------------------------------------------------- /inst/editor/src/components/Inputs/CSSUnitInput/CSSUnitInfo.module.css: -------------------------------------------------------------------------------- 1 | .infoIcon { 2 | width: 24px; 3 | color: var(--rstudio-blue); 4 | background-color: transparent; 5 | font-size: 19px; 6 | display: grid; 7 | place-content: center; 8 | } 9 | 10 | .container { 11 | width: min(100%, max-content); 12 | padding: 4px; 13 | } 14 | 15 | .header { 16 | border-bottom: 1px solid var(--divider-color, pink); 17 | margin-bottom: 3px; 18 | padding-bottom: 3px; 19 | } 20 | 21 | .info { 22 | display: grid; 23 | grid-template-columns: auto auto; 24 | gap: 4px; 25 | } 26 | 27 | .unit { 28 | text-align: end; 29 | font-weight: bold; 30 | } 31 | .description { 32 | font-style: italic; 33 | } 34 | -------------------------------------------------------------------------------- /inst/editor/src/components/Inputs/OptionsDropdown/styles.scss: -------------------------------------------------------------------------------- 1 | .OptionsDropdown { 2 | border-radius: var(--corner-radius); 3 | padding: 2px 5px; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /inst/editor/src/components/PopoverEl/styles.module.css: -------------------------------------------------------------------------------- 1 | .popoverMarkdown { 2 | max-width: 300px; 3 | } 4 | 5 | .popoverMarkdown p:last-of-type { 6 | margin-bottom: 0; 7 | } 8 | 9 | .popoverMarkdown code { 10 | font-family: var(--mono-fonts); 11 | } 12 | -------------------------------------------------------------------------------- /inst/editor/src/components/PortalModal/Portal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import * as ReactDOM from "react-dom"; 4 | 5 | /** 6 | * React portal based on https://stackoverflow.com/a/59154364 7 | * @param children Child elements 8 | * @param el HTML element to create. default: div 9 | */ 10 | 11 | export function Portal({ 12 | children, 13 | el = "div", 14 | }: { 15 | el?: string; 16 | children: React.ReactNode; 17 | }) { 18 | const [container] = React.useState(document.createElement(el)); 19 | 20 | React.useEffect(() => { 21 | document.body.appendChild(container); 22 | return () => { 23 | document.body.removeChild(container); 24 | }; 25 | }, [container]); 26 | 27 | return ReactDOM.createPortal(children, container); 28 | } 29 | -------------------------------------------------------------------------------- /inst/editor/src/components/Tabs/TabPanel/TabPanel.tsx: -------------------------------------------------------------------------------- 1 | interface TabPanelProps extends React.ComponentPropsWithoutRef<"div"> { 2 | title: string; 3 | } 4 | 5 | function TabPanel({ title, children, ...divProps }: TabPanelProps) { 6 | return ( 7 |
13 | {children} 14 |
15 | ); 16 | } 17 | 18 | export default TabPanel; 19 | -------------------------------------------------------------------------------- /inst/editor/src/components/Tabs/Tabset/useActiveTab.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function useActiveTab(numTabs: number, initalSelection: number = 0) { 4 | const [activeTab, setActiveTab] = React.useState(initalSelection); 5 | 6 | React.useEffect(() => { 7 | if (numTabs <= activeTab) { 8 | // If we have a selected tab that is no longer present, delete it 9 | setActiveTab(numTabs - 1); 10 | } 11 | }, [activeTab, numTabs]); 12 | const setActiveTabVerified = (tabIndex: number) => { 13 | // if (numTabs <= tabIndex) { 14 | // throw new Error( 15 | // `Can't select tab that doesn't exist (${tabIndex}). Only ${numTabs} exist.` 16 | // ); 17 | // } 18 | setActiveTab(tabIndex); 19 | }; 20 | 21 | return { activeTab, setActiveTab: setActiveTabVerified }; 22 | } 23 | -------------------------------------------------------------------------------- /inst/editor/src/components/TemplatePreviews/useRequestTemplate.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import type { TemplateSelection } from "communication-types"; 4 | 5 | import { templateToAppContents } from "../../assets/app-templates/app_templates"; 6 | import { useBackendConnection } from "../../backendCommunication/useBackendMessageCallbacks"; 7 | import { useLanguageMode } from "../../state/languageMode"; 8 | 9 | export function useRequestTemplate() { 10 | const { sendMsg } = useBackendConnection(); 11 | const language = useLanguageMode(); 12 | 13 | const requestTemplate = React.useCallback( 14 | (template: TemplateSelection) => { 15 | sendMsg({ 16 | path: "UPDATED-APP", 17 | payload: templateToAppContents(template, language), 18 | }); 19 | }, 20 | [sendMsg, language] 21 | ); 22 | 23 | return requestTemplate; 24 | } 25 | -------------------------------------------------------------------------------- /inst/editor/src/components/UndoRedoButtons/UndoRedoButtons.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | position: relative; 4 | } 5 | 6 | .container > button > svg { 7 | color: var(--icon-color, silver); 8 | } 9 | 10 | .container > button { 11 | height: var(--header-height, 100%); 12 | padding: 0px; 13 | position: relative; 14 | font-size: 2rem; 15 | } 16 | 17 | .container > button:disabled { 18 | color: var(--disabled-color); 19 | opacity: 0.2; 20 | } 21 | -------------------------------------------------------------------------------- /inst/editor/src/devModeApp.tsx: -------------------------------------------------------------------------------- 1 | // import { pythonSidebarAndTabs as devModeTree } from "ui-node-definitions/src/sample_ui_trees/pythonSidebarAndTabs"; 2 | 3 | import type { LanguageMode } from "communication-types/src/AppInfo"; 4 | 5 | // import { basicNavbarPage as devModeTree } from "./ui-node-definitions/sample_ui_trees/basicNavbarPage"; 6 | // import { bslibCards as devModeTree } from "./state/sample_ui_trees/bslibCards"; 7 | // import { errorTestingTree as devModeTree } from "./state/sample_ui_trees/errorTesting"; 8 | export const devModeLanguage: LanguageMode = "R"; 9 | 10 | export const devModeApp = "TEMPLATE_CHOOSER"; 11 | -------------------------------------------------------------------------------- /inst/editor/src/env_variables.ts: -------------------------------------------------------------------------------- 1 | // Put here so we're not tied to a given build tool's decision on where to place 2 | // env variables. 3 | // @ts-ignore 4 | export let DEV_MODE = import.meta.env.MODE === "development"; 5 | -------------------------------------------------------------------------------- /inst/editor/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import "vite/client"; 3 | declare module "*.module.css"; 4 | declare module "*.png"; 5 | -------------------------------------------------------------------------------- /inst/editor/src/main.ts: -------------------------------------------------------------------------------- 1 | export { runSUE } from "./runSUE"; 2 | export { SUE } from "./SUE"; 3 | export { staticDispatchFromTree } from "./backendCommunication/staticBackend"; 4 | -------------------------------------------------------------------------------- /inst/editor/src/parsing/ParsedAppInfo.ts: -------------------------------------------------------------------------------- 1 | import type { ParserNode } from "treesitter-parsers"; 2 | 3 | import type { IdToNodeMap } from "./nodesToLocations"; 4 | 5 | /** 6 | * The result of parsing an app script with tree-sitter. Contains the root node 7 | * of the tree, the node representing the ui, and the node representing the 8 | * server. Is language agnostic. 9 | */ 10 | export type ParsedAppInfo = { 11 | /** 12 | * The root node of the tree-sitter tree 13 | */ 14 | root_node: ParserNode; 15 | /** 16 | * The node representing the ui of the app script 17 | */ 18 | ui_node: ParserNode; 19 | /** 20 | * The node representing the server of the app script 21 | */ 22 | server_node: ParserNode; 23 | }; 24 | 25 | export type ParsedAppServerNodes = { 26 | inputNodes: IdToNodeMap; 27 | outputNodes: IdToNodeMap; 28 | serverNode: ParserNode; 29 | }; 30 | -------------------------------------------------------------------------------- /inst/editor/src/parsing/Primatives.ts: -------------------------------------------------------------------------------- 1 | export type Primatives = string | number | boolean; 2 | -------------------------------------------------------------------------------- /inst/editor/src/parsing/nodesToLocations.ts: -------------------------------------------------------------------------------- 1 | import type { ServerPositions } from "communication-types/src/MessageToBackend"; 2 | import type { ParserNode } from "treesitter-parsers"; 3 | import { getNodePosition } from "treesitter-parsers"; 4 | 5 | export type IdToNodeMap = Map; 6 | 7 | /** 8 | * Convert an array of parser nodes to a list of positions 9 | * @param nodes Array of parser nodes 10 | * @returns Array of positions 11 | */ 12 | export function nodesToLocations(nodes: ParserNode[]): ServerPositions { 13 | return nodes.map(getNodePosition); 14 | } 15 | -------------------------------------------------------------------------------- /inst/editor/src/python-parsing/NodeTypes/NumberNode.ts: -------------------------------------------------------------------------------- 1 | import type { Brand } from "util-functions/src/TypescriptUtils"; 2 | import type Parser from "web-tree-sitter"; 3 | 4 | type TSNumberNode = Brand; 5 | 6 | export function isNumberNode(node: Parser.SyntaxNode): node is TSNumberNode { 7 | return node.type === "integer" || node.type === "float"; 8 | } 9 | 10 | /** 11 | * Get the contents of a string node without the quotes 12 | * @param node String node to extract the content from 13 | * @returns The text of the string node with the quotes removed 14 | */ 15 | export function extractNumberContent(node: TSNumberNode): number { 16 | // Parse text as number or error if parsing fails 17 | const number = Number(node.text); 18 | if (isNaN(number)) { 19 | throw new Error(`Failed to parse number: ${node.text}`); 20 | } 21 | return number; 22 | } 23 | -------------------------------------------------------------------------------- /inst/editor/src/python-parsing/NodeTypes/StringNode.ts: -------------------------------------------------------------------------------- 1 | import type { Brand } from "util-functions/src/TypescriptUtils"; 2 | import type Parser from "web-tree-sitter"; 3 | 4 | type TSStringNode = Brand; 5 | 6 | export function isStringNode(node: Parser.SyntaxNode): node is TSStringNode { 7 | return node.type === "string"; 8 | } 9 | /** 10 | * Get the contents of a string node without the quotes 11 | * @param node String node to extract the content from 12 | * @returns The text of the string node with the quotes removed 13 | */ 14 | export function extractStringContent(node: TSStringNode): string { 15 | return node.text.slice(1, -1); 16 | } 17 | -------------------------------------------------------------------------------- /inst/editor/src/python-parsing/assets/tree-sitter-python.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/python-parsing/assets/tree-sitter-python.wasm -------------------------------------------------------------------------------- /inst/editor/src/python-parsing/assets/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/editor/src/python-parsing/assets/tree-sitter.wasm -------------------------------------------------------------------------------- /inst/editor/src/python-parsing/generate_output_binding.test.ts: -------------------------------------------------------------------------------- 1 | import { generatePythonOutputBinding } from "./generate_output_binding"; 2 | 3 | test("Can generate output bindings from simple text", () => { 4 | const expected_output = `@output 5 | @render.plot(alt="A plot") 6 | def MyPlot(): 7 | # Not yet implemented 8 | # With a new line`; 9 | expect( 10 | generatePythonOutputBinding("MyPlot", { 11 | fn_name: `@render.plot(alt="A plot")`, 12 | fn_body: `# Not yet implemented\n# With a new line`, 13 | }) 14 | ).toBe(expected_output); 15 | }); 16 | -------------------------------------------------------------------------------- /inst/editor/src/python-parsing/generate_output_binding.ts: -------------------------------------------------------------------------------- 1 | import { collapseText, indent_text_block } from "util-functions/src/strings"; 2 | 3 | import type { OutputBindingScaffold } from "../ui-node-definitions/nodeInfoFactory"; 4 | 5 | export function generatePythonOutputBinding( 6 | id: string, 7 | { fn_name, fn_body }: OutputBindingScaffold 8 | ): string { 9 | return collapseText( 10 | `@output`, 11 | `${fn_name}`, 12 | `def ${id}():`, 13 | indent_text_block(fn_body, 4, true) 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /inst/editor/src/python-parsing/get_imported_pkgs.ts: -------------------------------------------------------------------------------- 1 | import type { ParserTree } from "treesitter-parsers"; 2 | 3 | /** 4 | * Get names of all packages that an app star imports 5 | * @param app_tree Result of running `parse` on the whole app script 6 | * @returns An array of package names of packages that were imported with a star 7 | * import (aka they can be used without a prefix). 8 | */ 9 | export function getImportedPkgs(app_tree: ParserTree): string[] { 10 | const import_nodes = app_tree.rootNode.descendantsOfType( 11 | "import_from_statement" 12 | ); 13 | 14 | const star_imports = import_nodes.filter( 15 | (node) => node.namedChild(1)?.text === "*" 16 | ); 17 | 18 | let imported_pkg_names: string[] = []; 19 | 20 | star_imports.forEach((node) => { 21 | const pkg_name = node.namedChild(0)?.text; 22 | 23 | if (pkg_name) { 24 | imported_pkg_names.push(pkg_name); 25 | } 26 | }); 27 | 28 | return imported_pkg_names; 29 | } 30 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/NodeTypes/CallNode.ts: -------------------------------------------------------------------------------- 1 | import type { ParserNode } from "treesitter-parsers"; 2 | import type { Brand } from "util-functions/src/TypescriptUtils"; 3 | 4 | type TSCallNode = Brand; 5 | 6 | export function is_call_node(node: ParserNode): node is TSCallNode { 7 | return ( 8 | node.type === "call" && 9 | Boolean(node.namedChild(0)) && 10 | Boolean(node.namedChild(1)) 11 | ); 12 | } 13 | 14 | /** 15 | * Get the contents of a string node without the quotes 16 | * @param node String node to extract the content from 17 | * @returns The text of the string node with the quotes removed 18 | */ 19 | export function extract_call_content(node: TSCallNode) { 20 | // We already validated above, so we can be dangerous with the ! here 21 | return { 22 | fn_name: node.namedChild(0)!.text, 23 | fn_args: node.namedChild(1)!.namedChildren, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/NodeTypes/StringNode.ts: -------------------------------------------------------------------------------- 1 | import type { Brand } from "util-functions/src/TypescriptUtils"; 2 | import type Parser from "web-tree-sitter"; 3 | 4 | type TSStringNode = Brand; 5 | 6 | export function is_string_node(node: Parser.SyntaxNode): node is TSStringNode { 7 | return node.type === "string"; 8 | } 9 | /** 10 | * Get the contents of a string node without the quotes 11 | * @param node String node to extract the content from 12 | * @returns The text of the string node with the quotes removed 13 | */ 14 | export function extract_string_content(node: TSStringNode): string { 15 | return node.text.slice(1, -1); 16 | } 17 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/generate_output_binding.test.ts: -------------------------------------------------------------------------------- 1 | import { generate_r_output_binding } from "./generate_output_binding"; 2 | 3 | test("Can generate output bindings from simple text", () => { 4 | const expected_output = `output$MyText <- renderText({ 5 | "Hello, World" 6 | })`; 7 | 8 | expect( 9 | generate_r_output_binding("MyText", { 10 | fn_name: "renderText", 11 | fn_body: `"Hello, World"`, 12 | }) 13 | ).toBe(expected_output); 14 | }); 15 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/generate_output_binding.ts: -------------------------------------------------------------------------------- 1 | import { collapseText, indent_text_block } from "util-functions/src/strings"; 2 | 3 | import type { OutputBindingScaffold } from "../ui-node-definitions/nodeInfoFactory"; 4 | 5 | export function generate_r_output_binding( 6 | id: string, 7 | { fn_name, fn_body }: OutputBindingScaffold 8 | ): string { 9 | const body = collapseText( 10 | `${fn_name}({`, 11 | `${indent_text_block(fn_body, 2, true)}`, 12 | `})` 13 | ); 14 | 15 | return `output$${id} <- ${body}`; 16 | } 17 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/get_name_of_accessed_property.ts: -------------------------------------------------------------------------------- 1 | import type { ParserNode } from "treesitter-parsers"; 2 | 3 | /** 4 | * Check if a node represents an access to a property on an R object using the 5 | * `obj$prop` syntax and returns the name of the property if the `obj` name 6 | * matches what was requested 7 | * @param node Node to check 8 | * @param obj_name Name of the object to check for access of 9 | * @returns Name of the accessed property if the node is an access to the 10 | * requested object, otherwise null 11 | */ 12 | export function getNameOfAccessedProperty( 13 | node: ParserNode, 14 | obj_name: string 15 | ): string | null { 16 | if (node.type !== "dollar" || node.firstNamedChild?.text !== obj_name) { 17 | return null; 18 | } 19 | 20 | const output_name = node.namedChild(1)?.text ?? null; 21 | 22 | return output_name; 23 | } 24 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/get_r_info_if_known.ts: -------------------------------------------------------------------------------- 1 | import type { ParserNode } from "treesitter-parsers"; 2 | import { extract_call_content, is_call_node } from "treesitter-parsers"; 3 | 4 | import { rFnNameToNodeInfo } from "../ui-node-definitions/uiNodeTypes"; 5 | 6 | /** 7 | * Checks if node is known to the ui editor and returns info if it is. 8 | * @param node Node from R tree-sitter tree that may be a known ui node 9 | * @returns If the node is a call to a known ui node, return the info about that 10 | * node. Otherwise return null. 11 | */ 12 | 13 | export function getRInfoIfKnown(node: ParserNode) { 14 | if (!is_call_node(node)) { 15 | return null; 16 | } 17 | const { fn_name, fn_args } = extract_call_content(node); 18 | 19 | const info = rFnNameToNodeInfo(fn_name); 20 | 21 | if (!info) { 22 | return null; 23 | } 24 | 25 | return { 26 | info, 27 | fn_name, 28 | fn_args, 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/get_r_packages_in_script.ts: -------------------------------------------------------------------------------- 1 | import type { ParserNode } from "treesitter-parsers"; 2 | 3 | type PkgInfo = { 4 | names: string[]; 5 | lines: string[]; 6 | }; 7 | /** 8 | * Get the names of all the libraries imported in an R script node 9 | * @param root_node Node for a parsed R script 10 | * @returns Array of library names 11 | */ 12 | export function getRPackagesInScript(root_node: ParserNode): PkgInfo { 13 | return root_node 14 | .descendantsOfType("call") 15 | .filter((node) => node.firstNamedChild?.text === "library") 16 | .reduce( 17 | (acc, node) => { 18 | const name = node.lastNamedChild?.text; 19 | 20 | if (name) { 21 | acc.names.push(name); 22 | acc.lines.push(node.text); 23 | } 24 | 25 | return acc; 26 | }, 27 | { names: [], lines: [] } 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/parseRScript.ts: -------------------------------------------------------------------------------- 1 | import type { TSParser } from "treesitter-parsers"; 2 | 3 | export function parseRScript(parser: TSParser, script_text: string) { 4 | // Parse the current script 5 | return parser.parse(script_text); 6 | } 7 | -------------------------------------------------------------------------------- /inst/editor/src/r-parsing/scratch.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { setup_r_parser } from "treesitter-parsers"; 4 | 5 | import { generateRAppScriptTemplate } from "./generate_app_script_template"; 6 | import { parseRScript } from "./parseRScript"; 7 | 8 | const my_parser = await setup_r_parser(); 9 | 10 | const app_script = ` 11 | library(shiny) 12 | library(bslib) 13 | library(DT) 14 | 15 | ui <- page_navbar( 16 | title = "Hello Shiny!", 17 | nav( 18 | "Hi", 19 | div("Hello World") 20 | ) 21 | ) 22 | 23 | server <- function(input, output) { 24 | output$hello <- renderText({ 25 | "Hello World" 26 | }) 27 | } 28 | 29 | shinyApp(ui, server) 30 | `; 31 | 32 | const script_node = parseRScript(my_parser, app_script).rootNode; 33 | 34 | const generated_template = generateRAppScriptTemplate(script_node).code; 35 | 36 | console.log(script_node); 37 | -------------------------------------------------------------------------------- /inst/editor/src/runSUE.tsx: -------------------------------------------------------------------------------- 1 | import type { BackendConnection } from "communication-types"; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import { SUE } from "./SUE"; 5 | 6 | export function runSUE({ 7 | container, 8 | showMessages, 9 | backendDispatch, 10 | }: { 11 | container: HTMLElement | null; 12 | backendDispatch: BackendConnection; 13 | showMessages: boolean; 14 | }) { 15 | const root = createRoot(container!); // createRoot(container!) if you use TypeScript 16 | root.render( 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /inst/editor/src/scratch.ts: -------------------------------------------------------------------------------- 1 | // Used to test out ideas 2 | export {}; 3 | -------------------------------------------------------------------------------- /inst/editor/src/setupTests.js: -------------------------------------------------------------------------------- 1 | import matchers from "@testing-library/jest-dom/matchers"; 2 | import { expect } from "vitest"; 3 | 4 | expect.extend(matchers); 5 | -------------------------------------------------------------------------------- /inst/editor/src/state/ReduxProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Provider } from "react-redux"; 4 | 5 | import { store } from "./store"; 6 | 7 | function ReduxProvider({ children }: { children: React.ReactNode }) { 8 | return {children}; 9 | } 10 | 11 | export default ReduxProvider; 12 | -------------------------------------------------------------------------------- /inst/editor/src/state/connectedToServer.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import type { PayloadAction } from "@reduxjs/toolkit"; 4 | import { createSlice } from "@reduxjs/toolkit"; 5 | import { useDispatch } from "react-redux"; 6 | 7 | export const connectedToServerSlice = createSlice({ 8 | name: "connectedToServer", 9 | initialState: true as boolean, 10 | reducers: { 11 | DISCONNECTED_FROM_SERVER: (_prev, action: PayloadAction) => false, 12 | }, 13 | }); 14 | 15 | // Action creators are generated for each case reducer function 16 | const { DISCONNECTED_FROM_SERVER } = connectedToServerSlice.actions; 17 | 18 | export const useSetDisconnectedFromServer = () => { 19 | const dispatch = useDispatch(); 20 | 21 | const set_disconected = React.useCallback(() => { 22 | dispatch(DISCONNECTED_FROM_SERVER()); 23 | }, [dispatch]); 24 | return set_disconected; 25 | }; 26 | 27 | export default connectedToServerSlice.reducer; 28 | -------------------------------------------------------------------------------- /inst/editor/src/state/languageMode.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | 3 | import type { RootState } from "./store"; 4 | 5 | export const useLanguageMode = () => { 6 | const app_info = useSelector((state: RootState) => state.app_info); 7 | 8 | if (app_info.mode === "MAIN") { 9 | return app_info.language; 10 | } 11 | 12 | return "R"; 13 | }; 14 | -------------------------------------------------------------------------------- /inst/editor/src/state/metaData.ts: -------------------------------------------------------------------------------- 1 | import type { PayloadAction } from "@reduxjs/toolkit"; 2 | import { createSlice } from "@reduxjs/toolkit"; 3 | import type { MessageToClientByPath } from "communication-types"; 4 | import { useSelector } from "react-redux"; 5 | 6 | import type { RootState } from "./store"; 7 | 8 | export const metaDataSlice = createSlice({ 9 | name: "metaData", 10 | initialState: null as MessageToClientByPath["CHECKIN"] | null, 11 | reducers: { 12 | SET_META_DATA: ( 13 | state, 14 | { payload: meta_data }: PayloadAction 15 | ) => { 16 | return { ...state, ...meta_data }; 17 | }, 18 | }, 19 | }); 20 | 21 | // Action creators are generated for each case reducer function 22 | export const { SET_META_DATA } = metaDataSlice.actions; 23 | 24 | export function useMetaData() { 25 | return useSelector((state: RootState) => state.meta_data); 26 | } 27 | 28 | export default metaDataSlice.reducer; 29 | -------------------------------------------------------------------------------- /inst/editor/src/state/middleware/resetSelectionInTemplateChooser.ts: -------------------------------------------------------------------------------- 1 | import { createListenerMiddleware } from "@reduxjs/toolkit"; 2 | 3 | import { SET_FULL_STATE } from "../app_info"; 4 | import { SET_SELECTION } from "../selectedPath"; 5 | 6 | // Create the middleware instance and methods 7 | const listenForTemplateChooserMode = createListenerMiddleware(); 8 | 9 | // Add one or more listener entries that look for specific actions. 10 | // They may contain any sync or async logic, similar to thunks. 11 | listenForTemplateChooserMode.startListening({ 12 | actionCreator: SET_FULL_STATE, 13 | effect: async (action, listenerApi) => { 14 | listenerApi.dispatch(SET_SELECTION({ path: [] })); 15 | }, 16 | }); 17 | 18 | export const resetSelectionInTemplateChooser = 19 | listenForTemplateChooserMode.middleware; 20 | -------------------------------------------------------------------------------- /inst/editor/src/state/uiTreesAreSame.tsx: -------------------------------------------------------------------------------- 1 | import compare from "just-compare"; 2 | 3 | import type { ShinyUiNode } from "../ui-node-definitions/ShinyUiNode"; 4 | 5 | /** 6 | * Check if two UI trees are different. Useful to avoid duplicating work/ state 7 | * saves etc. 8 | * @param treeA 9 | * @param treeB 10 | * @returns 11 | */ 12 | export function uiTreesAreSame(treeA: ShinyUiNode, treeB: ShinyUiNode) { 13 | // TODO: Make this more efficient. 14 | return compare(treeA, treeB); 15 | } 16 | -------------------------------------------------------------------------------- /inst/editor/src/state/useDeleteNode.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { useDispatch } from "react-redux"; 4 | 5 | import type { NodePath } from "../ui-node-definitions/NodePath"; 6 | 7 | import { DELETE_NODE } from "./app_info"; 8 | 9 | /** 10 | * Generates a callback for deleting the node pointed to at given path and also 11 | * 12 | * @param pathToNode Path the a node to be deleted. 13 | * @returns callback for deleting the node at `pathToNode` in current ui tree 14 | */ 15 | export function useDeleteNode(pathToNode: NodePath | null) { 16 | const dispatch = useDispatch(); 17 | 18 | const deleteNode = React.useCallback(() => { 19 | if (pathToNode === null) return; 20 | 21 | dispatch(DELETE_NODE({ path: pathToNode })); 22 | }, [dispatch, pathToNode]); 23 | 24 | return deleteNode; 25 | } 26 | -------------------------------------------------------------------------------- /inst/editor/src/state/usePlaceNode.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useDispatch } from "react-redux"; 4 | 5 | import type { PlaceNodeArguments } from "../ui-node-definitions/TreeManipulation/placeNode"; 6 | import type { WrappingNode } from "../ui-node-definitions/TreeManipulation/wrapInNode"; 7 | import { wrapInNode } from "../ui-node-definitions/TreeManipulation/wrapInNode"; 8 | 9 | import { PLACE_NODE } from "./app_info"; 10 | 11 | export function usePlaceNode() { 12 | const dispatch = useDispatch(); 13 | 14 | const place_node = React.useCallback( 15 | ({ 16 | wrappingNode, 17 | node, 18 | ...opts 19 | }: PlaceNodeArguments & { 20 | wrappingNode?: WrappingNode; 21 | }) => { 22 | if (wrappingNode) { 23 | node = wrapInNode({ child: node, wrapper: wrappingNode }); 24 | } 25 | 26 | dispatch(PLACE_NODE({ node, ...opts })); 27 | }, 28 | [dispatch] 29 | ); 30 | 31 | return place_node; 32 | } 33 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/Bslib/card_footer.ts: -------------------------------------------------------------------------------- 1 | import { nodeInfoFactory } from "../nodeInfoFactory"; 2 | 3 | export const card_footer = nodeInfoFactory<{}>()({ 4 | r_info: { 5 | fn_name: "card_footer", 6 | package: "bslib", 7 | }, 8 | id: "card_footer", 9 | title: "Card Footer", 10 | takesChildren: true, 11 | settingsInfo: {}, 12 | category: "Cards", 13 | description: "Header for bslib cards", 14 | }); 15 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/Bslib/card_header.ts: -------------------------------------------------------------------------------- 1 | import { nodeInfoFactory } from "../nodeInfoFactory"; 2 | 3 | export const card_header = nodeInfoFactory<{}>()({ 4 | r_info: { 5 | fn_name: "card_header", 6 | package: "bslib", 7 | }, 8 | id: "card_header", 9 | title: "Card Header", 10 | takesChildren: true, 11 | settingsInfo: {}, 12 | category: "Cards", 13 | description: "Header for bslib cards", 14 | }); 15 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/NodePath.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Path to a given node. Starts at [0] for the root. The first child for 3 | * instance would be then [0,1] 4 | */ 5 | export type PathElement = number | string; 6 | export type NodePath = PathElement[]; 7 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/Shiny/panel_main.ts: -------------------------------------------------------------------------------- 1 | import { nodeInfoFactory } from "../nodeInfoFactory"; 2 | 3 | export const panel_main = nodeInfoFactory<{}>()({ 4 | id: "panel_main", 5 | py_info: { 6 | fn_name: "ui.panel_main", 7 | package: "shiny", 8 | }, 9 | title: "Main content panel", 10 | takesChildren: true, 11 | settingsInfo: {}, 12 | category: "Layout", 13 | description: 14 | "Container for content placed in the `main` area of a sidebar layout", 15 | }); 16 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/aIsParentOfB.test.ts: -------------------------------------------------------------------------------- 1 | import { aIsParentOfB } from "./aIsParentOfB"; 2 | 3 | describe("Can distinguish parent-child relationships properly", () => { 4 | it("Handles simple parent grandparent etc..", () => { 5 | expect(aIsParentOfB([1], [1, 2])).toEqual(true); 6 | expect(aIsParentOfB([1], [1, 2, 3])).toEqual(true); 7 | 8 | expect(aIsParentOfB([1], [0, 2, 3])).toEqual(false); 9 | expect(aIsParentOfB([1, 2], [1])).toEqual(false); 10 | expect(aIsParentOfB([1, 2, 3], [1, 2])).toEqual(false); 11 | }); 12 | 13 | it("Knows siblings are not parents", () => { 14 | expect(aIsParentOfB([1], [1])).toEqual(false); 15 | expect(aIsParentOfB([1, 2], [1, 2])).toEqual(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/aIsParentOfB.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath } from "../NodePath"; 2 | 3 | import { nodeDepth } from "./nodeDepth"; 4 | import { pathsSameAtDepth } from "./pathsSameAtDepth"; 5 | 6 | /** 7 | * Is node A the parent, grandparent, great-grand parent,... of B? 8 | * @param aPath Path to node A 9 | * @param bPath Path to node B 10 | */ 11 | export function aIsParentOfB(aPath: NodePath, bPath: NodePath) { 12 | const aDepth = nodeDepth(aPath); 13 | const bDepth = nodeDepth(bPath); 14 | 15 | // If a is lowed up the tree or equal to b then it can't be descended 16 | if (aDepth >= bDepth) return false; 17 | 18 | return pathsSameAtDepth(aPath, bPath, aDepth); 19 | } 20 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/getNamedPath.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath } from "../NodePath"; 2 | import type { ShinyUiNode } from "../ShinyUiNode"; 3 | import { getUiNodeTitle } from "../uiNodeTypes"; 4 | 5 | import { getNode } from "./getNode"; 6 | 7 | export function getNamedPath(path: NodePath, tree: ShinyUiNode): string[] { 8 | const totalDepth = path.length; 9 | let pathString: string[] = []; 10 | for (let depth = 0; depth <= totalDepth; depth++) { 11 | const nodeAtDepth = getNode(tree, path.slice(0, depth)); 12 | if (nodeAtDepth === undefined) { 13 | // If the selection is not valid (node probably just got moved) then don't 14 | // render breadcrumb 15 | break; 16 | } 17 | 18 | pathString.push(getUiNodeTitle(nodeAtDepth.id)); 19 | } 20 | 21 | return pathString; 22 | } 23 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/nodeDepth.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath } from "../NodePath"; 2 | 3 | export function nodeDepth(path: NodePath): number { 4 | return path.length; 5 | } 6 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/nodesAreSiblings.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | nodesAreSiblings, 3 | nodesShareImmediateParent, 4 | } from "./nodesAreSiblings"; 5 | 6 | describe("Can detect when siblings", () => { 7 | test("A is a sibling of B", () => { 8 | expect(nodesAreSiblings([0, 1, 2], [0, 1, 3])).toEqual(true); 9 | }); 10 | 11 | test("A is not a sibling of B", () => { 12 | expect(nodesAreSiblings([0, 2, 2], [0, 1, 3])).toEqual(false); 13 | expect(nodesAreSiblings([0, 1], [0, 1, 3])).toEqual(false); 14 | }); 15 | 16 | test("A node is not its own sibling", () => { 17 | expect(nodesAreSiblings([0, 1], [0, 1])).toEqual(false); 18 | }); 19 | 20 | test("A node does share its own common parent", () => { 21 | expect(nodesShareImmediateParent([0, 1], [0, 1])).toEqual(true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/nodesShareCommonParent.test.ts: -------------------------------------------------------------------------------- 1 | import { nodesShareCommonParent } from "./nodesShareCommonParent"; 2 | 3 | test("Works with common parent as root", () => { 4 | expect(nodesShareCommonParent([1, 2], [0])).toEqual(true); 5 | expect(nodesShareCommonParent([0], [0, 1])).toEqual(true); 6 | expect(nodesShareCommonParent([], [0, 1])).toEqual(true); 7 | }); 8 | 9 | test("Works with deep nodes", () => { 10 | expect(nodesShareCommonParent([1, 2, 3, 4, 5], [1, 4])).toEqual(true); 11 | expect(nodesShareCommonParent([1, 4], [1, 2, 3, 4, 5])).toEqual(true); 12 | expect(nodesShareCommonParent([1, 2, 3, 4, 5], [2, 4])).toEqual(false); 13 | }); 14 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/nodesShareCommonParent.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath } from "../NodePath"; 2 | 3 | import { pathsSameAtDepth } from "./pathsSameAtDepth"; 4 | 5 | /** 6 | * Is the parent of the shortest path contained in the longer path? 7 | * @param aPath Path to node A 8 | * @param bPath Path to node B 9 | */ 10 | export function nodesShareCommonParent( 11 | aPath: NodePath, 12 | bPath: NodePath 13 | ): boolean { 14 | const compareDepth = Math.min(aPath.length, bPath.length) - 1; 15 | 16 | if (compareDepth <= 0) return true; 17 | return pathsSameAtDepth(aPath, bPath, compareDepth); 18 | } 19 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/pathsSameAtDepth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Is the parent of the shortest path contained in the longer path? 3 | * @param aPath Path to node A 4 | * @param bPath Path to node B 5 | */ 6 | 7 | import { sameArray } from "util-functions/src/equalityCheckers"; 8 | 9 | import type { NodePath } from "../NodePath"; 10 | 11 | export function pathsSameAtDepth( 12 | aPath: NodePath, 13 | bPath: NodePath, 14 | depth: number 15 | ): boolean { 16 | if (depth === 0) return true; 17 | return sameArray(aPath.slice(0, depth), bPath.slice(0, depth)); 18 | } 19 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/samePath.ts: -------------------------------------------------------------------------------- 1 | import { sameArray } from "util-functions/src/equalityCheckers"; 2 | 3 | import type { NodePath } from "../NodePath"; 4 | 5 | /** 6 | * Are two node paths the same? 7 | * @param aPath Path to node A 8 | * @param bPath Path to node B 9 | */ 10 | export function samePath( 11 | aPath: NodePath | undefined | null, 12 | bPath?: NodePath | undefined | null 13 | ): boolean { 14 | if (!aPath || !bPath) return false; 15 | return sameArray(aPath, bPath); 16 | } 17 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/TreeManipulation/wrapInNode.ts: -------------------------------------------------------------------------------- 1 | import type { ShinyUiNode, ShinyUiLeafNode } from "../ShinyUiNode"; 2 | 3 | type Wrapper = Pick; 4 | export type WrappingNode = Wrapper | ((child: ShinyUiNode) => Wrapper | null); 5 | 6 | type ChildToWrapperFunction = (child: ShinyUiNode) => ShinyUiLeafNode | null; 7 | export function wrapInNode({ 8 | child, 9 | wrapper, 10 | }: { 11 | child: ShinyUiNode; 12 | wrapper: ShinyUiLeafNode | ChildToWrapperFunction; 13 | }): ShinyUiNode { 14 | if (typeof wrapper === "function") { 15 | const wrapping_node = wrapper(child); 16 | if (wrapping_node === null) { 17 | return child; 18 | } 19 | 20 | wrapper = wrapping_node; 21 | } 22 | return { 23 | ...wrapper, 24 | children: [child], 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/code_generation/generate_full_app_script.ts: -------------------------------------------------------------------------------- 1 | import type { AppInfo } from "communication-types/src/AppInfo"; 2 | 3 | import { generateUiScript } from "./generate_ui_script"; 4 | 5 | export function generateFullAppScript(info: AppInfo): string { 6 | const { ui_tree } = info; 7 | return generateUiScript({ 8 | ui_tree, 9 | language: info.language, 10 | ...info.app, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/code_generation/get_ordered_positional_args.ts: -------------------------------------------------------------------------------- 1 | import type { DynamicArgumentInfo } from "../buildStaticSettingsInfo"; 2 | 3 | export function getOrderedPositionalArgs( 4 | settingsInfo: DynamicArgumentInfo 5 | ): Set { 6 | let positional_args: [position: number, value: string][] = []; 7 | 8 | for (let [name, info] of Object.entries(settingsInfo)) { 9 | if ( 10 | "py_positional_index" in info && 11 | typeof info.py_positional_index === "number" 12 | ) { 13 | positional_args.push([info.py_positional_index, name]); 14 | } 15 | } 16 | 17 | positional_args.sort(([a], [b]) => a - b); 18 | 19 | return new Set(positional_args.map(([_, name]) => name)); 20 | } 21 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/code_generation/ui_node_to_R_code.ts: -------------------------------------------------------------------------------- 1 | import type { Primatives } from "../../parsing/Primatives"; 2 | 3 | import { isNamedList, printNamedRList } from "./print_named_list"; 4 | import { printPrimative } from "./printPrimative"; 5 | import { NL_INDENT } from "./utils"; 6 | 7 | export function printRArgumentValue(value: unknown): string { 8 | if (Array.isArray(value)) return printRArray(value); 9 | 10 | if (isNamedList(value)) return printNamedRList(value); 11 | 12 | if (typeof value === "boolean") return value ? "TRUE" : "FALSE"; 13 | 14 | return JSON.stringify(value); 15 | } 16 | 17 | function printRArray(vals: Primatives[]): string { 18 | const values = vals.map(printPrimative); 19 | 20 | return `c(${NL_INDENT}${values.join(`,${NL_INDENT}`)}\n)`; 21 | } 22 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/code_generation/ui_node_to_python_code.ts: -------------------------------------------------------------------------------- 1 | import type { Primatives } from "../../parsing/Primatives"; 2 | 3 | import { isNamedList, printNamedPythonList } from "./print_named_list"; 4 | import { printPrimative } from "./printPrimative"; 5 | import { NL_INDENT } from "./utils"; 6 | 7 | export function printPythonArgumentValue(value: unknown): string { 8 | if (Array.isArray(value)) return printPythonArray(value); 9 | 10 | if (isNamedList(value)) return printNamedPythonList(value); 11 | 12 | if (typeof value === "boolean") return value ? "True" : "False"; 13 | 14 | return JSON.stringify(value); 15 | } 16 | 17 | export function printPythonArray(vals: Primatives[]): string { 18 | const values = vals.map(printPrimative); 19 | 20 | return `[${NL_INDENT}${values.join(`,${NL_INDENT}`)}\n]`; 21 | } 22 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/get_ordered_positional_args.ts: -------------------------------------------------------------------------------- 1 | import type { DynamicArgumentInfo } from "./buildStaticSettingsInfo"; 2 | 3 | export function getOrderedPositionalArgs( 4 | settingsInfo: DynamicArgumentInfo 5 | ): Set { 6 | let positional_args: [position: number, value: string][] = []; 7 | 8 | for (let [name, info] of Object.entries(settingsInfo)) { 9 | if ( 10 | "py_positional_index" in info && 11 | typeof info.py_positional_index === "number" 12 | ) { 13 | positional_args.push([info.py_positional_index, name]); 14 | } 15 | } 16 | 17 | positional_args.sort(([a], [b]) => a - b); 18 | 19 | return new Set(positional_args.map(([_, name]) => name)); 20 | } 21 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/gridlayout/GridLayoutArgs.ts: -------------------------------------------------------------------------------- 1 | import type { CSSMeasure } from "../inputFieldTypes"; 2 | 3 | export type GridLayoutArgs = { 4 | layout: string[]; 5 | row_sizes: CSSMeasure[]; 6 | col_sizes: CSSMeasure[]; 7 | gap_size: CSSMeasure; 8 | }; 9 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/gridlayout/gridTemplates/TemplatedGridProps.ts: -------------------------------------------------------------------------------- 1 | import type { CSSMeasure } from "../../inputFieldTypes"; 2 | 3 | export type TemplatedGridProps = { 4 | areas: string[][]; 5 | row_sizes: CSSMeasure[]; 6 | col_sizes: CSSMeasure[]; 7 | gap_size: CSSMeasure; 8 | }; 9 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/gridlayout/gridTemplates/areasOfChildren.ts: -------------------------------------------------------------------------------- 1 | /** Get the grid areas present in the children nodes passed to the Grid_Page() 2 | * component. This assumes that they are stored in the "area" property on the 3 | * namedArgs */ 4 | 5 | import type { ShinyUiParentNode } from "../../ShinyUiNode"; 6 | 7 | export function areasOfChildren(children: ShinyUiParentNode["children"] = []) { 8 | let all_children_areas: string[] = []; 9 | children.forEach((child) => { 10 | if ("area" in child.namedArgs && child.namedArgs.area !== undefined) { 11 | const area = child.namedArgs.area; 12 | all_children_areas.push(area as string); 13 | } 14 | }); 15 | 16 | return all_children_areas; 17 | } 18 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/gridlayout/gridTemplates/findItemLocations.test.ts: -------------------------------------------------------------------------------- 1 | import { findItemLocations, findEmptyCells } from "./findItemLocation"; 2 | import type { TemplatedGridProps } from "./TemplatedGridProps"; 3 | 4 | describe("Finds single item locations", () => { 5 | const areas: TemplatedGridProps["areas"] = [ 6 | [".", "a", "b"], 7 | ["c", "c", "."], 8 | ["d", "e", "."], 9 | ]; 10 | 11 | test("Empty cells", () => { 12 | expect(findEmptyCells(areas)).toStrictEqual([ 13 | { row: 1, col: 1 }, 14 | { row: 2, col: 3 }, 15 | { row: 3, col: 3 }, 16 | ]); 17 | }); 18 | test("Single cell item", () => { 19 | expect(findItemLocations(areas, "a")).toStrictEqual([{ row: 1, col: 2 }]); 20 | }); 21 | test("non-existant items", () => { 22 | expect(findItemLocations(areas, "doesntexist")).toStrictEqual([]); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/gridlayout/gridTemplates/itemBoundsInDir.ts: -------------------------------------------------------------------------------- 1 | import type { TractDirection } from "util-functions/src/matrix-helpers"; 2 | 3 | import type { ItemLocation } from "./types"; 4 | 5 | export function itemBoundsInDir(item: ItemLocation, dir: TractDirection) { 6 | switch (dir) { 7 | case "rows": 8 | return { 9 | itemStart: item.rowStart, 10 | itemEnd: item.rowStart + item.rowSpan - 1, 11 | }; 12 | case "cols": 13 | return { 14 | itemStart: item.colStart, 15 | itemEnd: item.colStart + item.colSpan - 1, 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/gridlayout/gridTemplates/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Positional info of item on grid along with validity 3 | */ 4 | export type ItemLocation = { 5 | rowStart: number; 6 | colStart: number; 7 | rowSpan: number; 8 | colSpan: number; 9 | }; 10 | 11 | export type GridItemExtent = { 12 | rowStart: number; 13 | colStart: number; 14 | rowEnd: number; 15 | colEnd: number; 16 | }; 17 | 18 | export type GridCellLocation = { 19 | row: number; 20 | col: number; 21 | }; 22 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/gridlayout/gridTemplates/utils.ts: -------------------------------------------------------------------------------- 1 | import { fillArr } from "util-functions/src/arrays"; 2 | 3 | import type { TemplatedGridProps } from "./TemplatedGridProps"; 4 | 5 | export function fillInPartialTemplate({ 6 | areas, 7 | row_sizes, 8 | col_sizes, 9 | gap_size, 10 | }: { 11 | areas: string[][]; 12 | row_sizes?: TemplatedGridProps["row_sizes"]; 13 | col_sizes?: TemplatedGridProps["col_sizes"]; 14 | gap_size?: TemplatedGridProps["gap_size"]; 15 | }): TemplatedGridProps { 16 | return { 17 | areas, 18 | row_sizes: row_sizes ?? fillArr("1fr", areas.length), 19 | col_sizes: col_sizes ?? fillArr("1fr", areas[0].length), 20 | gap_size: gap_size ?? "10px", 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/index.ts: -------------------------------------------------------------------------------- 1 | import type { node_info_by_id } from "./uiNodeTypes"; 2 | 3 | export { node_info_by_id } from "./uiNodeTypes"; 4 | export type NodeInfoById = typeof node_info_by_id; 5 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/internal/testing_error_node.ts: -------------------------------------------------------------------------------- 1 | import { nodeInfoFactory } from "../nodeInfoFactory"; 2 | 3 | // Provides a node that throws an error when a button is clicked in editor or 4 | // settings panel (for testing error boundaries) 5 | export const testing_error_node = nodeInfoFactory<{ 6 | error_msg: string; 7 | }>()({ 8 | r_info: { 9 | fn_name: "error_node", 10 | package: "TESTING", 11 | }, 12 | id: "error_node", 13 | title: "Error Throwing Node", 14 | takesChildren: false, 15 | settingsInfo: { 16 | error_msg: { 17 | label: "Message for error", 18 | inputType: "string", 19 | defaultValue: "Uh oh, an error!", 20 | }, 21 | }, 22 | category: "TESTING", 23 | description: 24 | "Node that throws an error when a button is clicked in editor or settings panel", 25 | }); 26 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/make_unknown_ui_function.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a node for an unknown UI function from text. 3 | * @param text The text of the function call 4 | * @param explanation An optional explanation of why the function is unknown 5 | * @returns A node for the unknown UI function 6 | */ 7 | export function makeUnknownUiFunction(text: string, explanation?: string) { 8 | return { 9 | id: "unknownUiFunction", 10 | namedArgs: { 11 | text, 12 | explanation, 13 | }, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/nodePathUtils.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath, PathElement } from "./NodePath"; 2 | 3 | export function makeChildPath( 4 | path: NodePath, 5 | childIndex: PathElement 6 | ): NodePath { 7 | return [...path, childIndex]; 8 | } 9 | 10 | export function pathToString(path: NodePath): string { 11 | return path.join("-"); 12 | } 13 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/sample_ui_trees/errorTesting.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 2 | 3 | import type { KnownShinyUiNode } from "../uiNodeTypes"; 4 | 5 | /** 6 | * An app with a node that spits errors for testing error catching. 7 | */ 8 | export const errorTestingTree = { 9 | id: "grid_page", 10 | namedArgs: { 11 | layout: ["A"], 12 | gap_size: "1rem", 13 | col_sizes: ["1fr"], 14 | row_sizes: ["1fr"], 15 | }, 16 | children: [ 17 | { 18 | id: "grid_card", 19 | namedArgs: { 20 | area: "A", 21 | }, 22 | children: [ 23 | { 24 | id: "card_body", 25 | namedArgs: {}, 26 | children: [ 27 | { 28 | id: "error_node", 29 | namedArgs: { error_msg: "Uh oh" }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | ], 36 | } satisfies KnownShinyUiNode; 37 | -------------------------------------------------------------------------------- /inst/editor/src/ui-node-definitions/sample_ui_trees/minimalPage.tsx: -------------------------------------------------------------------------------- 1 | import type { KnownShinyUiNode } from "../uiNodeTypes"; 2 | 3 | export const minimalPage = { 4 | id: "navbarPage", 5 | namedArgs: { 6 | title: "My Navbar Page", 7 | collapsible: false, 8 | }, 9 | children: [ 10 | { 11 | id: "nav_panel", 12 | namedArgs: { 13 | title: "Plot 1", 14 | }, 15 | children: [ 16 | { 17 | id: "plotOutput", 18 | namedArgs: { 19 | outputId: "MyPlot", 20 | width: "100%", 21 | height: "100%", 22 | }, 23 | }, 24 | ], 25 | }, 26 | ], 27 | } satisfies KnownShinyUiNode; 28 | -------------------------------------------------------------------------------- /inst/editor/src/utils/mergeClasses.test.ts: -------------------------------------------------------------------------------- 1 | import { mergeClasses } from "./mergeClasses"; 2 | 3 | describe("Merges multiple and conditional classes into single string", () => { 4 | test("Simple case", () => { 5 | expect(mergeClasses("foo", "bar")).toBe("foo bar"); 6 | }); 7 | 8 | test("Handles missing elements", () => { 9 | expect(mergeClasses("foo", undefined, "bar", null)).toBe("foo bar"); 10 | }); 11 | 12 | test("Can be used with unpacked arrays", () => { 13 | const extraClasses = ["A", "B"]; 14 | const extraClass = "extra"; 15 | 16 | expect(mergeClasses("foo", "bar", ...extraClasses, extraClass)).toBe( 17 | "foo bar A B extra" 18 | ); 19 | }); 20 | 21 | test("Works with objects for conditional addition of classes", () => { 22 | expect(mergeClasses("foo", { bar: false, baz: true })).toBe("foo baz"); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /inst/editor/src/utils/onMac.tsx: -------------------------------------------------------------------------------- 1 | export function onMac(): boolean { 2 | return /mac/i.test(window.navigator.platform); 3 | } 4 | -------------------------------------------------------------------------------- /inst/editor/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = require("../shared-configs/tailwind.config"); 3 | -------------------------------------------------------------------------------- /inst/editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "shared-configs/tsconfig.json", 3 | "include": ["src", "playwright"], 4 | "compilerOptions": { 5 | "plugins": [{ "name": "typescript-plugin-css-modules" }], 6 | "types": ["vite/client", "jest", "@testing-library/jest-dom"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /inst/r-package-build-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r-package-build-tool", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "install-r-pkg": "R CMD INSTALL --preclean --no-multiarch --with-keep.source ../../", 7 | "test-r-pkg": "R --slave --silent --no-save -e \"devtools::test('../../')\"", 8 | "format": "R --slave --silent --no-save -e \"styler::style_pkg(pkg = '../../')\"" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Run ShinyUiEditor 2 | Description: Start ShinyUiEditor on the currently active app file. If no app present will show template chooser. 3 | Binding: launch_editor_addin 4 | Interactive: true -------------------------------------------------------------------------------- /inst/shared-configs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shared-configs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "publishConfig": { 6 | "access": "public" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /inst/shared-configs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /inst/shared-configs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "target": "ES2020", 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "allowJs": true, 8 | "checkJs": false, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "sourceMap": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "jsx": "react-jsx" 21 | }, 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/build.mjs: -------------------------------------------------------------------------------- 1 | import * as esbuild from "esbuild"; 2 | 3 | await esbuild.build({ 4 | entryPoints: ["./src/index.ts"], 5 | bundle: true, 6 | outdir: "dist/", 7 | loader: { ".wasm": "binary" }, 8 | packages: "external", 9 | format: "esm", 10 | }); 11 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/dist/CallNode.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import type { Brand } from "util-functions/src/TypescriptUtils"; 3 | import type { ParserNode } from "."; 4 | type TSCallNode = Brand; 5 | export declare function is_call_node(node: ParserNode): node is TSCallNode; 6 | /** 7 | * Get the contents of a string node without the quotes 8 | * @param node String node to extract the content from 9 | * @returns The text of the string node with the quotes removed 10 | */ 11 | export declare function extract_call_content(node: TSCallNode): { 12 | fn_name: string; 13 | fn_args: import("web-tree-sitter").SyntaxNode[]; 14 | }; 15 | export {}; 16 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/dist/get_assignment_nodes.d.ts: -------------------------------------------------------------------------------- 1 | import type { ParserTree, ParserNode } from "."; 2 | /** 3 | * Find all assignment nodes in a parsed script 4 | * @param tree Syntax node of parsed script from tree-sitter parser 5 | * @param assignment_type The type of assignment node to search for. Defaults to 6 | * `"left_assignment"` and `"assignment"`. To search for all assignment types in 7 | * both python and R. 8 | * @returns All assignment nodes in the script as a map of variable name to the 9 | * node 10 | */ 11 | export declare function get_assignment_nodes(tree: ParserTree, assignment_type?: string | string[]): Node_Assignment_Map; 12 | /** 13 | * A map keyed by name of all assignments in a given python script pointing to 14 | * the node being assigned 15 | */ 16 | export type Node_Assignment_Map = Map; 17 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/dist/get_ui_assignment.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import type { Node_Assignment_Map } from "."; 3 | /** 4 | * 5 | * @param assignment_map Map of all assignment nodes in the script as given by 6 | * `find_assignment_nodes()` 7 | * @param ui_node_name Name of the variable we're looking for that contains the 8 | * UI definition. Defaults to `app_ui`. 9 | * @returns The node containing the UI definition, `null` if not found 10 | * @throws Error if the UI node is not found 11 | */ 12 | export declare function get_ui_assignment(assignment_map: Node_Assignment_Map, ui_node_name?: string): import("web-tree-sitter").SyntaxNode; 13 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/dist/setup_language_parsers.d.ts: -------------------------------------------------------------------------------- 1 | import Parser from "web-tree-sitter"; 2 | export type ParserInitOptions = { 3 | locateFile?: (scriptName: string, scriptDirectory: string) => string; 4 | }; 5 | /** 6 | * Setup a tree-sitter parser with the Python grammar 7 | * @returns A tree-sitter parser with the Python grammar loaded 8 | * @param opts Options to pass to the parser as emscripten module-object, see 9 | * https://emscripten.org/docs/api_reference/module.html 10 | */ 11 | export declare function setup_python_parser(opts?: ParserInitOptions): Promise; 12 | /** 13 | * Setup a tree-sitter parser with the Python grammar 14 | * @returns A tree-sitter parser with the Python grammar loaded 15 | */ 16 | export declare function setup_r_parser(opts?: ParserInitOptions): Promise; 17 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/dist/setup_python_parser.d.ts: -------------------------------------------------------------------------------- 1 | import Parser from "web-tree-sitter"; 2 | export type ParserInitOptions = { 3 | locateFile?: (scriptName: string, scriptDirectory: string) => string; 4 | }; 5 | /** 6 | * Setup a tree-sitter parser with the Python grammar 7 | * @returns A tree-sitter parser with the Python grammar loaded 8 | * @param opts Options to pass to the parser as emscripten module-object, see 9 | * https://emscripten.org/docs/api_reference/module.html 10 | */ 11 | export declare function setup_python_parser(opts?: ParserInitOptions): Promise; 12 | /** 13 | * Setup a tree-sitter parser with the Python grammar 14 | * @returns A tree-sitter parser with the Python grammar loaded 15 | */ 16 | export declare function setup_r_parser(opts?: ParserInitOptions): Promise; 17 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/dist/setup_r_parser.d.ts: -------------------------------------------------------------------------------- 1 | import Parser from "web-tree-sitter"; 2 | /** 3 | * Setup a tree-sitter parser with the Python grammar 4 | * @returns A tree-sitter parser with the Python grammar loaded 5 | */ 6 | export declare function setup_r_parser(): Promise; 7 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/src/CallNode.ts: -------------------------------------------------------------------------------- 1 | import type { Brand } from "util-functions/src/TypescriptUtils"; 2 | 3 | import type { ParserNode } from "."; 4 | 5 | type TSCallNode = Brand; 6 | 7 | export function is_call_node(node: ParserNode): node is TSCallNode { 8 | return ( 9 | node.type === "call" && 10 | Boolean(node.namedChild(0)) && 11 | Boolean(node.namedChild(1)) 12 | ); 13 | } 14 | 15 | /** 16 | * Get the contents of a string node without the quotes 17 | * @param node String node to extract the content from 18 | * @returns The text of the string node with the quotes removed 19 | */ 20 | export function extract_call_content(node: TSCallNode) { 21 | // We already validated above, so we can be dangerous with the ! here 22 | return { 23 | fn_name: node.namedChild(0)!.text, 24 | fn_args: node.namedChild(1)!.namedChildren, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/src/assets/tree-sitter-python.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/treesitter-parsers/src/assets/tree-sitter-python.wasm -------------------------------------------------------------------------------- /inst/treesitter-parsers/src/assets/tree-sitter-r.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/treesitter-parsers/src/assets/tree-sitter-r.wasm -------------------------------------------------------------------------------- /inst/treesitter-parsers/src/assets/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/treesitter-parsers/src/assets/tree-sitter.wasm -------------------------------------------------------------------------------- /inst/treesitter-parsers/src/get_ui_assignment.ts: -------------------------------------------------------------------------------- 1 | import type { Node_Assignment_Map } from "."; 2 | 3 | /** 4 | * 5 | * @param assignment_map Map of all assignment nodes in the script as given by 6 | * `find_assignment_nodes()` 7 | * @param ui_node_name Name of the variable we're looking for that contains the 8 | * UI definition. Defaults to `app_ui`. 9 | * @returns The node containing the UI definition, `null` if not found 10 | * @throws Error if the UI node is not found 11 | */ 12 | export function get_ui_assignment( 13 | assignment_map: Node_Assignment_Map, 14 | ui_node_name: string = "app_ui" 15 | ) { 16 | const ui_node = assignment_map.get(ui_node_name); 17 | if (ui_node) { 18 | return ui_node; 19 | } 20 | return null; 21 | } 22 | -------------------------------------------------------------------------------- /inst/treesitter-parsers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "shared-configs/tsconfig.json", 3 | "compilerOptions": { 4 | // "target": "ES2020" 5 | // "rootDir": "src", 6 | "esModuleInterop": true 7 | }, 8 | "exclude": ["node_modules"] 9 | 10 | // "include": ["src/"] 11 | } 12 | -------------------------------------------------------------------------------- /inst/util-functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "util-functions", 3 | "main": "./src/index.ts", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "shared-configs": "*", 8 | "typescript": "^5.0.4", 9 | "vitest": "^0.30.0", 10 | "just-clone": "^6.1.1" 11 | }, 12 | "scripts": { 13 | "type-check": "tsc -p ./ --noEmit", 14 | "test": "vitest run", 15 | "test-watch": "vitest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /inst/util-functions/src/convertMapToObject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a `Map` to an object (as long as it's keys are strings) 3 | * @param m The map to convert 4 | * @returns The object with the same key-value pairs as the map 5 | */ 6 | export function convertMapToObject( 7 | m: Map 8 | ): Record { 9 | const obj: Record = {}; 10 | for (const [key, value] of m) { 11 | obj[key] = value; 12 | } 13 | return obj; 14 | } 15 | -------------------------------------------------------------------------------- /inst/util-functions/src/equalityCheckers.ts: -------------------------------------------------------------------------------- 1 | export function sameArray(a: T[], b: T[]): boolean { 2 | // If referential equality is there then no need to go deeper 3 | if (a === b) return true; 4 | 5 | if (a.length !== b.length) return false; 6 | 7 | for (let i = 0; i < a.length; i++) { 8 | if (a[i] !== b[i]) return false; 9 | } 10 | 11 | return true; 12 | } 13 | 14 | type Obj = { [key: string]: any }; 15 | 16 | export function sameObject( 17 | a: Obj, 18 | b: Obj, 19 | ignoredKeys: string[] | string = [] 20 | ) { 21 | // If referential equality is there then no need to go deeper 22 | if (a === b) return true; 23 | 24 | const aKeys = Object.keys(a).filter((key) => !ignoredKeys.includes(key)); 25 | const bKeys = Object.keys(b).filter((key) => !ignoredKeys.includes(key)); 26 | if (!sameArray(aKeys, bKeys)) return false; 27 | 28 | for (let key of aKeys) { 29 | if (a[key] !== b[key]) return false; 30 | } 31 | 32 | return true; 33 | } 34 | -------------------------------------------------------------------------------- /inst/util-functions/src/index.ts: -------------------------------------------------------------------------------- 1 | export * as arrays from "./arrays"; 2 | export * as strings from "./strings"; 3 | -------------------------------------------------------------------------------- /inst/util-functions/src/is_object.ts: -------------------------------------------------------------------------------- 1 | export function is_object(x: unknown): x is object { 2 | return typeof x === "object" && x !== null; 3 | } 4 | -------------------------------------------------------------------------------- /inst/util-functions/src/numbers.ts: -------------------------------------------------------------------------------- 1 | // Roundabout way to avoid ugly machine-epsilon floating point numbers like 2 | // 1.4999999999991 3 | export const cleanNumber = (num: number) => Number(num.toFixed(4)); 4 | -------------------------------------------------------------------------------- /inst/util-functions/src/sum_booleans.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Count number of `true` booleans for a given list of them 3 | * @param all_bools Boolean variables to be counted 4 | * @returns number of booleans with value of `true` in arguments 5 | */ 6 | export function sum_booleans(...all_bools: boolean[]): number { 7 | let i = 0; 8 | 9 | for (const bool of all_bools) { 10 | if (bool) { 11 | i += 1; 12 | } 13 | } 14 | 15 | return i; 16 | } 17 | -------------------------------------------------------------------------------- /inst/util-functions/src/within.ts: -------------------------------------------------------------------------------- 1 | export function within(x: number, a: number, b: number) { 2 | const low = a < b ? a : b; 3 | const high = a < b ? b : a; 4 | 5 | return x >= low && x <= high; 6 | } 7 | -------------------------------------------------------------------------------- /inst/util-functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "shared-configs/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /inst/util-functions/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | const setup = () => { 4 | return defineConfig({ 5 | base: "./", 6 | test: { 7 | include: [`src/**/*.test.{ts,tsx}`], 8 | globals: true, 9 | environment: "jsdom", 10 | }, 11 | }); 12 | }; 13 | export default setup; 14 | -------------------------------------------------------------------------------- /inst/vscode-extension-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-extension-client", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "dependencies": { 7 | "editor": "*" 8 | }, 9 | "scripts": { 10 | "build": "vite build", 11 | "dev": "vite build --mode dev", 12 | "type-check": "tsc -p ./ --noEmit" 13 | }, 14 | "devDependencies": { 15 | "@rollup/plugin-inject": "^5.0.3", 16 | "@types/vscode": "^1.65.0", 17 | "@types/vscode-webview": "^1.57.0", 18 | "autoprefixer": "^10.4.15", 19 | "rollup-plugin-node-builtins": "^2.1.2", 20 | "tailwindcss": "^3.3.3", 21 | "shared-configs": "*", 22 | "tsx": "^3.12.1", 23 | "typescript": "^5.0.4", 24 | "vite": "^4.2.1", 25 | "vite-tsconfig-paths": "^4.0.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /inst/vscode-extension-client/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("../shared-configs/postcss.config"); 2 | -------------------------------------------------------------------------------- /inst/vscode-extension-client/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.module.css"; 2 | declare module "*.png"; 3 | -------------------------------------------------------------------------------- /inst/vscode-extension-client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import twConfig from "../shared-configs/tailwind.config"; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | ...twConfig, 6 | content: ["../editor/index.html", "../editor/src/**/*.{ts,tsx}"], 7 | }; 8 | -------------------------------------------------------------------------------- /inst/vscode-extension-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "shared-configs/tsconfig.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "types": ["vite/client"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /inst/vscode-extension/.eslintignore: -------------------------------------------------------------------------------- 1 | media/*.js -------------------------------------------------------------------------------- /inst/vscode-extension/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } -------------------------------------------------------------------------------- /inst/vscode-extension/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false 3 | } -------------------------------------------------------------------------------- /inst/vscode-extension/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /inst/vscode-extension/.vscodeignore: -------------------------------------------------------------------------------- 1 | **/*.ts 2 | **/*.R 3 | **/*.r 4 | **/*.DS_Store 5 | **/tsconfig.json 6 | !file.ts 7 | 8 | # Avoid stuff hanging around in inst/ folder 9 | ../**/ 10 | 11 | # Avoid root files in repo 12 | ../../* 13 | 14 | # Avoid subfolders of repo root 15 | ../../*/ 16 | 17 | **/tree-sitter-r/ -------------------------------------------------------------------------------- /inst/vscode-extension/assets/extension-with-code-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/assets/extension-with-code-open.png -------------------------------------------------------------------------------- /inst/vscode-extension/assets/launch-editor-cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/assets/launch-editor-cmd.png -------------------------------------------------------------------------------- /inst/vscode-extension/assets/run-sue-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/assets/run-sue-btn.png -------------------------------------------------------------------------------- /inst/vscode-extension/assets/shinyuieditor-hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/assets/shinyuieditor-hex.png -------------------------------------------------------------------------------- /inst/vscode-extension/build.mts: -------------------------------------------------------------------------------- 1 | import * as esbuild from "esbuild"; 2 | import { copyFileSync } from "fs"; 3 | 4 | const args = process.argv.slice(2); 5 | const isDev = args.includes("--dev"); 6 | 7 | if (isDev) { 8 | console.log("Building with dev mode"); 9 | } 10 | 11 | await esbuild.build({ 12 | entryPoints: ["./src/extension.ts"], 13 | bundle: true, 14 | sourcemap: isDev, 15 | minify: !isDev, 16 | platform: "node", 17 | external: ["vscode"], 18 | target: ["node16"], 19 | outdir: "build/", 20 | format: "cjs", 21 | }); 22 | 23 | // Copy over wasm binary for tree sitter parser to the build folder 24 | copyFileSync( 25 | "../treesitter-parsers/src/assets/tree-sitter.wasm", 26 | "./build/tree-sitter.wasm" 27 | ); 28 | -------------------------------------------------------------------------------- /inst/vscode-extension/build/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/build/tree-sitter.wasm -------------------------------------------------------------------------------- /inst/vscode-extension/documentation/extension-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/documentation/extension-running.png -------------------------------------------------------------------------------- /inst/vscode-extension/exampleFiles/empty_app.r: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/exampleFiles/empty_app.r -------------------------------------------------------------------------------- /inst/vscode-extension/exampleFiles/python/app.py: -------------------------------------------------------------------------------- 1 | from shiny import * 2 | 3 | app_ui = ui.page_navbar( 4 | ui.nav( 5 | "Settings", 6 | ui.input_slider( 7 | id="inputId", label="Slider Input", min=0, max=10, value=5, width="100%" 8 | ), 9 | ), 10 | ui.nav("Plot 1", ui.output_plot(id="MyPlot", width="100%", height="100%")), 11 | title="My Navbar Page", 12 | collapsible=False, 13 | ) 14 | 15 | 16 | def server(input, output, session): 17 | pass 18 | 19 | 20 | app = App(app_ui, server) 21 | -------------------------------------------------------------------------------- /inst/vscode-extension/exampleFiles/testing/assets/a_text_file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/exampleFiles/testing/assets/a_text_file.txt -------------------------------------------------------------------------------- /inst/vscode-extension/exampleFiles/testing/myOtherScript.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/exampleFiles/testing/myOtherScript.R -------------------------------------------------------------------------------- /inst/vscode-extension/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /inst/vscode-extension/media/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/media/tree-sitter.wasm -------------------------------------------------------------------------------- /inst/vscode-extension/shinyuieditor-0.4.3.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/shinyuieditor-0.4.3.vsix -------------------------------------------------------------------------------- /inst/vscode-extension/shinyuieditor-0.5.0.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/shinyuieditor-0.5.0.vsix -------------------------------------------------------------------------------- /inst/vscode-extension/shinyuieditor-0.5.1.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/vscode-extension/shinyuieditor-0.5.1.vsix -------------------------------------------------------------------------------- /inst/vscode-extension/src/App_Parser.ts: -------------------------------------------------------------------------------- 1 | import type { AppInfo } from "communication-types/src/AppInfo"; 2 | import type { ServerPositions } from "communication-types/src/MessageToBackend"; 3 | 4 | import type { CommandOutputGeneric } from "./R-Utils/runRCommand"; 5 | 6 | export type ServerInfo = { 7 | server_pos: { 8 | server_fn: ServerPositions[number]; 9 | indent: number; 10 | }; 11 | get_output_position: (outputId: string) => ServerPositions; 12 | get_input_positions: (inputId: string) => ServerPositions; 13 | }; 14 | 15 | export type InfoGetResults = 16 | | { 17 | ui: AppInfo; 18 | server: ServerInfo; 19 | } 20 | | "EMPTY"; 21 | 22 | export type AppParser = { 23 | getInfo: () => Promise>; 24 | check_if_pkgs_installed: ( 25 | pkgs: string 26 | ) => Promise<{ success: true } | { success: false; msg: string }>; 27 | }; 28 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/Python-Utils/get_path_to_python.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | /** 4 | * Use the Python extension to get the path to the current Python interpreter 5 | * @returns The path to the current Python interpreter 6 | * @throws If the Python extension is not installed 7 | */ 8 | export async function getPathToPython(): Promise { 9 | // Get the Python extension api 10 | const pythonAPI = vscode.extensions.getExtension("ms-python.python"); 11 | 12 | if (!pythonAPI) { 13 | throw new Error("Python extension needed for previewing Python apps"); 14 | } 15 | 16 | const execution_details = 17 | await pythonAPI.exports.environment.getExecutionDetails( 18 | vscode.window.activeTextEditor?.document.uri 19 | ); 20 | 21 | return execution_details.execCommand.join(" "); 22 | } 23 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/Python-Utils/start_python_process.ts: -------------------------------------------------------------------------------- 1 | import type { ChildProcessWithoutNullStreams } from "child_process"; 2 | 3 | import type { StartProcessOptions } from "../startProcess"; 4 | import { startProcess } from "../startProcess"; 5 | 6 | import { getPathToPython } from "./get_path_to_python"; 7 | 8 | export type RProcess = { 9 | proc: ChildProcessWithoutNullStreams; 10 | stop: () => boolean; 11 | getIsRunning: () => boolean; 12 | }; 13 | 14 | export async function startPythonProcess( 15 | commands: string[], 16 | opts: StartProcessOptions = {} 17 | ): Promise { 18 | const pathToPython = await getPathToPython(); 19 | 20 | return startProcess(pathToPython, commands, opts); 21 | } 22 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/R-Utils/getRPathFromConfig.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | export function getRPathFromConfig(): string | undefined { 4 | const platform = 5 | process.platform === "win32" 6 | ? "windows" 7 | : process.platform === "darwin" 8 | ? "mac" 9 | : "linux"; 10 | 11 | const configEntry = `rpath.${platform}`; 12 | return vscode.workspace 13 | .getConfiguration("shinyuieditor") 14 | .get(configEntry); 15 | } 16 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/app-preview/getFreePort.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | export async function getFreePort(): Promise { 4 | return new Promise((res) => { 5 | const srv = net.createServer(); 6 | srv.listen(0, () => { 7 | const serverAddress = srv.address?.(); 8 | if (typeof serverAddress === "string" || serverAddress === null) { 9 | throw new Error("Failed to find a free port..."); 10 | } 11 | 12 | srv.close((err) => res(serverAddress.port)); 13 | }); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/app-preview/get_python_app_startup_info.ts: -------------------------------------------------------------------------------- 1 | import { getPathToPython } from "../Python-Utils/get_path_to_python"; 2 | 3 | import type { AppLocInfo, AppStartupInfo } from "./get_app_startup_info"; 4 | 5 | export async function getPythonAppStartupInfo({ 6 | pathToApp, 7 | port, 8 | host, 9 | }: AppLocInfo): Promise { 10 | const listen_for_ready_regex = new RegExp( 11 | `application startup complete.`, 12 | "i" 13 | ); 14 | 15 | return { 16 | path_to_executable: await getPathToPython(), 17 | startup_cmds: [ 18 | `-m`, 19 | `shiny`, 20 | `run`, 21 | `--port`, 22 | `${port}`, 23 | `--host`, 24 | host, 25 | `--reload`, 26 | pathToApp.replace(/([\\"])/g, "\\$1"), 27 | ], 28 | get_is_ready: (msg: string) => listen_for_ready_regex.test(msg), 29 | }; 30 | } 31 | 32 | // venv/bin/python -m shiny run --port 3333 --host 0.0.0.0 --reload "./scratch/python" 33 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/app-preview/get_r_app_startup_info.ts: -------------------------------------------------------------------------------- 1 | import { collapseText } from "util-functions/src/strings"; 2 | 3 | import { getPathToR } from "../R-Utils/getPathToR"; 4 | 5 | import type { AppLocInfo, AppStartupInfo } from "./get_app_startup_info"; 6 | 7 | export async function getRAppStartupInfo({ 8 | pathToApp, 9 | port, 10 | host, 11 | }: AppLocInfo): Promise { 12 | const listen_for_ready_regex = new RegExp(`listening on .+${port}`, "i"); 13 | const pathToR = await getPathToR(); 14 | return { 15 | path_to_executable: pathToR, 16 | startup_cmds: [ 17 | "--no-save", 18 | "--no-restore", 19 | "--silent", 20 | "-e", 21 | collapseText( 22 | `options(shiny.autoreload = TRUE)`, 23 | `shiny::runApp(appDir = "${pathToApp}", port = ${port}, host = "${host}")` 24 | ), 25 | ], 26 | get_is_ready: (msg: string) => listen_for_ready_regex.test(msg), 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/commands/startEditorOnActiveFile.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { window } from "vscode"; 3 | 4 | export function startEditorOnActiveFile(name: string = "world") { 5 | const activeEditor = window.activeTextEditor; 6 | 7 | if (!activeEditor) { 8 | window.showErrorMessage("No active file open to run ui editor on!"); 9 | return; 10 | } 11 | 12 | vscode.commands.executeCommand( 13 | "vscode.openWith", 14 | activeEditor.document.uri, 15 | "shinyuieditor.appFile" 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/extension-api-utils/clearAppFile.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | /** 4 | * Wipe app file clear 5 | */ 6 | export async function clearAppFile(document: vscode.TextDocument) { 7 | const uri = document.uri; 8 | const edit = new vscode.WorkspaceEdit(); 9 | 10 | const uiRange = document.validateRange( 11 | new vscode.Range(0, 0, Infinity, Infinity) 12 | ); 13 | 14 | edit.replace(uri, uiRange, ""); 15 | 16 | await vscode.workspace.applyEdit(edit); 17 | 18 | // Save so app preview will update 19 | document.save(); 20 | } 21 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { launchEditor } from "./commands/launchEditor"; 4 | import { startEditorOnActiveFile } from "./commands/startEditorOnActiveFile"; 5 | import { ShinyUiEditorProvider } from "./shinyuieditor_extension"; 6 | 7 | export function activate(context: vscode.ExtensionContext) { 8 | // Register our custom editor providers 9 | context.subscriptions.push(ShinyUiEditorProvider.register(context)); 10 | context.subscriptions.push( 11 | vscode.commands.registerCommand( 12 | "shinyuieditor.startEditorOnActiveFile", 13 | startEditorOnActiveFile 14 | ), 15 | vscode.commands.registerCommand("shinyuieditor.launchEditor", launchEditor) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.module.css"; 4 | declare module "*.png"; 5 | -------------------------------------------------------------------------------- /inst/vscode-extension/src/selectServerReferences.ts: -------------------------------------------------------------------------------- 1 | import type { ServerPositions } from "communication-types/src/MessageToBackend"; 2 | import * as vscode from "vscode"; 3 | 4 | export function selectAppLines({ 5 | editor, 6 | selections, 7 | }: { 8 | editor: vscode.TextEditor; 9 | selections: ServerPositions; 10 | }) { 11 | const selection_objs = selections.map((range) => { 12 | const start = new vscode.Position(range.start.row, range.start.column); 13 | const end = new vscode.Position(range.end.row, range.end.column); 14 | return new vscode.Selection(start, end); 15 | }); 16 | 17 | // Set the selection to found outputs 18 | editor.selection = selection_objs[0]; 19 | 20 | // Make sure that the user can actually see those outputs. 21 | editor.revealRange(selection_objs[0]); 22 | } 23 | -------------------------------------------------------------------------------- /inst/vscode-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "shared-configs/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "rootDir": "src", 6 | "outDir": "build" 7 | }, 8 | "include": ["src/"] 9 | } 10 | -------------------------------------------------------------------------------- /inst/website/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | /test-results/ 23 | /playwright-report/ 24 | /playwright/.cache/ 25 | -------------------------------------------------------------------------------- /inst/website/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /inst/website/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /inst/website/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import react from "@astrojs/react"; 3 | import tailwind from "@astrojs/tailwind"; 4 | // import builtins from "rollup-plugin-node-builtins"; 5 | 6 | import mdx from "@astrojs/mdx"; 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | site: "https://rstudio.github.io", 11 | base: "/shinyuieditor", 12 | integrations: [ 13 | react(), 14 | tailwind({ 15 | // ShadCN already does this in the styles/global.css file 16 | applyBaseStyles: false, 17 | }), 18 | mdx(), 19 | ], 20 | redirects: { 21 | // Make sure to keep the baseurl up to date here. In this case 22 | // `shinyuieditor` is the baseurl 23 | "/articles/ui-editor-live-demo": "/shinyuieditor/new-page", 24 | "/news": "/shinyuieditor/change-log", 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /inst/website/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "stone", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/add-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/add-element.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/add-tract.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/add-tract.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/delete-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/delete-an-element.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/delete-tract.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/delete-tract.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/move-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/move-an-element.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/resize-with-drag.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/resize-with-drag.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/resize-with-widget.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/resize-with-widget.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/select-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/select-an-element.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/show-size-widget.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/show-size-widget.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/undo-redo.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/undo-redo.webm -------------------------------------------------------------------------------- /inst/website/public/how-to-videos/update-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/how-to-videos/update-an-element.webm -------------------------------------------------------------------------------- /inst/website/public/layout-editing.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/layout-editing.mp4 -------------------------------------------------------------------------------- /inst/website/public/layout-editing.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/layout-editing.webm -------------------------------------------------------------------------------- /inst/website/public/live-demo/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/public/live-demo/tree-sitter.wasm -------------------------------------------------------------------------------- /inst/website/src/assets/screenshots/launch-editor-cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/src/assets/screenshots/launch-editor-cmd.png -------------------------------------------------------------------------------- /inst/website/src/assets/screenshots/run-sue-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/src/assets/screenshots/run-sue-btn.png -------------------------------------------------------------------------------- /inst/website/src/assets/screenshots/template-chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/src/assets/screenshots/template-chooser.png -------------------------------------------------------------------------------- /inst/website/src/assets/screenshots/unknown-arguments-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/src/assets/screenshots/unknown-arguments-display.png -------------------------------------------------------------------------------- /inst/website/src/assets/shinyuieditor-hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/inst/website/src/assets/shinyuieditor-hex.png -------------------------------------------------------------------------------- /inst/website/src/components/InternalLink.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | href: string; 4 | 5 | } 6 | 7 | const { href } = Astro.props; 8 | 9 | 10 | 11 | import { internalLink } from "@/lib/utils"; 12 | 13 | --- 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /inst/website/src/components/LinkButton.astro: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | const variantClasses = { 4 | "primary": "bg-rstudio-blue/90 hover:bg-rstudio-blue font-semibold text-white", 5 | "secondary": "rounded bg-white px-2 py-1 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" 6 | } 7 | 8 | interface Props { 9 | href: string; 10 | variant?: keyof typeof variantClasses; 11 | } 12 | 13 | const { href, variant = "primary" } = Astro.props; 14 | 15 | --- 16 | 17 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /inst/website/src/components/LogoLink.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | class?: string; 4 | } 5 | 6 | const { class: className } = Astro.props; 7 | 8 | import HexStickerImg from "@/assets/shinyuieditor-hex.png"; 9 | 10 | 11 | import { Image } from "astro:assets"; 12 | 13 | 14 | import {cn} from "@/lib/utils" 15 | 16 | --- 17 | 18 | 19 | 20 | ShinyUiEditor | Posit 21 | 22 | -------------------------------------------------------------------------------- /inst/website/src/components/MarkdownContainer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | import {cn} from "@/lib/utils" 4 | 5 | --- 6 |
10 | 11 |
-------------------------------------------------------------------------------- /inst/website/src/components/VideoEmbed.astro: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | import { internalLink } from '@/lib/utils'; 4 | interface Props { 5 | loc: string; 6 | } 7 | 8 | const { loc } = Astro.props; 9 | --- 10 | 11 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /inst/website/src/components/icons/GettingStartedIcon.astro: -------------------------------------------------------------------------------- 1 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /inst/website/src/components/icons/GithubIcon.astro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /inst/website/src/components/icons/LiveDemoIcon.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inst/website/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module "editor-component-lib"; 5 | -------------------------------------------------------------------------------- /inst/website/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | /** 9 | * Make a routing-safe link robust to changes in the base url 10 | * @param url Base URL for page to be visited. Should not start with a slash 11 | * @returns Link to an internal page in the website. This is useful because the 12 | * base url may change and this way we don't have to update every link to match 13 | */ 14 | export function internalLink(url: string) { 15 | // Remove leading slash if it exists 16 | const cleanUrl = url.startsWith("/") ? url.slice(1) : url; 17 | 18 | return `${import.meta.env.BASE_URL}/${cleanUrl}`; 19 | } 20 | -------------------------------------------------------------------------------- /inst/website/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "@/layouts/Layout.astro"; 3 | 4 | import Header from "@/components/Header.astro"; 5 | import Hero from "@/components/Hero.astro"; 6 | import CardSection from "@/components/CardSection.astro"; 7 | import MarkdownContainer from "@/components/MarkdownContainer.astro"; 8 | 9 | import { Content as LandingProse } from "@/assets/markdown/landing.mdx"; 10 | 11 | 12 | import "@/styles/globals.css"; 13 | --- 14 | 15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /inst/website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "jsx": "react-jsx", 6 | "jsxImportSource": "react", 7 | "paths": { 8 | "@/*": ["src/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /man/check_for_app_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_app_file_type.R 3 | \name{check_for_app_file} 4 | \alias{check_for_app_file} 5 | \title{Get the file type a shiny app directory} 6 | \usage{ 7 | check_for_app_file(app_loc, error_on_missing = FALSE) 8 | } 9 | \arguments{ 10 | \item{app_loc}{Path to a shiny app} 11 | 12 | \item{error_on_missing}{Should the lack of 13 | app ui file trigger an error? If not returns a type of "missing" and no path} 14 | } 15 | \value{ 16 | either \code{TRUE} if it finds an (\code{app.R}) or \code{FALSE} if no app detected 17 | } 18 | \description{ 19 | Also checks for multifile apps and emits a depreciation error 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/insert_server_code.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rstudioapi_utils.R 3 | \name{insert_server_code} 4 | \alias{insert_server_code} 5 | \title{Insert code into the server script at the given location} 6 | \usage{ 7 | insert_server_code(snippet, insert_at, app_loc) 8 | } 9 | \arguments{ 10 | \item{snippet}{Code to be inserted} 11 | 12 | \item{insert_at}{Location to insert the code at} 13 | 14 | \item{app_loc}{Path to directory containing Shiny app to be visually edited 15 | (either containing an \code{app.R} or both a \code{ui.R} and \code{server.R}).} 16 | } 17 | \description{ 18 | Insert code into the server script at the given location 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/select_server_code.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rstudioapi_utils.R 3 | \name{select_server_code} 4 | \alias{select_server_code} 5 | \title{Select the code for the given app location based on client-side locations} 6 | \usage{ 7 | select_server_code(locations, app_loc) 8 | } 9 | \arguments{ 10 | \item{locations}{Locations of the code to be selected in the client} 11 | 12 | \item{app_loc}{Path to directory containing Shiny app to be visually edited 13 | (either containing an \code{app.R} or both a \code{ui.R} and \code{server.R}).} 14 | } 15 | \description{ 16 | Select the code for the given app location based on client-side locations 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/watch_for_app_close.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/watch_for_app_close.R 3 | \name{watch_for_app_close} 4 | \alias{watch_for_app_close} 5 | \title{Create a watcher that checks when an app is as indicated by a websocket 6 | connection being severed} 7 | \usage{ 8 | watch_for_app_close(on_close) 9 | } 10 | \arguments{ 11 | \item{on_close}{A function to call when the app 12 | closes @return A list of functions to call when the app opens or closes} 13 | } 14 | \description{ 15 | Create a watcher that checks when an app is as indicated by a websocket 16 | connection being severed 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "inst/*" 5 | ], 6 | "scripts": { 7 | "build": "turbo run build", 8 | "dev": "turbo run dev", 9 | "prod": "turbo run build", 10 | "test": "turbo run test", 11 | "playwright": "turbo run playwright", 12 | "update-visual-snapshots": "turbo run update-visual-snapshots", 13 | "type-check": "turbo run type-check", 14 | "build-storybook": "turbo run build-storybook", 15 | "watch": "turbo run watch --parallel --continue" 16 | }, 17 | "devDependencies": { 18 | "turbo": "^1.10.14" 19 | }, 20 | "packageManager": "yarn@1.22.19" 21 | } 22 | -------------------------------------------------------------------------------- /scratch/ast_generation_scratch.R: -------------------------------------------------------------------------------- 1 | devtools::load_all(".") 2 | library(lobstr) 3 | library(shiny) 4 | library(bslib) 5 | 6 | rlang::expr( 7 | value_box( 8 | title = "I got", 9 | value = textOutput("my_value"), 10 | # showcase = bs_icon("music-note-beamed") 11 | ) 12 | ) |> 13 | serialize_ast() |> 14 | # tree(index_unnamed = TRUE) 15 | jsonlite::toJSON(auto_unbox = TRUE) 16 | 17 | 18 | # file_lines <- readLines("scratch/app-w-unknown-code/app.R") 19 | # parsed <- parse(text = file_lines, keep.source = TRUE) 20 | # full_ast <- serialize_ast(parsed) 21 | # jsonlite::toJSON(full_ast, auto_unbox = TRUE) 22 | -------------------------------------------------------------------------------- /scratch/broken_ui/ui.R: -------------------------------------------------------------------------------- 1 | my_custom_ui_fn( 2 | app_title = "hi there", 3 | app_contents = div("App body") 4 | ) 5 | -------------------------------------------------------------------------------- /scratch/empty_directory/utility_fns.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/scratch/empty_directory/utility_fns.R -------------------------------------------------------------------------------- /scratch/python/app.py: -------------------------------------------------------------------------------- 1 | from shiny import App, ui, render 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import shiny.experimental as x 5 | from shinywidgets import output_widget, render_widget 6 | 7 | app_ui = ui.page_navbar( 8 | ui.nav( 9 | "It's Alive!", 10 | ui.input_slider( 11 | id="n", label="Slider Input", min=5, max=100, value=25, width="100%" 12 | ), 13 | ), 14 | ui.nav("Plot 1", ui.output_plot(id="MyPlot", width="100%", height="400px")), 15 | title="My cool app", 16 | collapsible=True, 17 | ) 18 | 19 | 20 | def server(input, output, session): 21 | @output 22 | @render.plot(alt="A histogram") 23 | def MyPlot(): 24 | np.random.seed(19680801) 25 | x = 100 + 15 * np.random.randn(437) 26 | plt.hist(x, input.n(), density=True) 27 | 28 | 29 | app = App( 30 | app_ui, 31 | server, 32 | ) 33 | -------------------------------------------------------------------------------- /scratch/simple-multi-file/server.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | 3 | # A comment about the server 4 | server <- function(input, output) { 5 | print("I am a server function") 6 | } 7 | -------------------------------------------------------------------------------- /scratch/simple-multi-file/ui.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(gridlayout) 3 | 4 | ui <- grid_page( 5 | layout = c( 6 | "A" 7 | ), 8 | row_sizes = c( 9 | "1fr" 10 | ), 11 | col_sizes = c( 12 | "1fr" 13 | ), 14 | gap_size = "10px", 15 | grid_card(area = "A") 16 | ) 17 | -------------------------------------------------------------------------------- /scratch/start_editor.R: -------------------------------------------------------------------------------- 1 | library(shinyuieditor) 2 | launch_editor( 3 | app_loc = here::here("scratch/navbarpage/"), 4 | port = 8888, 5 | launch_browser = TRUE, 6 | stop_on_browser_close = FALSE 7 | ) 8 | 9 | 10 | 11 | launch_editor(app_loc = here::here("scratch/single-file-app/")) 12 | launch_editor(app_loc = here::here("scratch/webapp")) 13 | launch_editor(app_loc = here::here("scratch/unknown-args")) 14 | launch_editor(app_loc = here::here("scratch/just_server")) 15 | launch_editor(app_loc = here::here("scratch/just_ui")) 16 | launch_editor(app_loc = here::here("scratch/empty_directory")) 17 | launch_editor(app_loc = here::here("scratch/non_existant")) 18 | launch_editor(app_loc = here::here("scratch/broken_ui")) 19 | 20 | launch_editor(app_loc = here::here("scratch/navbarpage/")) 21 | 22 | 23 | launch_editor(app_loc = here::here("inst/app-templates/empty/")) 24 | -------------------------------------------------------------------------------- /scratch/start_editor_non_interactive.R: -------------------------------------------------------------------------------- 1 | devtools::load_all(".") 2 | 3 | launch_editor( 4 | app_loc = here::here("scratch/a-brand-new-app3"), 5 | port = 8888, 6 | launch_browser = FALSE, 7 | stop_on_browser_close = FALSE 8 | ) 9 | -------------------------------------------------------------------------------- /shinyuieditor-hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/shinyuieditor-hex.png -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(shinyuieditor) 3 | 4 | test_check("shinyuieditor") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-app-location-validation.R: -------------------------------------------------------------------------------- 1 | 2 | test_that("Validate path to app script, or to folder container app", { 3 | 4 | expect_equal( 5 | validate_app_loc("my/app/loc/app.R"), 6 | validate_app_loc("my/app/loc") 7 | ) 8 | 9 | expect_equal( 10 | validate_app_loc("my/app/loc/ui.R"), 11 | validate_app_loc("my/app/loc") 12 | ) 13 | }) 14 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/demo-app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/demo-app/favicon.ico -------------------------------------------------------------------------------- /vignettes/demo-app/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/demo-app/logo192.png -------------------------------------------------------------------------------- /vignettes/demo-app/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/demo-app/logo512.png -------------------------------------------------------------------------------- /vignettes/demo-app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /vignettes/demo-app/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /vignettes/demo-app/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/demo-app/tree-sitter.wasm -------------------------------------------------------------------------------- /vignettes/how-to-videos/add-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/add-element.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/add-tract.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/add-tract.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/delete-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/delete-an-element.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/delete-tract.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/delete-tract.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/move-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/move-an-element.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/resize-with-drag.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/resize-with-drag.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/resize-with-widget.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/resize-with-widget.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/select-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/select-an-element.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/show-size-widget.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/show-size-widget.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/undo-redo.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/undo-redo.webm -------------------------------------------------------------------------------- /vignettes/how-to-videos/update-an-element.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/how-to-videos/update-an-element.webm -------------------------------------------------------------------------------- /vignettes/screenshots/template-chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/screenshots/template-chooser.png -------------------------------------------------------------------------------- /vignettes/unknown-arguments-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/shinyuieditor/9832f41d6ef483e819b47ce55187fa2b8d0d0563/vignettes/unknown-arguments-display.png --------------------------------------------------------------------------------