UNPKG

@finos/legend-application-studio

Version:
216 lines 17.7 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, forwardRef, useRef } from 'react'; import { observer } from 'mobx-react-lite'; import { CORE_DND_TYPE, MappingElementDragSource, } from '../../../../stores/editor/utils/DnDUtils.js'; import { clsx, TreeView, ContextMenu, PlusIcon, LockIcon, FireIcon, StickArrowCircleRightIcon, ChevronRightIcon, ChevronDownIcon, FilterIcon, PanelDropZone, BlankPanelPlaceholder, MenuContent, MenuContentItem, } from '@finos/legend-art'; import { MappingElementState } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingElementState.js'; import { useDrop, useDrag } from 'react-dnd'; import { toSentenceCase } from '@finos/legend-shared'; import { getAllMappingElements, getMappingElementTarget, getMappingElementType, MappingEditorState, getMappingElementLabel, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingEditorState.js'; import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js'; import { getElementIcon } from '../../../ElementIconUtils.js'; import { NewMappingElementModal } from './NewMappingElementModal.js'; import { MappingElementDecorator } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingElementDecorator.js'; import { flowResult } from 'mobx'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { SetImplementation, EnumerationMapping, PropertyMapping, PureInstanceSetImplementation, stub_RawLambda, } from '@finos/legend-graph'; import { useApplicationStore } from '@finos/legend-application'; import { PureInstanceSetImplementationFilterState, PureInstanceSetImplementationState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/PureInstanceSetImplementationState.js'; import { pureInstanceSetImpl_setMappingFilter } from '../../../../stores/graph-modifier/DSL_Mapping_GraphModifierHelper.js'; export const MappingExplorerContextMenu = observer(forwardRef(function MappingExplorerContextMenu(props, ref) { const { mappingElement, openNewMapingModal } = props; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState); const currentMappingElement = mappingEditorState.currentTabState instanceof MappingElementState ? mappingEditorState.currentTabState.mappingElement : undefined; const removeMappingElement = () => { if (mappingElement) { flowResult(mappingEditorState.deleteMappingElement(mappingElement)).catch(applicationStore.alertUnhandledError); } if (currentMappingElement instanceof EnumerationMapping) { new MappingElementDecorator(editorStore).visitEnumerationMapping(currentMappingElement); } else if (currentMappingElement instanceof SetImplementation) { const mappingElementDecorator = new MappingElementDecorator(editorStore); mappingElementDecorator.editorStore = editorStore; currentMappingElement.accept_SetImplementationVisitor(new MappingElementDecorator(editorStore)); } mappingEditorState.reprocessMappingExplorerTree(); }; const queryMappingElement = () => { if (mappingElement instanceof SetImplementation) { flowResult(mappingEditorState.buildExecution(mappingElement)).catch(applicationStore.alertUnhandledError); } }; const createTestForMappingElement = () => { if (mappingElement instanceof SetImplementation) { flowResult(mappingEditorState.createNewTest(mappingElement)).catch(applicationStore.alertUnhandledError); } }; const addMappingFilter = () => { if (mappingElement instanceof PureInstanceSetImplementation) { if (!mappingElement.filter) { const stubLambda = stub_RawLambda(); pureInstanceSetImpl_setMappingFilter(mappingElement, stubLambda); } if (mappingEditorState.currentTabState instanceof PureInstanceSetImplementationState) { mappingEditorState.currentTabState.mappingFilterState = new PureInstanceSetImplementationFilterState(mappingElement, editorStore); } } }; const removeMappingFilter = applicationStore.guardUnhandledError(async () => { if (mappingEditorState.currentTabState instanceof PureInstanceSetImplementationState) { await flowResult(mappingEditorState.currentTabState.mappingFilterState?.convertLambdaObjectToGrammarString({ pretty: false })); mappingEditorState.currentTabState.mappingFilterState = undefined; if (mappingElement instanceof PureInstanceSetImplementation) { pureInstanceSetImpl_setMappingFilter(mappingElement, undefined); } } }); const allowAddFilter = mappingElement instanceof PureInstanceSetImplementation && !mappingElement.filter; const allowRemoveFilter = mappingElement instanceof PureInstanceSetImplementation && Boolean(mappingElement.filter); return (_jsxs(MenuContent, { ref: ref, children: [mappingElement instanceof SetImplementation && (_jsx(MenuContentItem, { onClick: queryMappingElement, children: "Query" })), mappingElement instanceof SetImplementation && (_jsx(MenuContentItem, { onClick: createTestForMappingElement, children: "Test" })), allowAddFilter && (_jsx(MenuContentItem, { onClick: addMappingFilter, children: "Add Filter" })), allowRemoveFilter && (_jsx(MenuContentItem, { onClick: removeMappingFilter, children: "Remove Filter" })), mappingElement && (_jsx(MenuContentItem, { onClick: removeMappingElement, children: "Delete" })), !mappingElement && openNewMapingModal && (_jsx(MenuContentItem, { onClick: openNewMapingModal, children: "Create new mapping element" }))] })); })); export const MappingElementExplorer = observer((props) => { const { mappingElement, openNewMapingModal, isReadOnly } = props; const editorStore = useEditorStore(); const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState); const currentMappingElement = mappingEditorState.currentTabState instanceof MappingElementState ? mappingEditorState.currentTabState.mappingElement : undefined; const openMappingElement = () => mappingEditorState.openMappingElement(mappingElement, false); const mappingElementTarget = getMappingElementTarget(mappingElement); // Drag and Drop const [, dragConnector] = useDrag(() => ({ type: mappingElement instanceof SetImplementation ? CORE_DND_TYPE.MAPPING_EXPLORER_CLASS_MAPPING : CORE_DND_TYPE.NONE, item: new MappingElementDragSource(mappingElement), }), [mappingElement]); const ref = useRef(null); dragConnector(ref); // Selection const isActive = currentMappingElement?.id.value === mappingElement.id.value; const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] = useState(false); const onContextMenuOpen = () => setIsSelectedFromContextMenu(true); const onContextMenuClose = () => setIsSelectedFromContextMenu(false); return (_jsx(ContextMenu, { disabled: isReadOnly, content: _jsx(MappingExplorerContextMenu, { mappingElement: mappingElement, openNewMapingModal: openNewMapingModal }), menuProps: { elevation: 7 }, onOpen: onContextMenuOpen, onClose: onContextMenuClose, children: _jsx("div", { ref: ref, className: clsx('mapping-explorer__item', { 'mapping-explorer__item--selected-from-context-menu': !isActive && isSelectedFromContextMenu, }, { 'mapping-explorer__item--active': isActive }), children: _jsxs("button", { className: "mapping-explorer__item__label", onClick: openMappingElement, tabIndex: -1, title: `${toSentenceCase(getMappingElementType(mappingElement)).toLowerCase()} mapping '${mappingElement.id.value}' for '${mappingElementTarget.name}'`, children: [_jsx("div", { className: "mapping-explorer__item__label__icon", children: getElementIcon(mappingElementTarget, editorStore) }), _jsx("div", { className: "mapping-explorer__item__label__text", children: getMappingElementLabel(mappingElement, editorStore).value })] }) }) })); }); const MappingElementTreeNodeContainer = observer((props) => { const { node, level, stepPaddingInRem, onNodeSelect, innerProps } = props; const { isReadOnly, onNodeExpand } = innerProps; const mappingElement = node.mappingElement; const editorStore = useEditorStore(); const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState); const currentMappingElement = mappingEditorState.currentTabState instanceof MappingElementState ? mappingEditorState.currentTabState.mappingElement : undefined; const isExpandable = Boolean(node.childrenIds?.length); const nodeExpandIcon = isExpandable ? (node.isOpen ? (_jsx(ChevronDownIcon, {})) : (_jsx(ChevronRightIcon, {}))) : (_jsx("div", {})); const mappingElementTarget = getMappingElementTarget(mappingElement); const mappingElementTooltipText = mappingElement instanceof PropertyMapping && mappingElement._isEmbedded ? `Embedded class mapping '${mappingElement.id.value}' for property '${mappingElement.property.value.name}' (${mappingElement.property.value.genericType.value.rawType.name}) of type '${mappingElement.sourceSetImplementation.value.class.value.name}'` : `${toSentenceCase(getMappingElementType(mappingElement).toLowerCase())} mapping '${mappingElement.id.value}' for '${mappingElementTarget.name}'`; // Drag and Drop const [, dragConnector] = useDrag(() => ({ type: mappingElement instanceof SetImplementation ? CORE_DND_TYPE.MAPPING_EXPLORER_CLASS_MAPPING : CORE_DND_TYPE.NONE, item: new MappingElementDragSource(mappingElement), }), [mappingElement]); const ref = useRef(null); dragConnector(ref); // Selection const selectNode = () => onNodeSelect?.(node); const onExpandIconClick = () => onNodeExpand(node); const isActive = currentMappingElement?.id.value === mappingElement.id.value; const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] = useState(false); const onContextMenuOpen = () => setIsSelectedFromContextMenu(true); const onContextMenuClose = () => setIsSelectedFromContextMenu(false); return (_jsx(ContextMenu, { disabled: isReadOnly, content: _jsx(MappingExplorerContextMenu, { mappingElement: mappingElement }), menuProps: { elevation: 7 }, onOpen: onContextMenuOpen, onClose: onContextMenuClose, children: _jsx("div", { className: clsx('tree-view__node__container', { 'mapping-explorer__item--selected-from-context-menu': !isActive && isSelectedFromContextMenu, }, { 'mapping-explorer__item--active': isActive }), onClick: selectNode, ref: ref, style: { paddingLeft: `${(level - 1) * (stepPaddingInRem ?? 1) + 0.5}rem`, display: 'flex', }, children: _jsxs("button", { className: "mapping-explorer__item__label", tabIndex: -1, title: mappingElementTooltipText, children: [_jsx("div", { className: "tree-view__node__expand-icon", onClick: onExpandIconClick, children: nodeExpandIcon }), _jsx("div", { className: "mapping-explorer__item__label__icon", children: getElementIcon(mappingElementTarget, editorStore) }), _jsx("div", { className: "mapping-explorer__item__label__text", children: getMappingElementLabel(mappingElement, editorStore).value }), mappingElement instanceof PureInstanceSetImplementation && Boolean(mappingElement.filter) && (_jsx("div", { className: "mapping-explorer__item__label__filter-icon", children: _jsx(FilterIcon, {}) }))] }) }) })); }); const getMappingIdentitySortString = (me, type) => `${type.name}-${type.path}-${me.id.value}`; export const MappingExplorer = observer((props) => { const { isReadOnly } = props; const editorStore = useEditorStore(); const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState); const mapping = mappingEditorState.mapping; const mappingElements = getAllMappingElements(mapping).sort((a, b) => getMappingIdentitySortString(a, getMappingElementTarget(a)).localeCompare(getMappingIdentitySortString(b, getMappingElementTarget(b)))); const openNewMapingModal = () => mappingEditorState.createMappingElement({ showTarget: true, openInAdjacentTab: false, }); // Drag and Drop const handleDrop = useCallback((item) => isReadOnly ? undefined : mappingEditorState.createMappingElement({ showTarget: true, openInAdjacentTab: false, target: item.data.packageableElement, }), [isReadOnly, mappingEditorState]); const [{ isDragOver }, dropConnector] = useDrop(() => ({ accept: [ CORE_DND_TYPE.PROJECT_EXPLORER_ENUMERATION, CORE_DND_TYPE.PROJECT_EXPLORER_CLASS, CORE_DND_TYPE.PROJECT_EXPLORER_ASSOCIATION, ], drop: (item) => handleDrop(item), collect: (monitor) => ({ isDragOver: monitor.isOver({ shallow: true }), }), }), [handleDrop]); // Generation const generationParentElementPath = editorStore.graphState.graphGenerationState.findGenerationParentPath(mapping.path); const generationParentElement = generationParentElementPath ? editorStore.graphManagerState.graph.getNullableElement(generationParentElementPath) : undefined; const visitGenerationParentElement = () => { if (generationParentElement) { editorStore.graphEditorMode.openElement(generationParentElement); } }; // explorer tree data const mappingElementsTreeData = mappingEditorState.mappingExplorerTreeData; const onNodeSelect = (node) => mappingEditorState.onMappingExplorerTreeNodeSelect(node); const onNodeExpand = (node) => mappingEditorState.onMappingExplorerTreeNodeExpand(node); const getMappingElementTreeChildNodes = (node) => mappingEditorState.getMappingExplorerTreeChildNodes(node); return (_jsxs("div", { "data-testid": LEGEND_STUDIO_TEST_ID.MAPPING_EXPLORER, className: "panel mapping-explorer", children: [_jsxs("div", { className: "panel__header", children: [_jsxs("div", { className: clsx('panel__header__title', { 'panel__header__title--with-generation-origin': generationParentElement, }), children: [isReadOnly && (_jsx("div", { title: "Read Only", className: "mapping-explorer__header__lock", children: _jsx(LockIcon, {}) })), _jsx("div", { className: "panel__header__title__label", children: "mapping" }), _jsx("div", { className: "panel__header__title__content", children: mapping.name })] }), _jsxs("div", { className: "panel__header__actions", children: [!generationParentElement && (_jsx("button", { className: "panel__header__action", onClick: openNewMapingModal, disabled: isReadOnly, tabIndex: -1, title: "Create new mapping element", children: _jsx(PlusIcon, {}) })), generationParentElement && (_jsxs("button", { className: "mapping-explorer__header__generation-origin", onClick: visitGenerationParentElement, tabIndex: -1, title: `Visit generation parent '${generationParentElement.path}'`, children: [_jsx("div", { className: "mapping-explorer__header__generation-origin__label", children: _jsx(FireIcon, {}) }), _jsx("div", { className: "mapping-explorer__header__generation-origin__parent-name", children: generationParentElement.name }), _jsx("div", { className: "mapping-explorer__header__generation-origin__visit-btn", children: _jsx(StickArrowCircleRightIcon, {}) })] }))] })] }), _jsx(ContextMenu, { className: "panel__content", disabled: isReadOnly, content: _jsx(MappingExplorerContextMenu, { openNewMapingModal: openNewMapingModal }), menuProps: { elevation: 7 }, children: _jsx(PanelDropZone, { isDragOver: isDragOver && !isReadOnly, dropTargetConnector: dropConnector, children: _jsxs("div", { className: "mapping-explorer__content", children: [_jsx(TreeView, { components: { TreeNodeContainer: MappingElementTreeNodeContainer, }, treeData: mappingElementsTreeData, onNodeSelect: onNodeSelect, getChildNodes: getMappingElementTreeChildNodes, innerProps: { isReadOnly, onNodeExpand, } }), !isReadOnly && !mappingElements.length && (_jsx(BlankPanelPlaceholder, { text: "Add a mapping element", onClick: openNewMapingModal, clickActionType: "add", tooltipText: "Drop a class or an enumeration to start creating mappings", isDropZoneActive: isDragOver && !isReadOnly, disabled: isReadOnly, previewText: "No mapping" })), _jsx(NewMappingElementModal, {})] }) }) })] })); }); //# sourceMappingURL=MappingExplorer.js.map