From 0709c5941712e40cb45073819b339f1a24da8201 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Fri, 12 Nov 2021 15:42:34 +1100 Subject: [PATCH] action settings overhaul with form input support --- package.json | 2 +- .../CodeEditor/CodeEditorHelper.tsx | 1 + src/components/InlineOpenInNewIcon.tsx | 2 +- src/components/fields/Action/ActionFab.tsx | 11 +- .../fields/Action/FormDialog/Dialog.tsx | 14 +- .../fields/Action/FormFieldSnippets.tsx | 102 +++ src/components/fields/Action/Settings.tsx | 622 +++++++++++++----- yarn.lock | 8 +- 8 files changed, 590 insertions(+), 172 deletions(-) create mode 100644 src/components/fields/Action/FormFieldSnippets.tsx diff --git a/package.json b/package.json index 75a0c685..31dde2ab 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@mui/lab": "^5.0.0-alpha.50", "@mui/material": "^5.0.0", "@mui/styles": "^5.0.0", - "@rowy/form-builder": "^0.3.1", + "@rowy/form-builder": "^0.4.2", "@rowy/multiselect": "^0.2.3", "@tinymce/tinymce-react": "^3.12.6", "algoliasearch": "^4.8.6", diff --git a/src/components/CodeEditor/CodeEditorHelper.tsx b/src/components/CodeEditor/CodeEditorHelper.tsx index 0535409a..4c79f215 100644 --- a/src/components/CodeEditor/CodeEditorHelper.tsx +++ b/src/components/CodeEditor/CodeEditorHelper.tsx @@ -63,6 +63,7 @@ export default function CodeEditorHelper({ + setSnippetMenuAnchor(null)} + MenuListProps={{ "aria-labelledby": "snippet-button" }} + anchorOrigin={{ horizontal: "right", vertical: "bottom" }} + transformOrigin={{ horizontal: "right", vertical: "top" }} + > + {FORM_FIELD_SNIPPETS.map((snippet) => ( + { + // Write validation as nested array above, but must be converted + // to [ { 0: …, 1: …, … } ] since Firestore doesn't support + // nested arrays + const sanitized: any = snippet.value; + if (Array.isArray(snippet.value.validation)) + sanitized.validation = snippet.value.validation.reduce( + (a, c, i) => ({ ...a, [i]: c }), + {} + ); + + navigator.clipboard.writeText( + JSON.stringify(sanitized, undefined, 2) + ); + enqueueSnackbar("Copied to clipboard"); + setSnippetMenuAnchor(null); + }} + /> + ))} + + + ); +} diff --git a/src/components/fields/Action/Settings.tsx b/src/components/fields/Action/Settings.tsx index e8de7b34..b1a3e65e 100644 --- a/src/components/fields/Action/Settings.tsx +++ b/src/components/fields/Action/Settings.tsx @@ -1,16 +1,36 @@ -import { lazy, Suspense } from "react"; +import { lazy, Suspense, useState } from "react"; +import _get from "lodash/get"; +import stringify from "json-stable-stringify-without-jsonify"; + import { - Typography, + Stepper, + Step, + StepButton, + StepContent, + Stack, + Grid, TextField, - Switch, - FormControlLabel, Divider, + FormControl, + FormLabel, + FormControlLabel, + RadioGroup, + Radio, + Typography, + InputLabel, + Link, + Checkbox, + FormHelperText, } from "@mui/material"; +import ExpandIcon from "@mui/icons-material/KeyboardArrowDown"; + import MultiSelect from "@rowy/multiselect"; import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton"; -import { useProjectContext } from "@src/contexts/ProjectContext"; -import { InputLabel } from "@mui/material"; import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; +import FormFieldSnippets from "./FormFieldSnippets"; + +import { useProjectContext } from "@src/contexts/ProjectContext"; import { WIKI_LINKS } from "@src/constants/externalLinks"; const CodeEditor = lazy( @@ -20,165 +40,459 @@ const CodeEditor = lazy( const Settings = ({ config, onChange }) => { const { tableState, roles } = useProjectContext(); + + const [activeStep, setActiveStep] = useState(0); + const columnOptions = Object.values(tableState?.columns ?? {}).map((c) => ({ label: c.name, value: c.key, })); + + const formattedParamsJson = stringify( + Array.isArray(config.params) ? config.params : [], + { space: 2 } + ); + const [codeValid, setCodeValid] = useState(true); + + const scriptExtraLibs = [ + [ + "declare class ref {", + " /**", + " * Reference object of the row running the action script", + " */", + "static id:string", + "static path:string", + "static parentId:string", + "static tablePath:string", + "}", + ].join("\n"), + [ + "declare class actionParams {", + " /**", + " * actionParams are provided by dialog popup form", + " */", + (config.params ?? []).filter(Boolean).map((param) => { + const validationKeys = Object.keys(param.validation ?? {}); + if (validationKeys.includes("string")) { + return `static ${param.name}: string`; + } else if (validationKeys.includes("array")) { + return `static ${param.name}: any[]`; + } else return `static ${param.name}: any`; + }), + "}", + ].join("\n"), + ]; + + // Backwards-compatibility: previously user could set `confirmation` without + // having to set `friction: confirmation` + const showConfirmationField = + config.friction === "confirmation" || + (!config.friction && + typeof config.confirmation === "string" && + config.confirmation !== ""); + return ( - <> - Allowed roles - - Authenticated user must have at least one of these to run the script - - + Required fields - - All of the selected fields must have a value for the script to run - + "& .MuiStepLabel-root": { width: "100%" }, + "& .MuiStepLabel-label": { + display: "flex", + width: "100%", + typography: "subtitle2", + "&.Mui-active": { typography: "subtitle2" }, + }, + "& .MuiStepLabel-label svg": { + display: "block", + marginLeft: "auto", + my: ((24 - 18) / 2 / 8) * -1, + transition: (theme) => theme.transitions.create("transform"), + }, + "& .Mui-active svg": { + transform: "rotate(180deg)", + }, + }} + > + + setActiveStep(0)}> + Requirements + + - - - Confirmation template - - The action button will not ask for confirmation if this is left empty - - - { - onChange("confirmation")(e.target.value); - }} - fullWidth - /> - - onChange("isActionScript")(!Boolean(config.isActionScript)) - } - name="actionScript" - /> - } - label="Set as an action script" - /> - {!Boolean(config.isActionScript) ? ( - { - onChange("callableName")(e.target.value); - }} - /> - ) : ( - <> - Action script - - - }> - { - const validationKeys = Object.keys(param.validation); - if (validationKeys.includes("string")) { - return `static ${param.name}:string`; - } else if (validationKeys.includes("array")) { - return `static ${param.name}:any[]`; - } else return `static ${param.name}:any`; - }), - "}", - ].join("\n"), - ]} - onChange={onChange("script")} - /> - - - onChange("redo.enabled")(!Boolean(config.redo?.enabled)) - } - name="redo toggle" - /> - } - label="User can redo (re-runs the same script)" - /> - - onChange("undo.enabled")(!Boolean(config.undo?.enabled)) - } - name="undo toggle" - /> - } - label="User can undo" - /> - {config["undo.enabled"] && ( - <> - - Undo confirmation template - - { - onChange("undo.confirmation")(e.target.value); + + + + - Undo action script - }> - + + + + + + + + + setActiveStep(1)}> + Confirmation + + + + + + + + Clicking the action button will: + + + onChange("friction")(e.target.value)} + > + } + label="Run the action immediately" /> - - - )} - + } + label="Ask the user for confirmation" + /> + } + label={ + <> + + Ask the user for input in a form (Alpha) + + + + This feature is currently undocumented and is subject to + change in future minor versions + + + } + /> + + + + {showConfirmationField && ( + onChange("confirmation")(e.target.value)} + fullWidth + helperText="The action button will not ask for confirmation if this is left empty" + /> + )} + + {config.friction === "params" && ( + + + + Form fields + + + + + + + }> + { + try { + if (v) { + const parsed = JSON.parse(v); + onChange("params")(parsed); + } + } catch (e) { + console.log(`Failed to parse JSON: ${e}`); + setCodeValid(false); + } + }} + onValidStatusUpdate={({ isValid }) => setCodeValid(isValid)} + error={!codeValid} + /> + + + {!codeValid && ( + + Invalid JSON + + )} + + )} + + + + + + setActiveStep(2)}> + Action + + + + + + + + Clicking the action button will run a: + + + onChange("isActionScript")(e.target.value === "actionScript") + } + > + } + label={ + <> + Script + + Write JavaScript code below that will be executed by + Rowy Run.{" "} + + Requires Rowy Run setup + + + + + } + /> + } + label={ + <> + Callable + + A{" "} + + callable function + + {" "} + you’ve deployed on your Firestore or Google Cloud + project + + + } + /> + + + + {!config.isActionScript ? ( + onChange("callableName")(e.target.value)} + helperText={ + <> + Write the name of the callable function you’ve deployed to + your project.{" "} + + View your callable functions + + +
+ Your callable function must be compatible with Rowy Action + columns.{" "} + + View requirements + + + + } + /> + ) : ( + <> + + Action script + }> + + + + + + + + + onChange("redo.enabled")( + !Boolean(config.redo?.enabled) + ) + } + name="redo" + /> + } + label={ + <> + + User can redo + + + Re-runs the script above + + + } + style={{ marginLeft: -11 }} + /> + + + + onChange("undo.enabled")( + !Boolean(config.undo?.enabled) + ) + } + name="undo" + /> + } + label={ + <> + + User can undo + + + Runs a new script + + + } + style={{ marginLeft: -11 }} + /> + + + + )} +
+
+
+ + {config.isActionScript && _get(config, "undo.enabled") && ( + + setActiveStep(3)}> + Undo action + + + + + + {(showConfirmationField || + !config.friction || + config.friction === "none") && ( + { + onChange("undo.confirmation")(e.target.value); + }} + fullWidth + helperText={ + <> + {showConfirmationField && + "Override the confirmation message above. "} + The action button will not ask for confirmation if this is + left empty{showConfirmationField && "."} + + } + /> + )} + + + Undo script + }> + + + + + + + )} - +
); }; export default Settings; diff --git a/yarn.lock b/yarn.lock index 446bbe44..f42bb79e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2664,10 +2664,10 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@rowy/form-builder@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@rowy/form-builder/-/form-builder-0.3.1.tgz#7359a05563c99ae4c222fa5336a57ce7a344ae29" - integrity sha512-5IlNtBb6V5VdbpYWhO4P6QIBVqI42SVko15Bmn1YLd0JyoI3wza+seFBK0PgrBUB/OzTEA/hsBN5cyWhqlKt2Q== +"@rowy/form-builder@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@rowy/form-builder/-/form-builder-0.4.2.tgz#e1693b16af31ed486b7314cd657cd3a1c9c3e937" + integrity sha512-bedChgzyL7BxeQVijaxAXYJqcZtTaCLy4CfqInbsKEB+0OJ3nWi2c9l9d+v8yf6QJ2+3ERyveVfuKq6HAVTQ/g== dependencies: "@hookform/resolvers" "^2.6.0" "@mdi/js" "^5.9.55"