libs/designer-ui/src/lib/editor/initializevariable/variableEditor.tsx (315 lines of code) (raw):
import { Label } from '../../label';
import { Combobox, DropdownEditor, getVariableType, StringEditor, TrafficLightDot, useId } from '../..';
import type { DropdownItem } from '../../dropdown';
import type { BaseEditorProps, ChangeState } from '../base';
import { Button, Tooltip } from '@fluentui/react-components';
import { useIntl } from 'react-intl';
import {
bundleIcon,
ChevronDown24Filled,
ChevronDown24Regular,
ChevronRight24Filled,
ChevronRight24Regular,
Delete24Filled,
Delete24Regular,
Edit24Filled,
Edit24Regular,
} from '@fluentui/react-icons';
import { useState } from 'react';
import { createEmptyLiteralValueSegment, isSingleLiteralValueSegment } from '../base/utils/helper';
import { guid, RUN_AFTER_COLORS } from '@microsoft/logic-apps-shared';
import constants, { VARIABLE_TYPE } from '../../constants';
import { isEmptySegments } from '../base/utils/parsesegments';
import { useTheme } from '@fluentui/react';
import type { InitializeVariableProps } from './';
const DeleteIcon = bundleIcon(Delete24Filled, Delete24Regular);
const EditIcon = bundleIcon(Edit24Filled, Edit24Regular);
const ExpandIcon = bundleIcon(ChevronRight24Filled, ChevronRight24Regular);
const CollapseIcon = bundleIcon(ChevronDown24Filled, ChevronDown24Regular);
const variableOptions: DropdownItem[] = [
{ key: VARIABLE_TYPE.BOOLEAN, value: VARIABLE_TYPE.BOOLEAN, displayName: 'Boolean' },
{ key: VARIABLE_TYPE.INTEGER, value: VARIABLE_TYPE.INTEGER, displayName: 'Integer' },
{ key: VARIABLE_TYPE.FLOAT, value: VARIABLE_TYPE.FLOAT, displayName: 'Float' },
{ key: VARIABLE_TYPE.STRING, value: VARIABLE_TYPE.STRING, displayName: 'String' },
{ key: VARIABLE_TYPE.OBJECT, value: VARIABLE_TYPE.OBJECT, displayName: 'Object' },
{ key: VARIABLE_TYPE.ARRAY, value: VARIABLE_TYPE.ARRAY, displayName: 'Array' },
];
const agentParameterOptions: DropdownItem[] = [
{ key: VARIABLE_TYPE.STRING, value: VARIABLE_TYPE.STRING, displayName: 'String' },
{ key: VARIABLE_TYPE.INTEGER, value: VARIABLE_TYPE.INTEGER, displayName: 'Integer' },
{ key: VARIABLE_TYPE.NUMBER, value: VARIABLE_TYPE.NUMBER, displayName: 'Float (Number)' },
{ key: VARIABLE_TYPE.BOOLEAN, value: VARIABLE_TYPE.BOOLEAN, displayName: 'Boolean' },
];
export const VARIABLE_PROPERTIES = {
NAME: 'name',
TYPE: 'type',
VALUE: 'value',
DESCRIPTION: 'description',
};
export interface InitializeVariableErrors {
[key: string]: string;
}
interface VariableEditorProps extends Partial<BaseEditorProps> {
index: number;
variable: InitializeVariableProps;
disableDelete: boolean;
errors?: InitializeVariableErrors;
onDelete: () => void;
onVariableChange: (value: InitializeVariableProps) => void;
isMultiVariableEnabled?: boolean;
isAgentParameter?: boolean;
}
const FieldEditor = ({
label,
id,
index,
isRequired,
editor: EditorComponent,
editorProps,
errorMessage,
}: {
label: string;
id: string;
index: number;
isRequired: boolean;
editor: React.ElementType;
editorProps: Record<string, any>;
errorMessage?: string;
}) => (
<div className="msla-input-parameter-field">
<div className="msla-input-parameter-label">
<Label id={id} isRequiredField={isRequired} text={label} />
</div>
<EditorComponent {...editorProps} labelId={`${label} - ${index}`} />
{errorMessage ? <div className="msla-input-parameter-error">{errorMessage}</div> : null}
</div>
);
export const VariableEditor = ({
variable,
onDelete,
disableDelete,
onVariableChange,
errors,
index,
isMultiVariableEnabled,
isAgentParameter,
...baseEditorProps
}: VariableEditorProps) => {
const intl = useIntl();
const { isInverted } = useTheme();
const themeName = isInverted ? 'dark' : 'light';
const [expanded, setExpanded] = useState(!isAgentParameter);
const [variableId, setVariableId] = useState<string>(guid());
const handleToggleExpand = (): void => {
setExpanded(!expanded);
};
const deleteButtonTitle = intl.formatMessage({
defaultMessage: 'Delete',
id: 'JErLDT',
description: 'Delete label',
});
const deleteButtonDisabledVariableTitle = intl.formatMessage({
defaultMessage: 'Cannot delete the last variable',
id: 'YL00wK',
description: 'Delete label',
});
const deleteButtonDisabledAgentParameter = intl.formatMessage({
defaultMessage: "Can't delete the last agent parameter.",
id: 'zOq84J',
description: 'Delete agent last parameter label',
});
const editButtonTitle = intl.formatMessage({
defaultMessage: 'Edit',
id: 'crclvu',
description: 'Edit label',
});
const newAgentParameterName = intl.formatMessage({
defaultMessage: 'New agent parameter',
id: 'qkDzwI',
description: 'Heading title for an unnamed agent parameter',
});
const newVariableName = intl.formatMessage({
defaultMessage: 'New Variable',
id: 'TyFREt',
description: 'Heading Title for a Variable Without Name',
});
const nameVariablePlaceHolder = intl.formatMessage({
defaultMessage: 'Enter variable name',
id: 'QKC8fv',
description: 'Placeholder for variable name',
});
const nameAgentParameterPlaceHolder = intl.formatMessage({
defaultMessage: 'Enter the agent parameter name',
id: 'umS0FT',
description: 'Placeholder for parameter name',
});
const typeVariablePlaceHolder = intl.formatMessage({
defaultMessage: 'Select variable type',
id: 'Xrd4VK',
description: 'Placeholder for variable type',
});
const typeAgentParameterPlaceholder = intl.formatMessage({
defaultMessage: 'Select the agent parameter type',
id: 'vp016T',
description: 'Placeholder for the agent parameter type',
});
const valuePlaceHolder = intl.formatMessage({
defaultMessage: 'Enter initial value',
id: 'RlOSrx',
description: 'Placeholder for initial value',
});
const descriptionPlaceHolder = intl.formatMessage({
defaultMessage: 'Enter description',
id: '0ws70s',
description: 'Placeholder for description',
});
const handleBlur = (newState: ChangeState, property: string): void => {
const newVariable = {
...variable,
[property]: isEmptySegments(newState.value) ? [createEmptyLiteralValueSegment()] : newState.value,
};
onVariableChange(newVariable);
};
const { name, type, value, description } = variable;
const valueOrDescription = isAgentParameter ? VARIABLE_PROPERTIES.DESCRIPTION : VARIABLE_PROPERTIES.VALUE;
const displayName =
isSingleLiteralValueSegment(name) && name[0]?.value ? name[0]?.value : isAgentParameter ? newAgentParameterName : newVariableName;
const isBooleanType = type[0]?.value === VARIABLE_TYPE.BOOLEAN;
const variableType = getVariableType(type);
const fields = [
{
label: VARIABLE_PROPERTIES.NAME,
id: useId(VARIABLE_PROPERTIES.NAME),
isRequired: true,
editor: StringEditor,
editorProps: {
...baseEditorProps,
key: `${VARIABLE_PROPERTIES.NAME}-${variableId}`,
className: 'msla-setting-token-editor-container',
initialValue: name,
editorBlur: (newState: ChangeState) => handleBlur(newState, VARIABLE_PROPERTIES.NAME),
basePlugins: { ...baseEditorProps.basePlugins, tokens: false },
placeholder: isAgentParameter ? nameAgentParameterPlaceHolder : nameVariablePlaceHolder,
dataAutomationId: `${baseEditorProps.dataAutomationId}-${VARIABLE_PROPERTIES.NAME}-${index}`,
},
errorMessage: errors?.[VARIABLE_PROPERTIES.NAME],
},
{
label: VARIABLE_PROPERTIES.TYPE,
id: useId(VARIABLE_PROPERTIES.TYPE),
isRequired: true,
editor: DropdownEditor,
editorProps: {
...baseEditorProps,
key: `${VARIABLE_PROPERTIES.TYPE}-${variableId}`,
initialValue: type,
options: isAgentParameter ? agentParameterOptions : variableOptions,
onChange: (newState: ChangeState) => handleBlur(newState, VARIABLE_PROPERTIES.TYPE),
placeholder: isAgentParameter ? typeAgentParameterPlaceholder : typeVariablePlaceHolder,
dataAutomationId: `${baseEditorProps.dataAutomationId}-${VARIABLE_PROPERTIES.TYPE}-${index}`,
},
errorMessage: errors?.[VARIABLE_PROPERTIES.TYPE],
},
{
label: valueOrDescription,
id: useId(valueOrDescription),
isRequired: false,
editor: isBooleanType && !isAgentParameter ? Combobox : StringEditor,
editorProps: {
...baseEditorProps,
key: `${valueOrDescription}-${variableId}`,
className: 'msla-setting-token-editor-container',
initialValue: isAgentParameter ? (description ?? []) : value,
valueType: isAgentParameter ? constants.SWAGGER.TYPE.STRING : variableType,
editorBlur: (newState: ChangeState) => handleBlur(newState, valueOrDescription),
options:
isBooleanType && !isAgentParameter
? [
{ key: 'true', displayName: 'true', value: true },
{ key: 'false', displayName: 'false', value: false },
]
: undefined,
onChange: isBooleanType ? (newState: ChangeState) => handleBlur(newState, valueOrDescription) : undefined,
placeholder: isAgentParameter ? descriptionPlaceHolder : valuePlaceHolder,
dataAutomationId: `${baseEditorProps.dataAutomationId}-${valueOrDescription}-${index}`,
},
errorMessage: errors?.[valueOrDescription],
},
];
const handleDelete = () => {
setVariableId(guid());
onDelete();
};
return (
<div className={'msla-editor-initialize-variable'}>
<div className={'msla-variable-editor-heading'}>
<Button
appearance="subtle"
className="msla-variable-editor-heading-button"
onClick={handleToggleExpand}
icon={expanded ? <CollapseIcon /> : <ExpandIcon />}
aria-expanded={expanded}
style={{ justifyContent: 'flex-start', width: '90%' }}
>
{displayName}
{Object.values(errors ?? {}).filter((x) => !!x).length > 0 ? (
<span className="msla-initialize-variable-error-dot">
<TrafficLightDot fill={RUN_AFTER_COLORS[themeName]['FAILED']} />
</span>
) : null}
</Button>
{!isMultiVariableEnabled && !isAgentParameter ? null : (
<>
<div className={'msla-variable-editor-edit-or-delete-button'}>
<Tooltip relationship="label" content={editButtonTitle}>
<Button
appearance="subtle"
aria-label={editButtonTitle}
onClick={handleToggleExpand}
icon={<EditIcon />}
disabled={baseEditorProps?.readonly}
style={{ color: 'var(--colorBrandForeground1)' }}
/>
</Tooltip>
</div>
<div className={'msla-variable-editor-edit-or-delete-button'}>
<Tooltip
relationship="label"
content={
disableDelete
? isAgentParameter
? deleteButtonDisabledAgentParameter
: deleteButtonDisabledVariableTitle
: deleteButtonTitle
}
>
<Button
appearance="subtle"
aria-label={deleteButtonTitle}
onClick={handleDelete}
icon={<DeleteIcon />}
disabled={disableDelete || baseEditorProps?.readonly}
style={{ color: 'var(--colorBrandForeground1)' }}
/>
</Tooltip>
</div>
</>
)}
</div>
{expanded ? (
<div className="msla-variable-editor-content">
{fields.map(({ label, id, isRequired, editor, editorProps, errorMessage }) => (
<FieldEditor
key={id}
label={label}
id={id}
index={index}
isRequired={isRequired}
editor={editor}
editorProps={editorProps}
errorMessage={errorMessage}
/>
))}
</div>
) : null}
</div>
);
};