UNPKG

@finos/legend-application-studio

Version:
203 lines 16.3 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 { useRef, useCallback } from 'react'; import { observer } from 'mobx-react-lite'; import { GenerationSpecificationEditorState, } from '../../../stores/editor/editor-state/GenerationSpecificationEditorState.js'; import { useDrag, useDrop } from 'react-dnd'; import { getElementIcon } from '../../ElementIconUtils.js'; import { clsx, BlankPanelContent, CustomSelectorInput, ResizablePanel, ResizablePanelGroup, ResizablePanelSplitter, ResizablePanelSplitterLine, PURE_FileGenerationIcon, RefreshIcon, FireIcon, TimesIcon, PlusIcon, LongArrowRightIcon, PanelDropZone, DragPreviewLayer, useDragPreviewLayer, Panel, PanelContent, PanelDnDEntry, PanelHeader, PanelHeaderActions, PanelHeaderActionItem, } from '@finos/legend-art'; import { CORE_DND_TYPE, } from '../../../stores/editor/utils/DnDUtils.js'; import { flowResult } from 'mobx'; import { useEditorStore } from '../EditorStoreProvider.js'; import { FileGenerationSpecification, PackageableElementExplicitReference, GenerationTreeNode, } from '@finos/legend-graph'; import { useApplicationStore } from '@finos/legend-application'; import { buildElementOption, } from '@finos/legend-lego/graph-editor'; import { packageableElementReference_setValue } from '../../../stores/graph-modifier/DomainGraphModifierHelper.js'; import { generationSpecification_addFileGeneration, generationSpecification_deleteFileGeneration, generationSpecification_setId, } from '../../../stores/graph-modifier/DSL_Generation_GraphModifierHelper.js'; const GENERATION_SPEC_NODE_DND_TYPE = 'GENERATION_SPEC_NODE'; const ModelGenerationItem = observer((props) => { const ref = useRef(null); const { nodeState, specState, options } = props; const generationTreeNode = nodeState.node; const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const modelGenerationRef = generationTreeNode.generationElement; const modelGeneration = modelGenerationRef.value; const value = { label: modelGeneration.name, value: modelGeneration, }; const onChange = (val) => { if (val !== null) { packageableElementReference_setValue(modelGenerationRef, val.value); } }; const deleteNode = () => specState.deleteGenerationTreeNode(generationTreeNode); const visitModelGeneration = () => editorStore.graphEditorMode.openElement(modelGeneration); // generation id const isUnique = specState.spec.generationNodes.filter((n) => n.id === generationTreeNode.id).length < 2; const isDefault = generationTreeNode.id === generationTreeNode.generationElement.value.path && isUnique; const changeNodeId = (event) => generationSpecification_setId(generationTreeNode, event.target.value); // Drag and Drop const handleHover = useCallback((item, monitor) => { const dragIndex = specState.generationTreeNodeStates.findIndex((e) => e === item.nodeState); const hoverIndex = specState.generationTreeNodeStates.findIndex((e) => e === nodeState); if (dragIndex === -1 || hoverIndex === -1 || dragIndex === hoverIndex) { return; } // move the item being hovered on when the dragged item position is beyond the its middle point const hoverBoundingReact = ref.current?.getBoundingClientRect(); const distanceThreshold = ((hoverBoundingReact?.bottom ?? 0) - (hoverBoundingReact?.top ?? 0)) / 2; const dragDistance = (monitor.getClientOffset()?.y ?? 0) - (hoverBoundingReact?.top ?? 0); if (dragIndex < hoverIndex && dragDistance < distanceThreshold) { return; } if (dragIndex > hoverIndex && dragDistance > distanceThreshold) { return; } specState.moveGenerationNode(dragIndex, hoverIndex); }, [nodeState, specState]); const [{ nodeBeingDragged }, dropConnector] = useDrop(() => ({ accept: [GENERATION_SPEC_NODE_DND_TYPE], hover: (item, monitor) => handleHover(item, monitor), collect: (monitor) => ({ /** * @workaround typings - https://github.com/react-dnd/react-dnd/pull/3484 */ nodeBeingDragged: // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion monitor.getItem() ?.nodeState.node, }), }), [handleHover]); const isBeingDragged = nodeState.node === nodeBeingDragged; const [, dragConnector, dragPreviewConnector] = useDrag(() => ({ type: GENERATION_SPEC_NODE_DND_TYPE, item: () => ({ nodeState }), }), [nodeState]); dragConnector(dropConnector(ref)); useDragPreviewLayer(dragPreviewConnector); return (_jsxs(PanelDnDEntry, { ref: ref, className: "generation-spec-model-generation-editor__item", showPlaceholder: isBeingDragged, children: [_jsx("div", { className: "btn--sm generation-spec-model-generation-editor__item__label", children: getElementIcon(modelGeneration, editorStore) }), _jsx("input", { className: clsx('generation-spec-model-generation-editor__item__id', { 'generation-spec-model-generation-editor__item__id--has-error': !isUnique, }), spellCheck: false, value: isDefault ? 'DEFAULT' : generationTreeNode.id, onChange: changeNodeId, disabled: isDefault }), _jsx(CustomSelectorInput, { className: "generation-spec-model-generation-editor__item__dropdown", options: options, onChange: onChange, value: value, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }), _jsx("button", { className: "btn--dark btn--sm", onClick: visitModelGeneration, tabIndex: -1, title: "See mapping", children: _jsx(LongArrowRightIcon, {}) }), _jsx("button", { className: "generation-spec-model-generation-editor__item__remove-btn", onClick: deleteNode, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) })] })); }); const ModelGenerationSpecifications = observer((props) => { const { specState } = props; const specNodesStates = specState.generationTreeNodeStates; const editorStore = useEditorStore(); const modelGenerationElementsInGraph = editorStore.pluginManager .getPureGraphManagerPlugins() .flatMap((plugin) => plugin.getExtraModelGenerationElementGetters?.() ?? []) .flatMap((getter) => getter(editorStore.graphManagerState.graph)); const extraModelGenerationSpecificationElementDnDTypes = editorStore.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraModelGenerationSpecificationElementDnDTypes?.() ?? []); const modelGenerationElementOptions = modelGenerationElementsInGraph.map(buildElementOption); const addModelGeneration = () => { const option = modelGenerationElementOptions[0]; if (option) { specState.addGenerationTreeNode(new GenerationTreeNode(PackageableElementExplicitReference.create(option.value))); } }; // Drag and Drop const handleDrop = useCallback((item) => specState.addGenerationTreeNode(new GenerationTreeNode(PackageableElementExplicitReference.create(item.data.packageableElement))), [specState]); const [{ isDragOver }, dropConnector] = useDrop(() => ({ accept: extraModelGenerationSpecificationElementDnDTypes, drop: (item, monitor) => { if (!monitor.didDrop()) { handleDrop(item); } // prevent drop event propagation to accomondate for nested DnD }, collect: (monitor) => ({ isDragOver: monitor.isOver({ shallow: true }), }), }), [handleDrop]); return (_jsxs(Panel, { children: [_jsx(PanelHeader, { title: "Model Generations", children: _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { onClick: addModelGeneration, disabled: !modelGenerationElementsInGraph.length, title: "Add File Generation", children: _jsx(PlusIcon, {}) }) }) }), _jsx(PanelContent, { children: _jsx(PanelDropZone, { isDragOver: isDragOver, dropTargetConnector: dropConnector, children: specNodesStates.length ? (_jsxs("div", { className: "generation-spec-model-generation-editor__items", children: [_jsx(DragPreviewLayer, { labelGetter: (item) => item.nodeState.node.generationElement.value.name, types: [GENERATION_SPEC_NODE_DND_TYPE] }), specNodesStates.map((nodeState) => (_jsx(ModelGenerationItem, { specState: specState, nodeState: nodeState, options: modelGenerationElementOptions }, nodeState.uuid)))] })) : (_jsx(BlankPanelContent, { children: modelGenerationElementsInGraph.length ? 'No model generation included in spec' : 'Create a model generation element to include in spec' })) }) })] })); }); const FileGenerationItem = observer((props) => { const { fileGeneraitonRef, generationSpecificationEditorState, options } = props; const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const fileGeneration = fileGeneraitonRef.value; const value = { label: fileGeneration.name, value: fileGeneration }; const onChange = (val) => { if (val !== null) { packageableElementReference_setValue(fileGeneraitonRef, val.value); } }; const deleteColumnSort = () => generationSpecification_deleteFileGeneration(generationSpecificationEditorState.spec, fileGeneraitonRef); const visitFileGen = () => editorStore.graphEditorMode.openElement(fileGeneration); return (_jsxs("div", { className: "generation-spec-file-generation-editor__item", children: [_jsx("div", { className: "btn--sm generation-spec-file-generation-editor__item__label", children: _jsx(PURE_FileGenerationIcon, {}) }), _jsx(CustomSelectorInput, { className: "generation-spec-file-generation-editor__item__dropdown", options: options, onChange: onChange, value: value, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }), _jsx("button", { className: "btn--dark btn--sm", onClick: visitFileGen, tabIndex: -1, title: "See mapping", children: _jsx(LongArrowRightIcon, {}) }), _jsx("button", { className: "generation-spec-file-generation-editor__item__remove-btn", onClick: deleteColumnSort, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) })] })); }); const FileGenerationSpecifications = observer((props) => { const { generationSpecificationEditorState } = props; const generationSpec = generationSpecificationEditorState.spec; const editorStore = useEditorStore(); const fileGenerations = generationSpecificationEditorState.spec.fileGenerations.map((f) => f.value); const fileGenerationInGraph = editorStore.graphManagerState.graph.ownFileGenerations; const fileGenerationsOptions = fileGenerationInGraph .filter((f) => !fileGenerations.includes(f)) .map(buildElementOption); const addFileGeneration = () => { const option = fileGenerationsOptions[0]; if (option) { generationSpecification_addFileGeneration(generationSpec, option.value); } }; // drag and drop const handleDrop = useCallback((item) => { const element = item.data.packageableElement; if (element instanceof FileGenerationSpecification && !fileGenerations.includes(element)) { generationSpecification_addFileGeneration(generationSpec, element); } }, [fileGenerations, generationSpec]); const [{ isDragOver }, dropConnector] = useDrop(() => ({ accept: [CORE_DND_TYPE.PROJECT_EXPLORER_FILE_GENERATION], drop: (item, monitor) => { if (!monitor.didDrop()) { handleDrop(item); } // prevent drop event propagation to accomondate for nested DnD }, collect: (monitor) => ({ isDragOver: monitor.isOver({ shallow: true }), }), }), [handleDrop]); return (_jsxs(Panel, { className: "generation-spec-file-generation-editor", children: [_jsxs(PanelHeader, { children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "File Generations" }) }), _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { onClick: addFileGeneration, disabled: !fileGenerationsOptions.length, title: "Add File Generation", children: _jsx(PlusIcon, {}) }) })] }), _jsx(PanelContent, { children: _jsx(PanelDropZone, { isDragOver: isDragOver, dropTargetConnector: dropConnector, children: generationSpec.fileGenerations.length ? (_jsx("div", { className: "generation-spec-file-generation-editor__items", children: generationSpec.fileGenerations.map((fileGen) => (_jsx(FileGenerationItem, { generationSpecificationEditorState: generationSpecificationEditorState, fileGeneraitonRef: fileGen, options: fileGenerationsOptions }, fileGen.value.path))) })) : (_jsx(BlankPanelContent, { children: fileGenerationInGraph.length ? 'Add file generation to spec' : 'Create a file generation to include in spec' })) }) })] })); }); export const GenerationSpecificationEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const generationSpecificationState = editorStore.tabManagerState.getCurrentEditorState(GenerationSpecificationEditorState); const modelGenerationState = editorStore.graphState.graphGenerationState; const generationSpec = generationSpecificationState.spec; const generate = applicationStore.guardUnhandledError(() => flowResult(modelGenerationState.globalGenerate())); const emptyGenerationEntities = applicationStore.guardUnhandledError(() => flowResult(modelGenerationState.clearGenerations())); return (_jsx("div", { className: "generation-spec-editor", children: _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsxs("div", { className: "panel__header__title", children: [_jsx("div", { className: "panel__header__title__label", children: "Generation Specification" }), _jsx("div", { className: "panel__header__title__content", children: generationSpec.name })] }), _jsxs(PanelHeaderActions, { children: [_jsx(PanelHeaderActionItem, { className: clsx('editor__status-bar__action editor__status-bar__generate-btn', { 'editor__status-bar__generate-btn--wiggling': modelGenerationState.isRunningGlobalGenerate, }), onClick: generate, title: "Generate", children: _jsx(FireIcon, {}) }), _jsx(PanelHeaderActionItem, { className: clsx('editor__status-bar__action editor__status-bar__generate-btn', { 'local-changes__refresh-btn--loading': modelGenerationState.clearingGenerationEntitiesState .isInProgress, }), onClick: emptyGenerationEntities, title: "Clear generation entities", children: _jsx(RefreshIcon, {}) })] })] }), _jsx(PanelContent, { className: "generation-spec-editor__content", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { size: 400, minSize: 25, children: _jsx(ModelGenerationSpecifications, { specState: generationSpecificationState }) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-200)" }) }), _jsx(ResizablePanel, { children: _jsx(FileGenerationSpecifications, { generationSpecificationEditorState: generationSpecificationState }) })] }) })] }) })); }); //# sourceMappingURL=GenerationSpecificationEditor.js.map