UNPKG

@finos/legend-studio

Version:
188 lines 15.9 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, useCallback } from 'react'; import { observer } from 'mobx-react-lite'; import { UMLEditorState, UML_EDITOR_TAB, } from '../../../../stores/editor-state/element-editor-state/UMLEditorState.js'; import { CORE_DND_TYPE, } from '../../../../stores/shared/DnDUtil.js'; import { useDrop } from 'react-dnd'; import { clsx, CustomSelectorInput, createFilter, ResizablePanel, ResizablePanelSplitter, ResizablePanelGroup, ResizablePanelSplitterLine, BlankPanelContent, getControlledResizablePanelProps, InputWithInlineValidation, LockIcon, PlusIcon, ArrowCircleRightIcon, LongArrowRightIcon, } from '@finos/legend-art'; import { getElementIcon } from '../../../shared/ElementIconUtils.js'; import { prettyCONSTName, guaranteeType } from '@finos/legend-shared'; import { LEGEND_STUDIO_TEST_ID } from '../../../LegendStudioTestID.js'; import { StereotypeSelector } from './StereotypeSelector.js'; import { TaggedValueEditor } from './TaggedValueEditor.js'; import { PropertyEditor } from './PropertyEditor.js'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { MULTIPLICITY_INFINITE, Profile, Multiplicity, Class, PrimitiveType, Unit, StereotypeExplicitReference, stub_Profile, stub_TaggedValue, stub_Tag, stub_Stereotype, getFirstAssociatedProperty, getSecondAssociatedProperty, getOtherAssociatedProperty, } from '@finos/legend-graph'; import { property_setName, property_setMultiplicity, annotatedElement_deleteStereotype, annotatedElement_addTaggedValue, annotatedElement_addStereotype, annotatedElement_deleteTaggedValue, association_changePropertyType, } from '../../../../stores/graphModifier/DomainGraphModifierHelper.js'; import { CLASS_PROPERTY_TYPE, getClassPropertyType, } from '../../../../stores/shared/ModelUtil.js'; import { useApplicationNavigationContext, } from '@finos/legend-application'; import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../stores/LegendStudioApplicationNavigationContext.js'; const AssociationPropertyBasicEditor = observer((props) => { const { association, property, selectProperty, isReadOnly } = props; const editorStore = useEditorStore(); const isPropertyDuplicated = (val) => { if (association.properties[0].name === val.name && association.properties[1].name === val.name) { return true; } return false; }; // Name const changeValue = (event) => { property_setName(property, event.target.value); }; // Generic Type const [isEditingType, setIsEditingType] = useState(false); // TODO: make this so that association can only refer to classes from the same graph space const propertyTypeOptions = editorStore.classOptions.filter((classOption) => classOption.value !== getOtherAssociatedProperty(association, property).genericType.value .rawType); const propertyType = property.genericType.value.rawType; const propertyTypeName = getClassPropertyType(propertyType); const filterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option) => option.value.path, }); const selectedPropertyType = { value: propertyType, label: propertyType.name, }; const changePropertyType = (val) => { association_changePropertyType(association, property, guaranteeType(val.value, Class, `Association property type can only be 'class'`)); setIsEditingType(false); }; // Multiplicity const [lowerBound, setLowerBound] = useState(property.multiplicity.lowerBound); const [upperBound, setUpperBound] = useState(property.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))) { property_setMultiplicity(property, 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); }; // Other const openElement = () => { if (!(propertyType instanceof PrimitiveType)) { editorStore.openElement(propertyType instanceof Unit ? propertyType.measure : propertyType); } }; return (_jsxs("div", { className: "property-basic-editor", children: [_jsx("div", { className: "input-group__input property-basic-editor__input", children: _jsx(InputWithInlineValidation, { className: "input-group__input property-basic-editor__input--with-validation", disabled: isReadOnly, value: property.name, spellCheck: false, onChange: changeValue, placeholder: `Property name`, validationErrorMessage: isPropertyDuplicated(property) ? 'Duplicated property' : undefined }) }), !isReadOnly && isEditingType && (_jsx(CustomSelectorInput, { className: "property-basic-editor__type", options: propertyTypeOptions, onChange: changePropertyType, value: selectedPropertyType, 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--${propertyTypeName.toLowerCase()}`, { 'property-basic-editor__type--has-visit-btn': propertyTypeName !== CLASS_PROPERTY_TYPE.PRIMITIVE, }), children: [propertyTypeName !== CLASS_PROPERTY_TYPE.PRIMITIVE && (_jsx("div", { className: "property-basic-editor__type__abbr", children: getElementIcon(editorStore, propertyType) })), _jsx("div", { className: "property-basic-editor__type__label", children: propertyType.name }), _jsx("div", { className: "property-basic-editor__type__label property-basic-editor__type__label--hover", onClick: () => setIsEditingType(true), children: "Click to edit" }), propertyTypeName !== CLASS_PROPERTY_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--${propertyTypeName.toLowerCase()}`, { 'property-basic-editor__type--has-visit-btn': propertyTypeName !== CLASS_PROPERTY_TYPE.PRIMITIVE, }), children: [propertyTypeName !== CLASS_PROPERTY_TYPE.PRIMITIVE && (_jsx("div", { className: "property-basic-editor__type__abbr", children: getElementIcon(editorStore, propertyType) })), _jsx("div", { className: "property-basic-editor__type__label", children: propertyType.name }), propertyTypeName !== CLASS_PROPERTY_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__basic__detail-btn", onClick: selectProperty, tabIndex: -1, title: 'See detail', children: _jsx(LongArrowRightIcon, {}) })] })); }); export const AssociationEditor = observer((props) => { const { association } = props; const editorStore = useEditorStore(); const editorState = editorStore.getCurrentEditorState(UMLEditorState); const isReadOnly = editorState.isReadOnly; // Selected property const [selectedProperty, setSelectedProperty] = useState(); const selectProperty = (e) => () => setSelectedProperty(e); // Tab const selectedTab = editorState.selectedTab; const tabs = [ UML_EDITOR_TAB.PROPERTIES, UML_EDITOR_TAB.TAGGED_VALUES, UML_EDITOR_TAB.STEREOTYPES, ]; const changeTab = (tab) => () => { editorState.setSelectedTab(tab); setSelectedProperty(undefined); }; let addButtonTitle = ''; switch (selectedTab) { case UML_EDITOR_TAB.TAGGED_VALUES: addButtonTitle = 'Add tagged value'; break; case UML_EDITOR_TAB.STEREOTYPES: addButtonTitle = 'Add stereotype'; break; default: break; } const add = () => { if (!isReadOnly) { if (selectedTab === UML_EDITOR_TAB.TAGGED_VALUES) { annotatedElement_addTaggedValue(association, stub_TaggedValue(stub_Tag(stub_Profile()))); } else if (selectedTab === UML_EDITOR_TAB.STEREOTYPES) { annotatedElement_addStereotype(association, StereotypeExplicitReference.create(stub_Stereotype(stub_Profile()))); } } }; // Tagged value and Stereotype const _deleteStereotype = (val) => () => annotatedElement_deleteStereotype(association, val); const _deleteTaggedValue = (val) => () => annotatedElement_deleteTaggedValue(association, val); // Property const deselectProperty = () => setSelectedProperty(undefined); // Drag and Drop const handleDropTaggedValue = useCallback((item) => { if (!isReadOnly && item.data.packageableElement instanceof Profile) { annotatedElement_addTaggedValue(association, stub_TaggedValue(stub_Tag(item.data.packageableElement))); } }, [association, 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(association, StereotypeExplicitReference.create(stub_Stereotype(item.data.packageableElement))); } }, [association, isReadOnly]); const [{ isStereotypeDragOver }, dropStereotypeRef] = useDrop(() => ({ accept: [CORE_DND_TYPE.PROJECT_EXPLORER_PROFILE], drop: (item) => handleDropStereotype(item), collect: (monitor) => ({ isStereotypeDragOver: monitor.isOver({ shallow: true }), }), }), [handleDropStereotype]); useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.ASSOCIATION_EDITOR); return (_jsx("div", { "data-testid": LEGEND_STUDIO_TEST_ID.ASSOCIATION_EDITOR, className: "uml-element-editor association-editor", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { minSize: 56, 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: "association" }), _jsx("div", { className: "panel__header__title__content", children: association.name })] }) }), _jsxs("div", { className: "panel__header uml-element-editor__tabs__header", children: [_jsx("div", { className: "uml-element-editor__tabs", children: tabs.map((tab) => (_jsx("div", { onClick: changeTab(tab), className: clsx('uml-element-editor__tab', { 'uml-element-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 === UML_EDITOR_TAB.PROPERTIES, onClick: add, tabIndex: -1, title: addButtonTitle, children: _jsx(PlusIcon, {}) }) })] }), _jsxs("div", { className: clsx('panel__content', { 'panel__content--with-backdrop-element': selectedTab === UML_EDITOR_TAB.DERIVED_PROPERTIES || selectedTab === UML_EDITOR_TAB.CONSTRAINTS, }), children: [selectedTab === UML_EDITOR_TAB.PROPERTIES && (_jsxs("div", { className: "panel__content__lists", children: [_jsx(AssociationPropertyBasicEditor, { association: association, property: getFirstAssociatedProperty(association), selectProperty: selectProperty(getFirstAssociatedProperty(association)), isReadOnly: isReadOnly }), _jsx(AssociationPropertyBasicEditor, { association: association, property: getSecondAssociatedProperty(association), selectProperty: selectProperty(getSecondAssociatedProperty(association)), isReadOnly: isReadOnly })] })), selectedTab === UML_EDITOR_TAB.TAGGED_VALUES && (_jsx("div", { ref: dropTaggedValueRef, className: clsx('panel__content__lists', { 'panel__content__lists--dnd-over': isTaggedValueDragOver && !isReadOnly, }), children: association.taggedValues.map((taggedValue) => (_jsx(TaggedValueEditor, { taggedValue: taggedValue, deleteValue: _deleteTaggedValue(taggedValue), isReadOnly: isReadOnly }, taggedValue._UUID))) })), selectedTab === UML_EDITOR_TAB.STEREOTYPES && (_jsx("div", { ref: dropStereotypeRef, className: clsx('panel__content__lists', { 'panel__content__lists--dnd-over': isStereotypeDragOver && !isReadOnly, }), children: association.stereotypes.map((stereotype) => (_jsx(StereotypeSelector, { stereotype: stereotype, deleteStereotype: _deleteStereotype(stereotype), isReadOnly: isReadOnly }, stereotype.value._UUID))) }))] })] }) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-light-grey-200)" }) }), _jsx(ResizablePanel, { ...getControlledResizablePanelProps(!selectedProperty, { size: 250, }), direction: -1, children: selectedProperty ? (_jsx(PropertyEditor, { property: selectedProperty, deselectProperty: deselectProperty, isReadOnly: isReadOnly })) : (_jsx("div", { className: "uml-element-editor__sub-editor", children: _jsx(BlankPanelContent, { children: "No property selected" }) })) })] }) })); }); //# sourceMappingURL=AssociationEditor.js.map