UNPKG

@finos/legend-studio

Version:
278 lines 23.2 kB
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