/** * 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, useRef } from 'react'; import { observer } from 'mobx-react-lite'; import { Dialog, type SelectComponent, CustomSelectorInput, createFilter, compareLabelFn, clsx, PanelDivider, } from '@finos/legend-art'; import { type MappingElement, createClassMapping, createEnumerationMapping, getAllMappingElements, MappingEditorState, BASIC_SET_IMPLEMENTATION_TYPE, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingEditorState.js'; import { guaranteeNonNullable, UnsupportedOperationError, } from '@finos/legend-shared'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { type PackageableElement, fromElementPathToMappingElementId, Class, Enumeration, Association, PrimitiveType, } from '@finos/legend-graph'; import { buildElementOption, getPackageableElementOptionFormatter, type PackageableElementOption, } from '@finos/legend-lego/graph-editor'; interface ClassMappingSubTypeOption { label: string; value: BASIC_SET_IMPLEMENTATION_TYPE; } export const NewMappingElementModal = observer(() => { const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState); const spec = mappingEditorState.newMappingElementSpec; // ID const mappingIdInputRef = useRef(null); const [id, setId] = useState(''); const handleIdChange: React.ChangeEventHandler = (event) => setId(event.target.value); const mapping = mappingEditorState.mapping; const mappingIds = getAllMappingElements(mapping).map( (mappingElement) => mappingElement.id.value, ); const isMappingIdUnique = !mappingIds.includes(id); const showId = spec?.target && mappingIds.includes(fromElementPathToMappingElementId(spec.target.path)); // Target const targetSelectorRef = useRef(null); const options: PackageableElementOption[] = [ ...editorStore.graphManagerState.usableEnumerations.map(buildElementOption), ...editorStore.graphManagerState.usableAssociations.map(buildElementOption), ...editorStore.graphManagerState.usableClasses.map(buildElementOption), ].sort(compareLabelFn); const filterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option: { data: PackageableElementOption; }): string => option.data.value.path, }); const selectedOption = spec?.target ? { label: spec.target.name, value: spec.target, } : null; const handleTargetChange = ( val: PackageableElementOption | null, ): void => { mappingEditorState.createMappingElement({ target: val?.value, showTarget: true, openInAdjacentTab: Boolean(spec?.openInAdjacentTab), }); const suggestedId = val?.value ? fromElementPathToMappingElementId(val.value.path) : ''; setId(suggestedId && !mappingIds.includes(suggestedId) ? suggestedId : ''); }; // Class Mapping Type const classMappingTypeSelectorRef = useRef(null); const classMappingTypeOptions = [ { value: BASIC_SET_IMPLEMENTATION_TYPE.INSTANCE, label: 'Instance' }, { value: BASIC_SET_IMPLEMENTATION_TYPE.OPERATION, label: 'Operation' }, ]; const initialClassMappingType = guaranteeNonNullable( classMappingTypeOptions[0], ); const [classMappingType, setClassMappingType] = useState(initialClassMappingType); const changeClassMappingType = (val: ClassMappingSubTypeOption): void => setClassMappingType(val); // Dialog actions const handleClose = (): void => mappingEditorState.setNewMappingElementSpec(undefined); const handleEnter = (): void => { if (spec) { const suggestedId = spec.target ? fromElementPathToMappingElementId(spec.target.path) : ''; setId( suggestedId && !mappingIds.includes(suggestedId) ? suggestedId : '', ); setClassMappingType(initialClassMappingType); // Set the focus properly depending on which input elements are shown if (spec.showTarget) { targetSelectorRef.current?.focus(); } else { if (showId) { mappingIdInputRef.current?.focus(); } else if (spec.target instanceof Class) { classMappingTypeSelectorRef.current?.focus(); } } } }; // Submit button const disableCreateButton = !spec?.target || !isMappingIdUnique || !id || (spec.target instanceof Class && !classMappingType); const handleSubmit = (): void => { if (spec) { if (id && spec.target) { let newMappingElement: MappingElement | undefined = undefined; if (spec.target instanceof Class) { if (classMappingType?.value) { newMappingElement = createClassMapping( mapping, id, spec.target, classMappingType.value, editorStore, ); } } else if (spec.target instanceof Enumeration) { newMappingElement = createEnumerationMapping( mapping, id, spec.target, PrimitiveType.STRING, ); } else if (spec.target instanceof Association) { throw new UnsupportedOperationError(); } if (spec.postSubmitAction) { spec.postSubmitAction(newMappingElement); } if (newMappingElement) { mappingEditorState.openMappingElement( newMappingElement, spec.openInAdjacentTab, ); } } handleClose(); } }; // Title const titleText = spec ? spec.showTarget ? 'Create New Mapping Element' : `Create New ${ spec.target instanceof Class ? 'Class' : spec.target instanceof Enumeration ? 'Enumeration' : 'Association' } Mapping ${spec.target ? `for ${spec.target.name}` : ''}` : undefined; return ( {spec && (
{ event.preventDefault(); handleSubmit(); }} className={clsx('modal search-modal new-mapping-element-modal', { 'modal--dark': true, })} > {titleText &&
{titleText}
} {spec.showTarget && ( )} {showId && (
Default ID could not be used, please specify one:
)} {spec.target instanceof Class && ( <> )}
)} {!spec &&
}
); });