mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-28 16:06:41 +01:00
add collapsible SteppedAccordion
This commit is contained in:
92
src/components/SteppedAccordion.tsx
Normal file
92
src/components/SteppedAccordion.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
Stepper,
|
||||
StepperProps,
|
||||
Step,
|
||||
StepProps,
|
||||
StepButton,
|
||||
StepButtonProps,
|
||||
Typography,
|
||||
StepContent,
|
||||
StepContentProps,
|
||||
} from "@mui/material";
|
||||
import ExpandIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
|
||||
export interface ISteppedAccordionProps extends Partial<StepperProps> {
|
||||
steps: {
|
||||
id: string;
|
||||
title: React.ReactNode;
|
||||
optional?: boolean;
|
||||
content: React.ReactNode;
|
||||
|
||||
stepProps?: Partial<StepProps>;
|
||||
titleProps?: Partial<StepButtonProps>;
|
||||
contentProps?: Partial<StepContentProps>;
|
||||
}[];
|
||||
}
|
||||
|
||||
export default function SteppedAccordion({
|
||||
steps,
|
||||
...props
|
||||
}: ISteppedAccordionProps) {
|
||||
const [activeStep, setActiveStep] = useState(steps[0].id);
|
||||
|
||||
return (
|
||||
<Stepper
|
||||
nonLinear
|
||||
activeStep={steps.findIndex((x) => x.id === activeStep)}
|
||||
orientation="vertical"
|
||||
{...props}
|
||||
sx={{
|
||||
mt: 0,
|
||||
|
||||
"& .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)",
|
||||
},
|
||||
|
||||
...props.sx,
|
||||
}}
|
||||
>
|
||||
{steps.map(
|
||||
({
|
||||
id,
|
||||
title,
|
||||
optional,
|
||||
content,
|
||||
stepProps,
|
||||
titleProps,
|
||||
contentProps,
|
||||
}) => (
|
||||
<Step key={id} {...stepProps}>
|
||||
<StepButton
|
||||
onClick={() => setActiveStep((s) => (s === id ? "" : id))}
|
||||
optional={
|
||||
optional && <Typography variant="caption">Optional</Typography>
|
||||
}
|
||||
{...titleProps}
|
||||
>
|
||||
{title}
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
|
||||
<StepContent {...contentProps}>{content}</StepContent>
|
||||
</Step>
|
||||
)
|
||||
)}
|
||||
</Stepper>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +1,18 @@
|
||||
import { useState } from "react";
|
||||
import _isEqual from "lodash/isEqual";
|
||||
import _upperFirst from "lodash/upperFirst";
|
||||
import useStateRef from "react-usestateref";
|
||||
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Stepper,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
Typography,
|
||||
Link,
|
||||
} from "@mui/material";
|
||||
import ExpandIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
import { Grid, TextField, FormControlLabel, Switch } from "@mui/material";
|
||||
|
||||
import Modal, { IModalProps } from "@src/components/Modal";
|
||||
import SteppedAccordion from "@src/components/SteppedAccordion";
|
||||
import Step1Triggers from "./Step1Triggers";
|
||||
import Step2RequiredFields from "./Step2RequiredFields";
|
||||
import Step3Conditions from "./Step3Conditions";
|
||||
import Step4Body from "./Step4Body";
|
||||
|
||||
import { useConfirmation } from "@src/components/ConfirmationDialog";
|
||||
|
||||
import { extensionNames, IExtension } from "./utils";
|
||||
import { WIKI_LINKS } from "@src/constants/externalLinks";
|
||||
|
||||
type StepValidation = Record<"condition" | "extensionBody", boolean>;
|
||||
export interface IExtensionModalStepProps {
|
||||
@@ -58,8 +43,6 @@ export default function ExtensionModal({
|
||||
const [extensionObject, setExtensionObject] =
|
||||
useState<IExtension>(initialObject);
|
||||
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
|
||||
const [validation, setValidation, validationRef] =
|
||||
useStateRef<StepValidation>({ condition: true, extensionBody: true });
|
||||
|
||||
@@ -144,100 +127,32 @@ export default function ExtensionModal({
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Stepper
|
||||
nonLinear
|
||||
activeStep={activeStep}
|
||||
orientation="vertical"
|
||||
sx={{
|
||||
mt: 0,
|
||||
|
||||
"& .MuiStepLabel-root": { width: "100%" },
|
||||
"& .MuiStepLabel-label": {
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
typography: "subtitle2",
|
||||
"&.Mui-active": { typography: "subtitle2" },
|
||||
<SteppedAccordion
|
||||
steps={[
|
||||
{
|
||||
id: "triggers",
|
||||
title: "Trigger events",
|
||||
content: <Step1Triggers {...stepProps} />,
|
||||
},
|
||||
"& .MuiStepLabel-label svg": {
|
||||
display: "block",
|
||||
marginLeft: "auto",
|
||||
my: ((24 - 18) / 2 / 8) * -1,
|
||||
transition: (theme) => theme.transitions.create("transform"),
|
||||
{
|
||||
id: "requiredFields",
|
||||
title: "Required fields",
|
||||
optional: true,
|
||||
content: <Step2RequiredFields {...stepProps} />,
|
||||
},
|
||||
"& .Mui-active svg": {
|
||||
transform: "rotate(180deg)",
|
||||
{
|
||||
id: "conditions",
|
||||
title: "Trigger conditions",
|
||||
optional: true,
|
||||
content: <Step3Conditions {...stepProps} />,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep(0)}>
|
||||
Trigger events
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Typography gutterBottom>
|
||||
Select which events trigger this extension
|
||||
</Typography>
|
||||
<Step1Triggers {...stepProps} />
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep(1)}>
|
||||
Required fields (optional)
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Typography gutterBottom>
|
||||
Optionally, select fields that must have a value set for the
|
||||
extension to be triggered for that row
|
||||
</Typography>
|
||||
<Step2RequiredFields {...stepProps} />
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep(2)}>
|
||||
Trigger conditions (optional)
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Typography gutterBottom>
|
||||
Optionally, write a function that determines if the extension
|
||||
should be triggered for a given row. Leave the function to
|
||||
always return <code>true</code> if you do not want to write
|
||||
additional logic.
|
||||
</Typography>
|
||||
<Step3Conditions {...stepProps} />
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep(3)}>
|
||||
Extension body
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Typography gutterBottom>
|
||||
Write the extension body function. Make sure you have set all
|
||||
the required parameters.{" "}
|
||||
<Link
|
||||
href={
|
||||
WIKI_LINKS[
|
||||
`extensions${_upperFirst(extensionObject.type)}`
|
||||
] || WIKI_LINKS.extensions
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Docs
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</Typography>
|
||||
<Step4Body {...stepProps} />
|
||||
</StepContent>
|
||||
</Step>
|
||||
</Stepper>
|
||||
{
|
||||
id: "body",
|
||||
title: "Extension body",
|
||||
content: <Step4Body {...stepProps} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
actions={{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IExtensionModalStepProps } from "./ExtensionModal";
|
||||
|
||||
import {
|
||||
Typography,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormGroup,
|
||||
@@ -15,42 +16,48 @@ export default function Step1Triggers({
|
||||
setExtensionObject,
|
||||
}: IExtensionModalStepProps) {
|
||||
return (
|
||||
<FormControl component="fieldset" required>
|
||||
<FormLabel component="legend" className="visually-hidden">
|
||||
Triggers
|
||||
</FormLabel>
|
||||
<>
|
||||
<Typography gutterBottom>
|
||||
Select which events trigger this extension
|
||||
</Typography>
|
||||
|
||||
<FormGroup>
|
||||
{triggerTypes.map((trigger) => (
|
||||
<FormControlLabel
|
||||
key={trigger}
|
||||
label={trigger}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={extensionObject.triggers.includes(trigger)}
|
||||
name={trigger}
|
||||
onChange={() => {
|
||||
setExtensionObject((extensionObject) => {
|
||||
if (extensionObject.triggers.includes(trigger)) {
|
||||
return {
|
||||
...extensionObject,
|
||||
triggers: extensionObject.triggers.filter(
|
||||
(t) => t !== trigger
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...extensionObject,
|
||||
triggers: [...extensionObject.triggers, trigger],
|
||||
};
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
<FormControl component="fieldset" required>
|
||||
<FormLabel component="legend" className="visually-hidden">
|
||||
Triggers
|
||||
</FormLabel>
|
||||
|
||||
<FormGroup>
|
||||
{triggerTypes.map((trigger) => (
|
||||
<FormControlLabel
|
||||
key={trigger}
|
||||
label={trigger}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={extensionObject.triggers.includes(trigger)}
|
||||
name={trigger}
|
||||
onChange={() => {
|
||||
setExtensionObject((extensionObject) => {
|
||||
if (extensionObject.triggers.includes(trigger)) {
|
||||
return {
|
||||
...extensionObject,
|
||||
triggers: extensionObject.triggers.filter(
|
||||
(t) => t !== trigger
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...extensionObject,
|
||||
triggers: [...extensionObject.triggers, trigger],
|
||||
};
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IExtensionModalStepProps } from "./ExtensionModal";
|
||||
import _sortBy from "lodash/sortBy";
|
||||
|
||||
import { Typography, ListItemIcon } from "@mui/material";
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import { ListItemIcon } from "@mui/material";
|
||||
|
||||
import { useProjectContext } from "@src/contexts/ProjectContext";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
@@ -15,45 +15,52 @@ export default function Step2RequiredFields({
|
||||
const { tableState } = useProjectContext();
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
aria-label="Required fields"
|
||||
multiple
|
||||
value={extensionObject.requiredFields}
|
||||
disabled={!tableState?.columns}
|
||||
options={
|
||||
tableState?.columns
|
||||
? _sortBy(Object.values(tableState!.columns), "index")
|
||||
.filter((c) => c.type !== FieldType.id)
|
||||
.map((c) => ({
|
||||
value: c.key,
|
||||
label: c.name,
|
||||
type: c.type,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
onChange={(requiredFields) =>
|
||||
setExtensionObject((e) => ({ ...e, requiredFields }))
|
||||
}
|
||||
TextFieldProps={{ autoFocus: true }}
|
||||
freeText
|
||||
AddButtonProps={{ children: "Add other field…" }}
|
||||
AddDialogProps={{
|
||||
title: "Add other field",
|
||||
textFieldLabel: "Field key",
|
||||
}}
|
||||
itemRenderer={(option: {
|
||||
value: string;
|
||||
label: string;
|
||||
type?: FieldType;
|
||||
}) => (
|
||||
<>
|
||||
<ListItemIcon style={{ minWidth: 40 }}>
|
||||
{option.type && getFieldProp("icon", option.type)}
|
||||
</ListItemIcon>
|
||||
{option.label}
|
||||
<code style={{ marginLeft: "auto" }}>{option.value}</code>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<>
|
||||
<Typography gutterBottom>
|
||||
Optionally, select fields that must have a value set for the extension
|
||||
to be triggered for that row
|
||||
</Typography>
|
||||
|
||||
<MultiSelect
|
||||
aria-label="Required fields"
|
||||
multiple
|
||||
value={extensionObject.requiredFields}
|
||||
disabled={!tableState?.columns}
|
||||
options={
|
||||
tableState?.columns
|
||||
? _sortBy(Object.values(tableState!.columns), "index")
|
||||
.filter((c) => c.type !== FieldType.id)
|
||||
.map((c) => ({
|
||||
value: c.key,
|
||||
label: c.name,
|
||||
type: c.type,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
onChange={(requiredFields) =>
|
||||
setExtensionObject((e) => ({ ...e, requiredFields }))
|
||||
}
|
||||
TextFieldProps={{ autoFocus: true }}
|
||||
freeText
|
||||
AddButtonProps={{ children: "Add other field…" }}
|
||||
AddDialogProps={{
|
||||
title: "Add other field",
|
||||
textFieldLabel: "Field key",
|
||||
}}
|
||||
itemRenderer={(option: {
|
||||
value: string;
|
||||
label: string;
|
||||
type?: FieldType;
|
||||
}) => (
|
||||
<>
|
||||
<ListItemIcon style={{ minWidth: 40 }}>
|
||||
{option.type && getFieldProp("icon", option.type)}
|
||||
</ListItemIcon>
|
||||
{option.label}
|
||||
<code style={{ marginLeft: "auto" }}>{option.value}</code>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { lazy, Suspense } from "react";
|
||||
import { IExtensionModalStepProps } from "./ExtensionModal";
|
||||
import useStateRef from "react-usestateref";
|
||||
|
||||
import { Typography } from "@mui/material";
|
||||
import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton";
|
||||
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
|
||||
import { WIKI_LINKS } from "@src/constants/externalLinks";
|
||||
@@ -49,6 +50,12 @@ export default function Step3Conditions({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography gutterBottom>
|
||||
Optionally, write a function that determines if the extension should be
|
||||
triggered for a given row. Leave the function to always return{" "}
|
||||
<code>true</code> if you do not want to write additional logic.
|
||||
</Typography>
|
||||
|
||||
<Suspense fallback={<FieldSkeleton height={200} />}>
|
||||
<CodeEditor
|
||||
value={extensionObject.conditions}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IWebhookModalStepProps } from "./WebhookModal";
|
||||
import useStateRef from "react-usestateref";
|
||||
|
||||
import { Typography } from "@mui/material";
|
||||
import CodeEditor from "@src/components/CodeEditor";
|
||||
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
|
||||
|
||||
@@ -24,6 +25,12 @@ export default function Step3Conditions({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography gutterBottom>
|
||||
Optionally, write a function that determines if the webhook call should
|
||||
be processed. Leave the function to always return <code>true</code> if
|
||||
you do not want to write additional logic.
|
||||
</Typography>
|
||||
|
||||
<div>
|
||||
<CodeEditor
|
||||
value={webhookObject.conditions}
|
||||
|
||||
@@ -2,11 +2,14 @@ import { IWebhookModalStepProps } from "./WebhookModal";
|
||||
import _upperFirst from "lodash/upperFirst";
|
||||
import useStateRef from "react-usestateref";
|
||||
|
||||
import { Typography, Link } from "@mui/material";
|
||||
import CodeEditor from "@src/components/CodeEditor";
|
||||
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
|
||||
import { WIKI_LINKS } from "@src/constants/externalLinks";
|
||||
import { parserExtraLibs } from "./utils";
|
||||
|
||||
const additionalVariables = [
|
||||
{
|
||||
key: "req",
|
||||
@@ -30,6 +33,22 @@ export default function Step4Body({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography gutterBottom>
|
||||
Write the webhook parsed function. The returned object of the parser
|
||||
will be added as new row{" "}
|
||||
<Link
|
||||
href={
|
||||
WIKI_LINKS[`webhooks${_upperFirst(webhookObject.type)}`] ||
|
||||
WIKI_LINKS.webhooks
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Docs
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
<div>
|
||||
<CodeEditor
|
||||
value={webhookObject.parser}
|
||||
|
||||
@@ -1,32 +1,17 @@
|
||||
import { useState } from "react";
|
||||
import _isEqual from "lodash/isEqual";
|
||||
import _upperFirst from "lodash/upperFirst";
|
||||
import useStateRef from "react-usestateref";
|
||||
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Stepper,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
Typography,
|
||||
Link,
|
||||
} from "@mui/material";
|
||||
import ExpandIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
import { Grid, TextField, FormControlLabel, Switch } from "@mui/material";
|
||||
|
||||
import Modal, { IModalProps } from "@src/components/Modal";
|
||||
import SteppedAccordion from "@src/components/SteppedAccordion";
|
||||
import Step1Auth from "./Step1Auth";
|
||||
import Step2Conditions from "./Step2Conditions";
|
||||
import Step3Body from "./Step3Parser";
|
||||
|
||||
import { useConfirmation } from "@src/components/ConfirmationDialog";
|
||||
|
||||
import { webhookNames, IWebhook } from "./utils";
|
||||
import { WIKI_LINKS } from "@src/constants/externalLinks";
|
||||
|
||||
type StepValidation = Record<"condition" | "parser", boolean>;
|
||||
export interface IWebhookModalStepProps {
|
||||
@@ -56,8 +41,6 @@ export default function WebhookModal({
|
||||
|
||||
const [webhookObject, setWebhookObject] = useState<IWebhook>(initialObject);
|
||||
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
|
||||
const [validation, setValidation, validationRef] =
|
||||
useStateRef<StepValidation>({ condition: true, parser: true });
|
||||
|
||||
@@ -141,83 +124,27 @@ export default function WebhookModal({
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Stepper
|
||||
nonLinear
|
||||
activeStep={activeStep}
|
||||
orientation="vertical"
|
||||
sx={{
|
||||
mt: 0,
|
||||
|
||||
"& .MuiStepLabel-root": { width: "100%" },
|
||||
"& .MuiStepLabel-label": {
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
typography: "subtitle2",
|
||||
"&.Mui-active": { typography: "subtitle2" },
|
||||
<SteppedAccordion
|
||||
steps={[
|
||||
{
|
||||
id: "verification",
|
||||
title: "Verification",
|
||||
optional: true,
|
||||
content: <Step1Auth {...stepProps} />,
|
||||
},
|
||||
"& .MuiStepLabel-label svg": {
|
||||
display: "block",
|
||||
marginLeft: "auto",
|
||||
my: ((24 - 18) / 2 / 8) * -1,
|
||||
transition: (theme) => theme.transitions.create("transform"),
|
||||
{
|
||||
id: "conditions",
|
||||
title: "Conditions",
|
||||
optional: true,
|
||||
content: <Step2Conditions {...stepProps} />,
|
||||
},
|
||||
"& .Mui-active svg": {
|
||||
transform: "rotate(180deg)",
|
||||
{
|
||||
id: "parser",
|
||||
title: "Parser",
|
||||
content: <Step3Body {...stepProps} />,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep(0)}>
|
||||
Verification
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Step1Auth {...stepProps} />
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep(1)}>
|
||||
Conditions (optional)
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Typography gutterBottom>
|
||||
Optionally, write a function that determines if the webhook
|
||||
call should be processed. Leave the function to always return{" "}
|
||||
<code>true</code> if you do not want to write additional
|
||||
logic.
|
||||
</Typography>
|
||||
<Step2Conditions {...stepProps} />
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep(2)}>
|
||||
Parser
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Typography gutterBottom>
|
||||
Write the webhook parsed function. The returned object of the
|
||||
parser will be added as new row{" "}
|
||||
<Link
|
||||
href={
|
||||
WIKI_LINKS[
|
||||
`webhooks${_upperFirst(webhookObject.type)}`
|
||||
] || WIKI_LINKS.webhooks
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Docs
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</Typography>
|
||||
<Step3Body {...stepProps} />
|
||||
</StepContent>
|
||||
</Step>
|
||||
</Stepper>
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
actions={{
|
||||
|
||||
@@ -27,6 +27,7 @@ import RunIcon from "@mui/icons-material/PlayArrow";
|
||||
import RedoIcon from "@mui/icons-material/Refresh";
|
||||
import UndoIcon from "@mui/icons-material/Undo";
|
||||
|
||||
import SteppedAccordion from "@src/components/SteppedAccordion";
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton";
|
||||
import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
|
||||
@@ -101,287 +102,368 @@ const Settings = ({ config, onChange }) => {
|
||||
config.confirmation !== "");
|
||||
|
||||
return (
|
||||
<Stepper
|
||||
nonLinear
|
||||
activeStep={steps.indexOf(activeStep)}
|
||||
orientation="vertical"
|
||||
sx={{
|
||||
mt: 0,
|
||||
|
||||
"& .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)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep("requirements")}>
|
||||
Requirements
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
|
||||
<StepContent>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<MultiSelect
|
||||
label="Required roles"
|
||||
options={roles ?? []}
|
||||
value={config.requiredRoles ?? []}
|
||||
onChange={onChange("requiredRoles")}
|
||||
TextFieldProps={{
|
||||
id: "requiredRoles",
|
||||
helperText:
|
||||
"The user must have at least one of these roles to run the script",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<MultiSelect
|
||||
label="Required fields"
|
||||
options={columnOptions}
|
||||
value={config.requiredFields ?? []}
|
||||
onChange={onChange("requiredFields")}
|
||||
TextFieldProps={{
|
||||
id: "requiredFields",
|
||||
helperText:
|
||||
"All the selected fields must have a value for the script to run",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep("friction")}>
|
||||
Confirmation
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
|
||||
<StepContent>
|
||||
<Stack spacing={3}>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">
|
||||
Clicking the action button will:
|
||||
</FormLabel>
|
||||
|
||||
<RadioGroup
|
||||
aria-label="Action button friction"
|
||||
name="friction"
|
||||
defaultValue={
|
||||
typeof config.confirmation === "string" &&
|
||||
config.confirmation !== ""
|
||||
? "confirmation"
|
||||
: "none"
|
||||
}
|
||||
value={config.friction}
|
||||
onChange={(e) => onChange("friction")(e.target.value)}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="none"
|
||||
control={<Radio />}
|
||||
label="Run the action immediately"
|
||||
<SteppedAccordion
|
||||
steps={[
|
||||
{
|
||||
id: "requirements",
|
||||
title: "Requirements",
|
||||
content: (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<MultiSelect
|
||||
label="Required roles"
|
||||
options={roles ?? []}
|
||||
value={config.requiredRoles ?? []}
|
||||
onChange={onChange("requiredRoles")}
|
||||
TextFieldProps={{
|
||||
id: "requiredRoles",
|
||||
helperText:
|
||||
"The user must have at least one of these roles to run the script",
|
||||
}}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="confirmation"
|
||||
control={<Radio />}
|
||||
label="Ask the user for confirmation"
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<MultiSelect
|
||||
label="Required fields"
|
||||
options={columnOptions}
|
||||
value={config.requiredFields ?? []}
|
||||
onChange={onChange("requiredFields")}
|
||||
TextFieldProps={{
|
||||
id: "requiredFields",
|
||||
helperText:
|
||||
"All the selected fields must have a value for the script to run",
|
||||
}}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="params"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
Ask the user for input in a form (Alpha)
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "confirmation",
|
||||
title: "Confirmation",
|
||||
content: (
|
||||
<Stack spacing={3}>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">
|
||||
Clicking the action button will:
|
||||
</FormLabel>
|
||||
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
This feature is currently undocumented and is subject to
|
||||
change in future minor versions
|
||||
</Typography>
|
||||
</>
|
||||
<RadioGroup
|
||||
aria-label="Action button friction"
|
||||
name="friction"
|
||||
defaultValue={
|
||||
typeof config.confirmation === "string" &&
|
||||
config.confirmation !== ""
|
||||
? "confirmation"
|
||||
: "none"
|
||||
}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
{showConfirmationField && (
|
||||
<TextField
|
||||
id="confirmation"
|
||||
label="Confirmation template"
|
||||
placeholder="Are sure you want to invest {{stockName}}?"
|
||||
value={config.confirmation}
|
||||
onChange={(e) => onChange("confirmation")(e.target.value)}
|
||||
fullWidth
|
||||
helperText="The action button will not ask for confirmation if this is left empty"
|
||||
/>
|
||||
)}
|
||||
|
||||
{config.friction === "params" && (
|
||||
<FormControl>
|
||||
<Grid container spacing={1} sx={{ mb: 0.5 }}>
|
||||
<Grid item xs>
|
||||
<InputLabel variant="filled">Form fields</InputLabel>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FormFieldSnippets />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Suspense fallback={<FieldSkeleton height={300} />}>
|
||||
<CodeEditor
|
||||
minHeight={200}
|
||||
defaultLanguage="json"
|
||||
value={formattedParamsJson}
|
||||
onChange={(v) => {
|
||||
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}
|
||||
value={config.friction}
|
||||
onChange={(e) => onChange("friction")(e.target.value)}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="none"
|
||||
control={<Radio />}
|
||||
label="Run the action immediately"
|
||||
/>
|
||||
</Suspense>
|
||||
<FormControlLabel
|
||||
value="confirmation"
|
||||
control={<Radio />}
|
||||
label="Ask the user for confirmation"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="params"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
Ask the user for input in a form (Alpha)
|
||||
</Typography>
|
||||
|
||||
{!codeValid && (
|
||||
<FormHelperText error variant="filled">
|
||||
Invalid JSON
|
||||
</FormHelperText>
|
||||
)}
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
This feature is currently undocumented and is subject
|
||||
to change in future minor versions
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
</Stack>
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep("action")}>
|
||||
Action
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
|
||||
<StepContent>
|
||||
<Stack spacing={3}>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">
|
||||
Clicking the action button will run a:
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="Action will run"
|
||||
name="isActionScript"
|
||||
value={config.isActionScript ? "actionScript" : "cloudFunction"}
|
||||
onChange={(e) =>
|
||||
onChange("isActionScript")(e.target.value === "actionScript")
|
||||
}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="actionScript"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">Script</Typography>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
Write JavaScript code below that will be executed by
|
||||
Rowy Run.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.rowyRun}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Requires Rowy Run setup
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
{showConfirmationField && (
|
||||
<TextField
|
||||
id="confirmation"
|
||||
label="Confirmation template"
|
||||
placeholder="Are sure you want to invest {{stockName}}?"
|
||||
value={config.confirmation}
|
||||
onChange={(e) => onChange("confirmation")(e.target.value)}
|
||||
fullWidth
|
||||
helperText="The action button will not ask for confirmation if this is left empty"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="cloudFunction"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">Callable</Typography>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
A{" "}
|
||||
<Link
|
||||
href="https://firebase.google.com/docs/functions/callable"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
callable function
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>{" "}
|
||||
you’ve deployed on your Firestore or Google Cloud
|
||||
project
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
{!config.isActionScript ? (
|
||||
<TextField
|
||||
id="callableName"
|
||||
label="Callable name"
|
||||
name="callableName"
|
||||
value={config.callableName}
|
||||
fullWidth
|
||||
onChange={(e) => onChange("callableName")(e.target.value)}
|
||||
helperText={
|
||||
<>
|
||||
Write the name of the callable function you’ve deployed to
|
||||
your project.{" "}
|
||||
<Link
|
||||
href={`https://console.firebase.google.com/project/rowyio/functions/list`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
View your callable functions
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
<br />
|
||||
Your callable function must be compatible with Rowy Action
|
||||
columns.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.fieldTypesAction + "#callable"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
View requirements
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{config.friction === "params" && (
|
||||
<FormControl>
|
||||
<InputLabel variant="filled">Action script</InputLabel>
|
||||
<Grid container spacing={1} sx={{ mb: 0.5 }}>
|
||||
<Grid item xs>
|
||||
<InputLabel variant="filled">Form fields</InputLabel>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FormFieldSnippets />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Suspense fallback={<FieldSkeleton height={300} />}>
|
||||
<CodeEditor
|
||||
minHeight={200}
|
||||
value={config.script}
|
||||
onChange={onChange("script")}
|
||||
defaultLanguage="json"
|
||||
value={formattedParamsJson}
|
||||
onChange={(v) => {
|
||||
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}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{!codeValid && (
|
||||
<FormHelperText error variant="filled">
|
||||
Invalid JSON
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
)}
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "action",
|
||||
title: "Action",
|
||||
content: (
|
||||
<Stack spacing={3}>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">
|
||||
Clicking the action button will run a:
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="Action will run"
|
||||
name="isActionScript"
|
||||
value={
|
||||
config.isActionScript ? "actionScript" : "cloudFunction"
|
||||
}
|
||||
onChange={(e) =>
|
||||
onChange("isActionScript")(
|
||||
e.target.value === "actionScript"
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="actionScript"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">Script</Typography>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
Write JavaScript code below that will be executed by
|
||||
Rowy Run.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.rowyRun}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Requires Rowy Run setup
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="cloudFunction"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">Callable</Typography>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
A{" "}
|
||||
<Link
|
||||
href="https://firebase.google.com/docs/functions/callable"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
callable function
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>{" "}
|
||||
you’ve deployed on your Firestore or Google Cloud
|
||||
project
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
{!config.isActionScript ? (
|
||||
<TextField
|
||||
id="callableName"
|
||||
label="Callable name"
|
||||
name="callableName"
|
||||
value={config.callableName}
|
||||
fullWidth
|
||||
onChange={(e) => onChange("callableName")(e.target.value)}
|
||||
helperText={
|
||||
<>
|
||||
Write the name of the callable function you’ve deployed to
|
||||
your project.{" "}
|
||||
<Link
|
||||
href={`https://console.firebase.google.com/project/rowyio/functions/list`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
View your callable functions
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
<br />
|
||||
Your callable function must be compatible with Rowy Action
|
||||
columns.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.fieldTypesAction + "#callable"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
View requirements
|
||||
<InlineOpenInNewIcon />
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FormControl>
|
||||
<InputLabel variant="filled">Action script</InputLabel>
|
||||
<Suspense fallback={<FieldSkeleton height={300} />}>
|
||||
<CodeEditor
|
||||
minHeight={200}
|
||||
value={config.script}
|
||||
onChange={onChange("script")}
|
||||
extraLibs={scriptExtraLibs}
|
||||
/>
|
||||
</Suspense>
|
||||
<CodeEditorHelper
|
||||
docLink={WIKI_LINKS.fieldTypesAction + "#script"}
|
||||
additionalVariables={[]}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={config.redo?.enabled}
|
||||
onChange={() =>
|
||||
onChange("redo.enabled")(
|
||||
!Boolean(config.redo?.enabled)
|
||||
)
|
||||
}
|
||||
name="redo"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
User can redo
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
>
|
||||
Re-runs the script above
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
style={{ marginLeft: -11 }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={config.undo?.enabled}
|
||||
onChange={() =>
|
||||
onChange("undo.enabled")(
|
||||
!Boolean(config.undo?.enabled)
|
||||
)
|
||||
}
|
||||
name="undo"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
User can undo
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
>
|
||||
Runs a new script
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
style={{ marginLeft: -11 }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
config.isActionScript &&
|
||||
_get(config, "undo.enabled") && {
|
||||
id: "undo",
|
||||
title: "Undo action",
|
||||
content: (
|
||||
<Stack spacing={3}>
|
||||
{(showConfirmationField ||
|
||||
!config.friction ||
|
||||
config.friction === "none") && (
|
||||
<TextField
|
||||
id="undo.confirmation"
|
||||
label="Undo confirmation template"
|
||||
placeholder="Are you sure you want to sell your stocks in {{stockName}}?"
|
||||
value={_get(config, "undo.confirmation")}
|
||||
onChange={(e) => {
|
||||
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 && "."}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<InputLabel variant="filled">Undo script</InputLabel>
|
||||
<Suspense fallback={<FieldSkeleton height={300} />}>
|
||||
<CodeEditor
|
||||
value={_get(config, "undo.script")}
|
||||
onChange={onChange("undo.script")}
|
||||
extraLibs={scriptExtraLibs}
|
||||
/>
|
||||
</Suspense>
|
||||
@@ -390,230 +472,90 @@ const Settings = ({ config, onChange }) => {
|
||||
additionalVariables={[]}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "customization",
|
||||
title: "Customization",
|
||||
content: (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={config.customIcons?.enabled}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.enabled")(e.target.checked)
|
||||
}
|
||||
name="customIcons.enabled"
|
||||
/>
|
||||
}
|
||||
label="Customize button icons with emoji"
|
||||
style={{ marginLeft: -11 }}
|
||||
/>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={config.redo?.enabled}
|
||||
onChange={() =>
|
||||
onChange("redo.enabled")(
|
||||
!Boolean(config.redo?.enabled)
|
||||
)
|
||||
}
|
||||
name="redo"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
User can redo
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Re-runs the script above
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
style={{ marginLeft: -11 }}
|
||||
/>
|
||||
{config.customIcons?.enabled && (
|
||||
<Grid container spacing={2} sx={{ mt: { xs: 0, sm: -1 } }}>
|
||||
<Grid item xs={12} sm={true}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField
|
||||
id="customIcons.run"
|
||||
value={_get(config, "customIcons.run")}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.run")(e.target.value)
|
||||
}
|
||||
label="Run:"
|
||||
className="labelHorizontal"
|
||||
inputProps={{ style: { width: "3ch" } }}
|
||||
/>
|
||||
<Fab size="small" aria-label="Preview of run button">
|
||||
{_get(config, "customIcons.run") || <RunIcon />}
|
||||
</Fab>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={config.undo?.enabled}
|
||||
onChange={() =>
|
||||
onChange("undo.enabled")(
|
||||
!Boolean(config.undo?.enabled)
|
||||
)
|
||||
}
|
||||
name="undo"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="inherit">
|
||||
User can undo
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Runs a new script
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
style={{ marginLeft: -11 }}
|
||||
/>
|
||||
|
||||
<Grid item xs={12} sm={true}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField
|
||||
id="customIcons.redo"
|
||||
value={_get(config, "customIcons.redo")}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.redo")(e.target.value)
|
||||
}
|
||||
label="Redo:"
|
||||
className="labelHorizontal"
|
||||
inputProps={{ style: { width: "3ch" } }}
|
||||
/>
|
||||
<Fab size="small" aria-label="Preview of redo button">
|
||||
{_get(config, "customIcons.redo") || <RedoIcon />}
|
||||
</Fab>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={true}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField
|
||||
id="customIcons.undo"
|
||||
value={_get(config, "customIcons.undo")}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.undo")(e.target.value)
|
||||
}
|
||||
label="Undo:"
|
||||
className="labelHorizontal"
|
||||
inputProps={{ style: { width: "3ch" } }}
|
||||
/>
|
||||
<Fab size="small" aria-label="Preview of undo button">
|
||||
{_get(config, "customIcons.undo") || <UndoIcon />}
|
||||
</Fab>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</StepContent>
|
||||
</Step>
|
||||
|
||||
{config.isActionScript && _get(config, "undo.enabled") && (
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep("undo")}>
|
||||
Undo action
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
|
||||
<StepContent>
|
||||
<Stack spacing={3}>
|
||||
{(showConfirmationField ||
|
||||
!config.friction ||
|
||||
config.friction === "none") && (
|
||||
<TextField
|
||||
id="undo.confirmation"
|
||||
label="Undo confirmation template"
|
||||
placeholder="Are you sure you want to sell your stocks in {{stockName}}?"
|
||||
value={_get(config, "undo.confirmation")}
|
||||
onChange={(e) => {
|
||||
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 && "."}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<InputLabel variant="filled">Undo script</InputLabel>
|
||||
<Suspense fallback={<FieldSkeleton height={300} />}>
|
||||
<CodeEditor
|
||||
value={_get(config, "undo.script")}
|
||||
onChange={onChange("undo.script")}
|
||||
extraLibs={scriptExtraLibs}
|
||||
/>
|
||||
</Suspense>
|
||||
<CodeEditorHelper
|
||||
docLink={WIKI_LINKS.fieldTypesAction + "#script"}
|
||||
additionalVariables={[]}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</StepContent>
|
||||
</Step>
|
||||
)}
|
||||
|
||||
<Step>
|
||||
<StepButton onClick={() => setActiveStep("customization")}>
|
||||
Customization
|
||||
<ExpandIcon />
|
||||
</StepButton>
|
||||
|
||||
<StepContent>
|
||||
{/* <Stack spacing={3}> */}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={config.customIcons?.enabled}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.enabled")(e.target.checked)
|
||||
}
|
||||
name="customIcons.enabled"
|
||||
/>
|
||||
}
|
||||
label="Customize button icons with emoji"
|
||||
style={{ marginLeft: -11 }}
|
||||
/>
|
||||
|
||||
{config.customIcons?.enabled && (
|
||||
<Grid container spacing={2} sx={{ mt: { xs: 0, sm: -1 } }}>
|
||||
<Grid item xs={12} sm={true}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField
|
||||
id="customIcons.run"
|
||||
value={_get(config, "customIcons.run")}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.run")(e.target.value)
|
||||
}
|
||||
label="Run:"
|
||||
className="labelHorizontal"
|
||||
inputProps={{ style: { width: "3ch" } }}
|
||||
/>
|
||||
<Fab size="small" aria-label="Preview of run button">
|
||||
{_get(config, "customIcons.run") || <RunIcon />}
|
||||
</Fab>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={true}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField
|
||||
id="customIcons.redo"
|
||||
value={_get(config, "customIcons.redo")}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.redo")(e.target.value)
|
||||
}
|
||||
label="Redo:"
|
||||
className="labelHorizontal"
|
||||
inputProps={{ style: { width: "3ch" } }}
|
||||
/>
|
||||
<Fab size="small" aria-label="Preview of redo button">
|
||||
{_get(config, "customIcons.redo") || <RedoIcon />}
|
||||
</Fab>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={true}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField
|
||||
id="customIcons.undo"
|
||||
value={_get(config, "customIcons.undo")}
|
||||
onChange={(e) =>
|
||||
onChange("customIcons.undo")(e.target.value)
|
||||
}
|
||||
label="Undo:"
|
||||
className="labelHorizontal"
|
||||
inputProps={{ style: { width: "3ch" } }}
|
||||
/>
|
||||
<Fab size="small" aria-label="Preview of undo button">
|
||||
{_get(config, "customIcons.undo") || <UndoIcon />}
|
||||
</Fab>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{/* </Stack> */}
|
||||
|
||||
{/* <Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<MultiSelect
|
||||
label="Required roles"
|
||||
options={roles ?? []}
|
||||
value={config.requiredRoles ?? []}
|
||||
onChange={onChange("requiredRoles")}
|
||||
TextFieldProps={{
|
||||
id: "requiredRoles",
|
||||
helperText:
|
||||
"The user must have at least one of these roles to run the script",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<MultiSelect
|
||||
label="Required fields"
|
||||
options={columnOptions}
|
||||
value={config.requiredFields ?? []}
|
||||
onChange={onChange("requiredFields")}
|
||||
TextFieldProps={{
|
||||
id: "requiredFields",
|
||||
helperText:
|
||||
"All the selected fields must have a value for the script to run",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid> */}
|
||||
</StepContent>
|
||||
</Step>
|
||||
</Stepper>
|
||||
</>
|
||||
),
|
||||
},
|
||||
].filter(Boolean)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default Settings;
|
||||
|
||||
Reference in New Issue
Block a user