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 | {label}
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 |
18 |
19 |
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
--------------------------------------------------------------------------------