@finos/legend-studio
Version:
278 lines • 23.2 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState, useEffect, useCallback } from 'react';
import { observer } from 'mobx-react-lite';
import { FunctionEditorState, FUNCTION_SPEC_TAB, } from '../../../stores/editor-state/element-editor-state/FunctionEditorState.js';
import { CORE_DND_TYPE, } from '../../../stores/shared/DnDUtil.js';
import { prettyCONSTName, UnsupportedOperationError, } from '@finos/legend-shared';
import { useDrop } from 'react-dnd';
import { clsx, CustomSelectorInput, createFilter, LockIcon, PlusIcon, TimesIcon, ArrowCircleRightIcon, } from '@finos/legend-art';
import { LEGEND_STUDIO_TEST_ID } from '../../LegendStudioTestID.js';
import { StereotypeSelector } from './uml-editor/StereotypeSelector.js';
import { TaggedValueEditor } from './uml-editor/TaggedValueEditor.js';
import { flowResult } from 'mobx';
import { useEditorStore } from '../EditorStoreProvider.js';
import { Profile, PRIMITIVE_TYPE, MULTIPLICITY_INFINITE, Unit, Type, Multiplicity, Enumeration, Class, PrimitiveType, StereotypeExplicitReference, stub_Tag, stub_Profile, stub_TaggedValue, stub_Stereotype, stub_RawVariableExpression, } from '@finos/legend-graph';
import { useApplicationNavigationContext, useApplicationStore, } from '@finos/legend-application';
import { StudioLambdaEditor } from '../../shared/StudioLambdaEditor.js';
import { getElementIcon } from '../../shared/ElementIconUtils.js';
import { function_setReturnType, function_setReturnMultiplicity, function_addParameter, function_deleteParameter, annotatedElement_addTaggedValue, annotatedElement_addStereotype, annotatedElement_deleteStereotype, annotatedElement_deleteTaggedValue, } from '../../../stores/graphModifier/DomainGraphModifierHelper.js';
import { rawVariableExpression_setMultiplicity, rawVariableExpression_setName, rawVariableExpression_setType, } from '../../../stores/graphModifier/ValueSpecificationGraphModifierHelper.js';
import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../stores/LegendStudioApplicationNavigationContext.js';
var FUNCTION_PARAMETER_TYPE;
(function (FUNCTION_PARAMETER_TYPE) {
FUNCTION_PARAMETER_TYPE["CLASS"] = "CLASS";
FUNCTION_PARAMETER_TYPE["ENUMERATION"] = "ENUMERATION";
FUNCTION_PARAMETER_TYPE["PRIMITIVE"] = "PRIMITIVE";
})(FUNCTION_PARAMETER_TYPE || (FUNCTION_PARAMETER_TYPE = {}));
export const getFunctionParameterType = (type) => {
if (type instanceof PrimitiveType) {
return FUNCTION_PARAMETER_TYPE.PRIMITIVE;
}
else if (type instanceof Enumeration) {
return FUNCTION_PARAMETER_TYPE.ENUMERATION;
}
else if (type instanceof Class) {
return FUNCTION_PARAMETER_TYPE.CLASS;
}
throw new UnsupportedOperationError(`Can't classify function parameter`, type);
};
const ParameterBasicEditor = observer((props) => {
const { parameter, deleteParameter, isReadOnly } = props;
const editorStore = useEditorStore();
// Name
const changeValue = (event) => rawVariableExpression_setName(parameter, event.target.value);
// Type
const [isEditingType, setIsEditingType] = useState(false);
const typeOptions = editorStore.classPropertyGenericTypeOptions;
const paramType = parameter.type.value;
const typeName = getFunctionParameterType(paramType);
const filterOption = createFilter({
ignoreCase: true,
ignoreAccents: false,
stringify: (option) => option.value.path,
});
const selectedType = { value: paramType, label: paramType.name };
const changeType = (val) => {
rawVariableExpression_setType(parameter, val.value);
setIsEditingType(false);
};
const openElement = () => {
if (!(paramType instanceof PrimitiveType)) {
editorStore.openElement(paramType instanceof Unit ? paramType.measure : paramType);
}
};
// Multiplicity
const [lowerBound, setLowerBound] = useState(parameter.multiplicity.lowerBound);
const [upperBound, setUpperBound] = useState(parameter.multiplicity.upperBound ?? MULTIPLICITY_INFINITE);
const updateMultiplicity = (lower, upper) => {
const lBound = typeof lower === 'number' ? lower : parseInt(lower, 10);
const uBound = upper === MULTIPLICITY_INFINITE
? undefined
: typeof upper === 'number'
? upper
: parseInt(upper, 10);
if (!isNaN(lBound) && (uBound === undefined || !isNaN(uBound))) {
rawVariableExpression_setMultiplicity(parameter, new Multiplicity(lBound, uBound));
}
};
const changeLowerBound = (event) => {
setLowerBound(event.target.value);
updateMultiplicity(event.target.value, upperBound);
};
const changeUpperBound = (event) => {
setUpperBound(event.target.value);
updateMultiplicity(lowerBound, event.target.value);
};
return (_jsxs("div", { className: "property-basic-editor", children: [isReadOnly && (_jsx("div", { className: "property-basic-editor__lock", children: _jsx(LockIcon, {}) })), _jsx("input", { className: "property-basic-editor__name", disabled: isReadOnly, value: parameter.name, spellCheck: false, onChange: changeValue, placeholder: `Property name` }), !isReadOnly && isEditingType && (_jsx(CustomSelectorInput, { className: "property-basic-editor__type", options: typeOptions, onChange: changeType, value: selectedType, placeholder: 'Choose a data type or enumeration', filterOption: filterOption })), !isReadOnly && !isEditingType && (_jsxs("div", { className: clsx('property-basic-editor__type', 'property-basic-editor__type--show-click-hint', `background--${typeName.toLowerCase()}`, {
'property-basic-editor__type--has-visit-btn': typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE,
}), children: [typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("div", { className: "property-basic-editor__type__abbr", children: getElementIcon(editorStore, paramType) })), _jsx("div", { className: "property-basic-editor__type__label", children: paramType.name }), _jsx("div", { className: "property-basic-editor__type__label property-basic-editor__type__label--hover", onClick: () => setIsEditingType(true), children: "Click to edit" }), typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("button", { "data-testid": LEGEND_STUDIO_TEST_ID.TYPE_VISIT, className: "property-basic-editor__type__visit-btn", onClick: openElement, tabIndex: -1, title: 'Visit element', children: _jsx(ArrowCircleRightIcon, {}) }))] })), isReadOnly && (_jsxs("div", { className: clsx('property-basic-editor__type', `background--${typeName.toLowerCase()}`, {
'property-basic-editor__type--has-visit-btn': typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE,
}), children: [typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("div", { className: "property-basic-editor__type__abbr", children: getElementIcon(editorStore, paramType) })), _jsx("div", { className: "property-basic-editor__type__label", children: paramType.name }), typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("button", { "data-testid": LEGEND_STUDIO_TEST_ID.TYPE_VISIT, className: "property-basic-editor__type__visit-btn", onClick: openElement, tabIndex: -1, title: 'Visit element', children: _jsx(ArrowCircleRightIcon, {}) }))] })), _jsxs("div", { className: "property-basic-editor__multiplicity", children: [_jsx("input", { className: "property-basic-editor__multiplicity-bound", disabled: isReadOnly, spellCheck: false, value: lowerBound, onChange: changeLowerBound }), _jsx("div", { className: "property-basic-editor__multiplicity__range", children: ".." }), _jsx("input", { className: "property-basic-editor__multiplicity-bound", disabled: isReadOnly, spellCheck: false, value: upperBound, onChange: changeUpperBound })] }), !isReadOnly && (_jsx("button", { className: "uml-element-editor__remove-btn", disabled: isReadOnly, onClick: deleteParameter, tabIndex: -1, title: 'Remove', children: _jsx(TimesIcon, {}) }))] }));
});
const ReturnTypeEditor = observer((props) => {
const { functionElement, isReadOnly } = props;
const { returnType, returnMultiplicity } = functionElement;
const editorStore = useEditorStore();
// Type
const [isEditingType, setIsEditingType] = useState(false);
const typeOptions = editorStore.classPropertyGenericTypeOptions;
const typeName = getFunctionParameterType(returnType.value);
const filterOption = createFilter({
ignoreCase: true,
ignoreAccents: false,
stringify: (option) => option.value.path,
});
const selectedType = { value: returnType, label: returnType.value.name };
const changeType = (val) => {
function_setReturnType(functionElement, val.value);
setIsEditingType(false);
};
const openElement = () => {
if (!(returnType.value instanceof PrimitiveType)) {
editorStore.openElement(returnType.value instanceof Unit
? returnType.value.measure
: returnType.value);
}
};
// Multiplicity
const [lowerBound, setLowerBound] = useState(returnMultiplicity.lowerBound);
const [upperBound, setUpperBound] = useState(returnMultiplicity.upperBound ?? MULTIPLICITY_INFINITE);
const updateMultiplicity = (lower, upper) => {
const lBound = typeof lower === 'number' ? lower : parseInt(lower, 10);
const uBound = upper === MULTIPLICITY_INFINITE
? undefined
: typeof upper === 'number'
? upper
: parseInt(upper, 10);
if (!isNaN(lBound) && (uBound === undefined || !isNaN(uBound))) {
function_setReturnMultiplicity(functionElement, new Multiplicity(lBound, uBound));
}
};
const changeLowerBound = (event) => {
setLowerBound(event.target.value);
updateMultiplicity(event.target.value, upperBound);
};
const changeUpperBound = (event) => {
setUpperBound(event.target.value);
updateMultiplicity(lowerBound, event.target.value);
};
return (_jsxs("div", { className: "function-editor__return__type-editor", children: [!isReadOnly && isEditingType && (_jsx(CustomSelectorInput, { className: "property-basic-editor__type", options: typeOptions, onChange: changeType, value: selectedType, placeholder: 'Choose a data type or enumeration', filterOption: filterOption })), !isReadOnly && !isEditingType && (_jsxs("div", { className: clsx('property-basic-editor__type', 'property-basic-editor__type--show-click-hint', `background--${typeName.toLowerCase()}`, {
'property-basic-editor__type--has-visit-btn': typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE,
}), children: [typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("div", { className: "property-basic-editor__type__abbr", children: getElementIcon(editorStore, returnType.value) })), _jsx("div", { className: "property-basic-editor__type__label", children: returnType.value.name }), _jsx("div", { className: "property-basic-editor__type__label property-basic-editor__type__label--hover", onClick: () => setIsEditingType(true), children: "Click to edit" }), typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("button", { "data-testid": LEGEND_STUDIO_TEST_ID.TYPE_VISIT, className: "property-basic-editor__type__visit-btn", onClick: openElement, tabIndex: -1, title: 'Visit element', children: _jsx(ArrowCircleRightIcon, {}) }))] })), isReadOnly && (_jsxs("div", { className: clsx('property-basic-editor__type', `background--${typeName.toLowerCase()}`, {
'property-basic-editor__type--has-visit-btn': typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE,
}), children: [typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("div", { className: "property-basic-editor__type__abbr", children: getElementIcon(editorStore, returnType.value) })), _jsx("div", { className: "property-basic-editor__type__label", children: returnType.value.name }), typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (_jsx("button", { "data-testid": LEGEND_STUDIO_TEST_ID.TYPE_VISIT, className: "property-basic-editor__type__visit-btn", onClick: openElement, tabIndex: -1, title: 'Visit element', children: _jsx(ArrowCircleRightIcon, {}) }))] })), _jsxs("div", { className: "property-basic-editor__multiplicity", children: [_jsx("input", { className: "property-basic-editor__multiplicity-bound", disabled: isReadOnly, spellCheck: false, value: lowerBound, onChange: changeLowerBound }), _jsx("div", { className: "property-basic-editor__multiplicity__range", children: ".." }), _jsx("input", { className: "property-basic-editor__multiplicity-bound", disabled: isReadOnly, spellCheck: false, value: upperBound, onChange: changeUpperBound })] }), _jsx("button", { className: "uml-element-editor__remove-btn", disabled: true, tabIndex: -1, children: _jsx(TimesIcon, {}) })] }));
});
export const FunctionMainEditor = observer((props) => {
const editorStore = useEditorStore();
const defaultType = editorStore.graphManagerState.graph.getPrimitiveType(PRIMITIVE_TYPE.STRING);
const { functionElement, isReadOnly, functionEditorState } = props;
const lambdaEditorState = functionEditorState.functionBodyEditorState;
// Parameters
const addParameter = () => {
function_addParameter(functionElement, stub_RawVariableExpression(defaultType));
};
const deleteParameter = (val) => () => {
function_deleteParameter(functionElement, val);
};
const handleDropParameter = useCallback((item) => {
if (!isReadOnly && item.data.packageableElement instanceof Type) {
function_addParameter(functionElement, stub_RawVariableExpression(item.data.packageableElement));
}
}, [functionElement, isReadOnly]);
const [{ isParameterDragOver }, dropParameterRef] = useDrop(() => ({
accept: [
CORE_DND_TYPE.PROJECT_EXPLORER_CLASS,
CORE_DND_TYPE.PROJECT_EXPLORER_ENUMERATION,
],
drop: (item) => handleDropParameter(item),
collect: (monitor) => ({
isParameterDragOver: monitor.isOver({ shallow: true }),
}),
}), [handleDropParameter]);
const handleDropReturnType = useCallback((item) => {
if (!isReadOnly && item.data.packageableElement instanceof Type) {
function_setReturnType(functionElement, item.data.packageableElement);
}
}, [functionElement, isReadOnly]);
const [{ isReturnTypeDragOver }, dropReturnTypeRef] = useDrop(() => ({
accept: [
CORE_DND_TYPE.PROJECT_EXPLORER_CLASS,
CORE_DND_TYPE.PROJECT_EXPLORER_ENUMERATION,
],
drop: (item) => handleDropReturnType(item),
collect: (monitor) => ({
isReturnTypeDragOver: monitor.isOver({ shallow: true }),
}),
}), [handleDropReturnType]);
return (_jsxs("div", { className: "panel__content function-editor__element", children: [_jsxs("div", { className: "function-editor__element__item", children: [_jsxs("div", { className: "function-editor__element__item__header", children: [_jsx("div", { className: "function-editor__element__item__header__title", children: "PARAMETERS" }), _jsx("button", { className: "function-editor__element__item__header__add-btn", disabled: isReadOnly, onClick: addParameter, tabIndex: -1, title: 'Add Parameter', children: _jsx(PlusIcon, {}) })] }), _jsx("div", { ref: dropParameterRef, className: clsx('function-editor__element__item__content', {
'panel__content__lists--dnd-over': isParameterDragOver && !isReadOnly,
}), children: functionElement.parameters.map((param) => (_jsx(ParameterBasicEditor, { parameter: param, deleteParameter: deleteParameter(param), isReadOnly: isReadOnly }, param._UUID))) })] }), _jsxs("div", { className: "function-editor__element__item", children: [_jsxs("div", { className: "function-editor__element__item__header", children: [_jsx("div", { className: "function-editor__element__item__header__title", children: "LAMBDA" }), _jsx("div", { ref: dropReturnTypeRef, className: clsx('function-editor__element__item__content', {
'panel__content__lists--dnd-over': isReturnTypeDragOver && !isReadOnly,
}), children: _jsx(ReturnTypeEditor, { functionElement: functionElement, isReadOnly: isReadOnly }) })] }), _jsx("div", { className: clsx('function-editor__element__item__content', {
backdrop__element: Boolean(functionEditorState.functionBodyEditorState.parserError),
}), children: _jsx(StudioLambdaEditor, { className: 'function-editor__element__lambda-editor', disabled: lambdaEditorState.isConvertingFunctionBodyToString || isReadOnly, lambdaEditorState: lambdaEditorState, expectedType: functionElement.returnType.value, forceBackdrop: false, forceExpansion: true }) })] })] }));
});
export const FunctionEditor = observer(() => {
const editorStore = useEditorStore();
const applicationStore = useApplicationStore();
const functionEditorState = editorStore.getCurrentEditorState(FunctionEditorState);
const isReadOnly = functionEditorState.isReadOnly;
const functionElement = functionEditorState.functionElement;
const selectedTab = functionEditorState.selectedTab;
let addButtonTitle = '';
switch (selectedTab) {
case FUNCTION_SPEC_TAB.TAGGED_VALUES:
addButtonTitle = 'Add stereotype';
break;
case FUNCTION_SPEC_TAB.STEREOTYPES:
addButtonTitle = 'Add tagged value';
break;
default:
break;
}
// Tagged Values and Stereotype
const add = () => {
if (!isReadOnly) {
if (selectedTab === FUNCTION_SPEC_TAB.TAGGED_VALUES) {
annotatedElement_addTaggedValue(functionElement, stub_TaggedValue(stub_Tag(stub_Profile())));
}
else if (selectedTab === FUNCTION_SPEC_TAB.STEREOTYPES) {
annotatedElement_addStereotype(functionElement, StereotypeExplicitReference.create(stub_Stereotype(stub_Profile())));
}
}
};
const handleDropTaggedValue = useCallback((item) => {
if (!isReadOnly && item.data.packageableElement instanceof Profile) {
annotatedElement_addTaggedValue(functionElement, stub_TaggedValue(stub_Tag(item.data.packageableElement)));
}
}, [functionElement, isReadOnly]);
const [{ isTaggedValueDragOver }, dropTaggedValueRef] = useDrop(() => ({
accept: [CORE_DND_TYPE.PROJECT_EXPLORER_PROFILE],
drop: (item) => handleDropTaggedValue(item),
collect: (monitor) => ({
isTaggedValueDragOver: monitor.isOver({ shallow: true }),
}),
}), [handleDropTaggedValue]);
const handleDropStereotype = useCallback((item) => {
if (!isReadOnly && item.data.packageableElement instanceof Profile) {
annotatedElement_addStereotype(functionElement, StereotypeExplicitReference.create(stub_Stereotype(item.data.packageableElement)));
}
}, [functionElement, isReadOnly]);
const [{ isStereotypeDragOver }, dropStereotypeRef] = useDrop(() => ({
accept: [CORE_DND_TYPE.PROJECT_EXPLORER_PROFILE],
drop: (item) => handleDropStereotype(item),
collect: (monitor) => ({
isStereotypeDragOver: monitor.isOver({ shallow: true }),
}),
}), [handleDropStereotype]);
const _deleteStereotype = (val) => () => annotatedElement_deleteStereotype(functionElement, val);
const _deleteTaggedValue = (val) => () => annotatedElement_deleteTaggedValue(functionElement, val);
const changeTab = (tab) => () => functionEditorState.setSelectedTab(tab);
useEffect(() => {
flowResult(functionEditorState.functionBodyEditorState.convertLambdaObjectToGrammarString(true, true)).catch(applicationStore.alertUnhandledError);
}, [applicationStore, functionEditorState]);
useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.FUNCTION_EDITOR);
return (_jsx("div", { className: "function-editor", children: _jsxs("div", { className: "panel", children: [_jsx("div", { className: "panel__header", children: _jsxs("div", { className: "panel__header__title", children: [isReadOnly && (_jsx("div", { className: "uml-element-editor__header__lock", children: _jsx(LockIcon, {}) })), _jsx("div", { className: "panel__header__title__label", children: "function" }), _jsx("div", { className: "panel__header__title__content", children: functionElement.name })] }) }), _jsxs("div", { className: "panel__header function-editor__tabs__header", children: [_jsx("div", { className: "function-editor__tabs", children: Object.values(FUNCTION_SPEC_TAB).map((tab) => (_jsx("div", { onClick: changeTab(tab), className: clsx('function-editor__tab', {
'function-editor__tab--active': tab === selectedTab,
}), children: prettyCONSTName(tab) }, tab))) }), _jsx("div", { className: "panel__header__actions", children: _jsx("button", { className: "panel__header__action", disabled: isReadOnly || selectedTab === FUNCTION_SPEC_TAB.GENERAL, onClick: add, tabIndex: -1, title: addButtonTitle, children: _jsx(PlusIcon, {}) }) })] }), selectedTab === FUNCTION_SPEC_TAB.GENERAL ? (_jsx(FunctionMainEditor, { functionEditorState: functionEditorState, functionElement: functionElement, isReadOnly: isReadOnly })) : (_jsxs("div", { className: "panel__content", children: [selectedTab === FUNCTION_SPEC_TAB.TAGGED_VALUES && (_jsx("div", { ref: dropTaggedValueRef, className: clsx('panel__content__lists', {
'panel__content__lists--dnd-over': isTaggedValueDragOver && !isReadOnly,
}), children: functionElement.taggedValues.map((taggedValue) => (_jsx(TaggedValueEditor, { taggedValue: taggedValue, deleteValue: _deleteTaggedValue(taggedValue), isReadOnly: isReadOnly }, taggedValue._UUID))) })), selectedTab === FUNCTION_SPEC_TAB.STEREOTYPES && (_jsx("div", { ref: dropStereotypeRef, className: clsx('panel__content__lists', {
'panel__content__lists--dnd-over': isStereotypeDragOver && !isReadOnly,
}), children: functionElement.stereotypes.map((stereotype) => (_jsx(StereotypeSelector, { stereotype: stereotype, deleteStereotype: _deleteStereotype(stereotype), isReadOnly: isReadOnly }, stereotype.value._UUID))) }))] }))] }) }));
});
//# sourceMappingURL=FunctionEditor.js.map