Merge pull request #456 from automatisch/issue-455
feat: make flow editor read only when published
This commit is contained in:
@@ -9,7 +9,7 @@ class Flow extends Base {
|
|||||||
id!: string;
|
id!: string;
|
||||||
name!: string;
|
name!: string;
|
||||||
userId!: string;
|
userId!: string;
|
||||||
active = false;
|
active: boolean;
|
||||||
steps?: [Step];
|
steps?: [Step];
|
||||||
published_at: string;
|
published_at: string;
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Autocomplete from '@mui/material/Autocomplete';
|
import Autocomplete from '@mui/material/Autocomplete';
|
||||||
|
|
||||||
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import { GET_APPS } from 'graphql/queries/get-apps';
|
import { GET_APPS } from 'graphql/queries/get-apps';
|
||||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||||
import type { IApp, IStep, ISubstep } from '@automatisch/types';
|
import type { IApp, IStep, ISubstep } from '@automatisch/types';
|
||||||
@@ -40,6 +41,8 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R
|
|||||||
onChange,
|
onChange,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const editorContext = React.useContext(EditorContext);
|
||||||
|
|
||||||
const isTrigger = step.type === 'trigger';
|
const isTrigger = step.type === 'trigger';
|
||||||
|
|
||||||
const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }});
|
const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }});
|
||||||
@@ -111,6 +114,7 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R
|
|||||||
fullWidth
|
fullWidth
|
||||||
disablePortal
|
disablePortal
|
||||||
disableClearable
|
disableClearable
|
||||||
|
disabled={editorContext.readOnly}
|
||||||
options={appOptions}
|
options={appOptions}
|
||||||
renderInput={(params) => <TextField {...params} label="Choose an app" />}
|
renderInput={(params) => <TextField {...params} label="Choose an app" />}
|
||||||
value={getOption(appOptions, step.appKey)}
|
value={getOption(appOptions, step.appKey)}
|
||||||
@@ -127,6 +131,7 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R
|
|||||||
fullWidth
|
fullWidth
|
||||||
disablePortal
|
disablePortal
|
||||||
disableClearable
|
disableClearable
|
||||||
|
disabled={editorContext.readOnly}
|
||||||
options={actionOptions}
|
options={actionOptions}
|
||||||
renderInput={(params) => <TextField {...params} label="Choose an event" />}
|
renderInput={(params) => <TextField {...params} label="Choose an event" />}
|
||||||
value={getOption(actionOptions, step.key)}
|
value={getOption(actionOptions, step.key)}
|
||||||
@@ -140,7 +145,7 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
disabled={!valid}
|
disabled={!valid || editorContext.readOnly}
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -6,6 +6,7 @@ import Collapse from '@mui/material/Collapse';
|
|||||||
import ListItem from '@mui/material/ListItem';
|
import ListItem from '@mui/material/ListItem';
|
||||||
import Autocomplete from '@mui/material/Autocomplete';
|
import Autocomplete from '@mui/material/Autocomplete';
|
||||||
|
|
||||||
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||||
import type { IApp, IConnection, IStep, ISubstep } from '@automatisch/types';
|
import type { IApp, IConnection, IStep, ISubstep } from '@automatisch/types';
|
||||||
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
||||||
@@ -42,6 +43,7 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
|
|||||||
connection,
|
connection,
|
||||||
appKey,
|
appKey,
|
||||||
} = step;
|
} = step;
|
||||||
|
const editorContext = React.useContext(EditorContext);
|
||||||
const { data, loading } = useQuery(GET_APP_CONNECTIONS, { variables: { key: appKey }});
|
const { data, loading } = useQuery(GET_APP_CONNECTIONS, { variables: { key: appKey }});
|
||||||
// TODO: show detailed error when connection test/verification fails
|
// TODO: show detailed error when connection test/verification fails
|
||||||
const [
|
const [
|
||||||
@@ -118,6 +120,7 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
|
|||||||
fullWidth
|
fullWidth
|
||||||
disablePortal
|
disablePortal
|
||||||
disableClearable
|
disableClearable
|
||||||
|
disabled={editorContext.readOnly}
|
||||||
options={connectionOptions}
|
options={connectionOptions}
|
||||||
renderInput={(params) => <TextField {...params} label="Choose connection" />}
|
renderInput={(params) => <TextField {...params} label="Choose connection" />}
|
||||||
value={getOption(connectionOptions, connection?.id)}
|
value={getOption(connectionOptions, connection?.id)}
|
||||||
@@ -130,7 +133,7 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
disabled={testResultLoading || !connection?.verified}
|
disabled={testResultLoading || !connection?.verified || editorContext.readOnly}
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -3,12 +3,12 @@ import { useMutation } from '@apollo/client';
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import type { IFlow } from '@automatisch/types';
|
||||||
|
|
||||||
import { GET_FLOW } from 'graphql/queries/get-flow';
|
import { GET_FLOW } from 'graphql/queries/get-flow';
|
||||||
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||||
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
||||||
import FlowStep from 'components/FlowStep';
|
import FlowStep from 'components/FlowStep';
|
||||||
import type { IFlow } from '@automatisch/types';
|
|
||||||
|
|
||||||
type EditorProps = {
|
type EditorProps = {
|
||||||
flow: IFlow;
|
flow: IFlow;
|
||||||
|
@@ -3,12 +3,13 @@ import { Link, useParams } from 'react-router-dom';
|
|||||||
import { useMutation, useQuery } from '@apollo/client';
|
import { useMutation, useQuery } from '@apollo/client';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
import Button from '@mui/material/Button';
|
||||||
import Switch from '@mui/material/Switch';
|
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||||
|
import Snackbar from '@mui/material/Snackbar';
|
||||||
|
|
||||||
|
import { EditorProvider } from 'contexts/Editor';
|
||||||
import EditableTypography from 'components/EditableTypography';
|
import EditableTypography from 'components/EditableTypography';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
import Editor from 'components/Editor';
|
import Editor from 'components/Editor';
|
||||||
@@ -45,9 +46,7 @@ export default function EditorLayout(): React.ReactElement {
|
|||||||
});
|
});
|
||||||
}, [flow?.id]);
|
}, [flow?.id]);
|
||||||
|
|
||||||
const onFlowStatusUpdate = React.useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
|
const onFlowStatusUpdate = React.useCallback(async (active: boolean) => {
|
||||||
const active = event.target.checked;
|
|
||||||
|
|
||||||
await updateFlowStatus({
|
await updateFlowStatus({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
@@ -93,22 +92,32 @@ export default function EditorLayout(): React.ReactElement {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box pr={1}>
|
<Box pr={1}>
|
||||||
<FormControlLabel
|
<Button variant="contained" size="small" onClick={() => onFlowStatusUpdate(!flow.active)}>
|
||||||
control={
|
{flow?.active ? formatMessage('flowEditor.unpublish') : formatMessage('flowEditor.publish')}
|
||||||
<Switch checked={flow?.active ?? false} onChange={onFlowStatusUpdate} />
|
</Button>
|
||||||
}
|
|
||||||
label={flow?.active ? formatMessage('flow.active') : formatMessage('flow.inactive')}
|
|
||||||
labelPlacement="start"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Container maxWidth="md">
|
<Container maxWidth="md">
|
||||||
{!flow && !loading && 'not found'}
|
<EditorProvider value={{ readOnly: !!flow?.active }}>
|
||||||
|
{!flow && !loading && 'not found'}
|
||||||
|
|
||||||
{flow && <Editor flow={flow} />}
|
{flow && <Editor flow={flow} />}
|
||||||
|
</EditorProvider>
|
||||||
</Container>
|
</Container>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
open={!!flow?.active}
|
||||||
|
message={formatMessage('flowEditor.publishFlowCannotBeUpdated')}
|
||||||
|
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
|
||||||
|
ContentProps={{ sx: { fontWeight: 300 }}}
|
||||||
|
action={(
|
||||||
|
<Button variant="contained" size="small" onClick={() => onFlowStatusUpdate(!flow.active)}>
|
||||||
|
{flow?.active ? formatMessage('flowEditor.unpublish') : formatMessage('flowEditor.publish')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import * as yup from 'yup';
|
|||||||
import type { BaseSchema } from 'yup';
|
import type { BaseSchema } from 'yup';
|
||||||
import type { IApp, IField, IStep, ISubstep } from '@automatisch/types';
|
import type { IApp, IField, IStep, ISubstep } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
||||||
import TestSubstep from 'components/TestSubstep';
|
import TestSubstep from 'components/TestSubstep';
|
||||||
import FlowSubstep from 'components/FlowSubstep';
|
import FlowSubstep from 'components/FlowSubstep';
|
||||||
@@ -99,6 +100,7 @@ export default function FlowStep(
|
|||||||
props: FlowStepProps
|
props: FlowStepProps
|
||||||
): React.ReactElement | null {
|
): React.ReactElement | null {
|
||||||
const { collapsed, onChange } = props;
|
const { collapsed, onChange } = props;
|
||||||
|
const editorContext = React.useContext(EditorContext);
|
||||||
const contextButtonRef = React.useRef<HTMLButtonElement | null>(null);
|
const contextButtonRef = React.useRef<HTMLButtonElement | null>(null);
|
||||||
const step: IStep = props.step;
|
const step: IStep = props.step;
|
||||||
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
|
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
|
||||||
@@ -204,7 +206,7 @@ export default function FlowStep(
|
|||||||
|
|
||||||
<Box display="flex" flex={1} justifyContent="end">
|
<Box display="flex" flex={1} justifyContent="end">
|
||||||
{/* as there are no other actions besides "delete step", we hide the context menu. */}
|
{/* as there are no other actions besides "delete step", we hide the context menu. */}
|
||||||
{!isTrigger && (
|
{!isTrigger && !editorContext.readOnly && (
|
||||||
<IconButton
|
<IconButton
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={onContextMenuClick}
|
onClick={onContextMenuClick}
|
||||||
|
@@ -5,6 +5,7 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||||
import InputCreator from 'components/InputCreator';
|
import InputCreator from 'components/InputCreator';
|
||||||
import type { IField, IStep, ISubstep } from '@automatisch/types';
|
import type { IField, IStep, ISubstep } from '@automatisch/types';
|
||||||
@@ -52,6 +53,7 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
|
|||||||
arguments: args,
|
arguments: args,
|
||||||
} = substep;
|
} = substep;
|
||||||
|
|
||||||
|
const editorContext = React.useContext(EditorContext);
|
||||||
const formContext = useFormContext();
|
const formContext = useFormContext();
|
||||||
const [validationStatus, setValidationStatus] = React.useState<boolean | null>(validateSubstep(substep, formContext.getValues() as IStep));
|
const [validationStatus, setValidationStatus] = React.useState<boolean | null>(validateSubstep(substep, formContext.getValues() as IStep));
|
||||||
|
|
||||||
@@ -105,6 +107,7 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
|
|||||||
schema={argument}
|
schema={argument}
|
||||||
namePrefix="parameters"
|
namePrefix="parameters"
|
||||||
stepId={step.id}
|
stepId={step.id}
|
||||||
|
disabled={editorContext.readOnly}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -114,7 +117,7 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
disabled={!validationStatus}
|
disabled={!validationStatus || editorContext.readOnly}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
|
@@ -15,6 +15,7 @@ type InputCreatorProps = {
|
|||||||
schema: IField;
|
schema: IField;
|
||||||
namePrefix?: string;
|
namePrefix?: string;
|
||||||
stepId?: string;
|
stepId?: string;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RawOption = {
|
type RawOption = {
|
||||||
@@ -33,6 +34,7 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
|
|||||||
schema,
|
schema,
|
||||||
namePrefix,
|
namePrefix,
|
||||||
stepId,
|
stepId,
|
||||||
|
disabled,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -65,6 +67,7 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
|
|||||||
onChange={console.log}
|
onChange={console.log}
|
||||||
description={description}
|
description={description}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -77,6 +80,7 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
|
|||||||
description={description}
|
description={description}
|
||||||
name={computedName}
|
name={computedName}
|
||||||
required={required}
|
required={required}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -86,8 +90,7 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
|
|||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
required={required}
|
required={required}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
disabled={readOnly}
|
readOnly={readOnly || disabled}
|
||||||
readOnly={readOnly}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
name={computedName}
|
name={computedName}
|
||||||
|
@@ -38,6 +38,7 @@ type PowerInputProps = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
docUrl?: string;
|
docUrl?: string;
|
||||||
clickToCopy?: boolean;
|
clickToCopy?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PowerInput = (props: PowerInputProps) => {
|
const PowerInput = (props: PowerInputProps) => {
|
||||||
@@ -49,6 +50,7 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
label,
|
label,
|
||||||
required,
|
required,
|
||||||
description,
|
description,
|
||||||
|
disabled,
|
||||||
} = props;
|
} = props;
|
||||||
const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
|
const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
|
||||||
const editorRef = React.useRef<HTMLDivElement | null>(null);
|
const editorRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
@@ -161,11 +163,11 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
<ClickAwayListener onClickAway={() => setSearch(null)}>
|
<ClickAwayListener onClickAway={() => setSearch(null)}>
|
||||||
{/* ref-able single child for ClickAwayListener */}
|
{/* ref-able single child for ClickAwayListener */}
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<FakeInput>
|
<FakeInput disabled={disabled}>
|
||||||
<InputLabelWrapper>
|
<InputLabelWrapper>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
shrink={true}
|
shrink={true}
|
||||||
// focused
|
disabled={disabled}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{ bgcolor: 'white', display: 'inline-block', px: .75 }}
|
sx={{ bgcolor: 'white', display: 'inline-block', px: .75 }}
|
||||||
>
|
>
|
||||||
@@ -174,6 +176,7 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
</InputLabelWrapper>
|
</InputLabelWrapper>
|
||||||
|
|
||||||
<Editable
|
<Editable
|
||||||
|
readOnly={disabled}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
renderElement={renderElement}
|
renderElement={renderElement}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
|
@@ -7,7 +7,7 @@ export const InputLabelWrapper = styled('div')`
|
|||||||
left: -6px;
|
left: -6px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FakeInput = styled('div')`
|
export const FakeInput = styled('div', { shouldForwardProp: prop => prop !== 'disabled'})<{ disabled?: boolean }>`
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -17,6 +17,11 @@ export const FakeInput = styled('div')`
|
|||||||
border-color: rgba(0, 0, 0, 0.23);
|
border-color: rgba(0, 0, 0, 0.23);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
${({ disabled, theme }) => !!disabled && `
|
||||||
|
color: ${theme.palette.action.disabled},
|
||||||
|
border-color: ${theme.palette.action.disabled},
|
||||||
|
`}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: ${({ theme }) => theme.palette.text.primary};
|
border-color: ${({ theme }) => theme.palette.text.primary};
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
|
||||||
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import JSONViewer from 'components/JSONViewer';
|
import JSONViewer from 'components/JSONViewer';
|
||||||
import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
|
import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
|
||||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||||
@@ -31,6 +32,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
|
|||||||
step,
|
step,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const editorContext = React.useContext(EditorContext);
|
||||||
const [executeFlow, { data, error, loading }] = useMutation(EXECUTE_FLOW, { context: { autoSnackbar: false }});
|
const [executeFlow, { data, error, loading }] = useMutation(EXECUTE_FLOW, { context: { autoSnackbar: false }});
|
||||||
const response = data?.executeFlow?.data;
|
const response = data?.executeFlow?.data;
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
disabled={editorContext.readOnly}
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
Test & Continue
|
Test & Continue
|
||||||
|
23
packages/web/src/contexts/Editor.tsx
Normal file
23
packages/web/src/contexts/Editor.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
interface IEditorContext {
|
||||||
|
readOnly: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditorContext = React.createContext<IEditorContext>({ readOnly: false });
|
||||||
|
|
||||||
|
type EditorProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
value: IEditorContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditorProvider = (props: EditorProviderProps): React.ReactElement => {
|
||||||
|
const { children, value } = props;
|
||||||
|
return (
|
||||||
|
<EditorContext.Provider
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</EditorContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@@ -40,6 +40,9 @@
|
|||||||
"createFlow.creating": "Creating a flow...",
|
"createFlow.creating": "Creating a flow...",
|
||||||
"flow.active": "ON",
|
"flow.active": "ON",
|
||||||
"flow.inactive": "OFF",
|
"flow.inactive": "OFF",
|
||||||
|
"flowEditor.publish": "PUBLISH",
|
||||||
|
"flowEditor.unpublish": "UNPUBLISH",
|
||||||
|
"flowEditor.publishFlowCannotBeUpdated": "To edit this flow, you must first unpublish it.",
|
||||||
"flow.createdAt": "created {datetime}",
|
"flow.createdAt": "created {datetime}",
|
||||||
"flow.updatedAt": "updated {datetime}",
|
"flow.updatedAt": "updated {datetime}",
|
||||||
"flow.view": "View",
|
"flow.view": "View",
|
||||||
|
Reference in New Issue
Block a user