UNPKG

@finos/legend-studio

Version:
216 lines 17.5 kB
import { jsx as _jsx, Fragment as _Fragment, 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, useEffect, useCallback } from 'react'; import { observer } from 'mobx-react-lite'; import { GenerationSpecificationEditorState, } from '../../../stores/editor-state/GenerationSpecificationEditorState.js'; import { getEmptyImage } from 'react-dnd-html5-backend'; import { useDragLayer, useDrag, useDrop, } from 'react-dnd'; import { getElementIcon } from '../../shared/ElementIconUtils.js'; import { clsx, BlankPanelContent, CustomSelectorInput, ResizablePanel, ResizablePanelGroup, ResizablePanelSplitter, ResizablePanelSplitterLine, PURE_FileGenerationIcon, RefreshIcon, FireIcon, TimesIcon, PlusIcon, LongArrowRightIcon, } from '@finos/legend-art'; import { CORE_DND_TYPE, } from '../../../stores/shared/DnDUtil.js'; import { getNullableFirstElement } from '@finos/legend-shared'; import { flowResult } from 'mobx'; import { useEditorStore } from '../EditorStoreProvider.js'; import { FileGenerationSpecification, PackageableElementExplicitReference, GenerationTreeNode, } from '@finos/legend-graph'; import { buildElementOption, useApplicationStore, } from '@finos/legend-application'; import { packageableElementReference_setValue } from '../../../stores/graphModifier/DomainGraphModifierHelper.js'; import { generationSpecification_addFileGeneration, generationSpecification_deleteFileGeneration, generationSpecification_setId, } from '../../../stores/graphModifier/DSLGeneration_GraphModifierHelper.js'; const ModelGenerationDragLayer = () => { const { itemType, item, isDragging, currentPosition } = useDragLayer((monitor) => ({ itemType: monitor.getItemType(), item: monitor.getItem(), isDragging: monitor.isDragging(), initialOffset: monitor.getInitialSourceClientOffset(), currentPosition: monitor.getClientOffset(), })); if (!isDragging || !item || itemType !== CORE_DND_TYPE.GENERATION_SPEC_NODE) { return null; } return (_jsx("div", { className: "generation-spec-model-generation-editor__item__drag-preview-layer", children: _jsx("div", { className: "generation-spec-model-generation-editor__item__drag-preview", style: !currentPosition ? { display: 'none' } : { transform: `translate(${currentPosition.x + 20}px, ${currentPosition.y + 10}px)`, }, children: item.nodeState.node.generationElement.value.name }) })); }; const ModelGenerationItem = observer((props) => { const ref = useRef(null); const { nodeState, specState, options, isRearrangingNodes } = props; const generationTreeNode = nodeState.node; const editorStore = useEditorStore(); 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.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 - (hoverBoundingReact?.top ?? 0); if (dragIndex < hoverIndex && dragDistance < distanceThreshold) { return; } if (dragIndex > hoverIndex && dragDistance > distanceThreshold) { return; } specState.moveGenerationNode(dragIndex, hoverIndex); }, [nodeState, specState]); const [, dropConnector] = useDrop(() => ({ accept: [CORE_DND_TYPE.GENERATION_SPEC_NODE], hover: (item, monitor) => handleHover(item, monitor), }), [handleHover]); const [, dragConnector, dragPreviewConnector] = useDrag(() => ({ type: CORE_DND_TYPE.GENERATION_SPEC_NODE, item: () => { nodeState.setIsBeingDragged(true); return { nodeState }; }, end: (item) => item?.nodeState.setIsBeingDragged(false), }), [nodeState]); dragConnector(dropConnector(ref)); // hide default HTML5 preview image useEffect(() => { dragPreviewConnector(getEmptyImage(), { captureDraggingState: true }); }, [dragPreviewConnector]); return (_jsxs("div", { ref: ref, className: clsx('generation-spec-model-generation-editor__item', { 'generation-spec-model-generation-editor__item--dragged': nodeState.isBeingDragged, 'generation-spec-model-generation-editor__item---no-hover': isRearrangingNodes, }), children: [nodeState.isBeingDragged && (_jsx("div", { className: "generation-spec-editor__dnd__placeholder" })), !nodeState.isBeingDragged && (_jsxs(_Fragment, { children: [_jsx("div", { className: "btn--sm generation-spec-model-generation-editor__item__label", children: getElementIcon(editorStore, modelGeneration) }), _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: true }), _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 = getNullableFirstElement(modelGenerationElementOptions); if (option) { specState.addGenerationTreeNode(new GenerationTreeNode(PackageableElementExplicitReference.create(option.value))); } }; // Drag and Drop const isRearrangingNodes = specNodesStates.some((nodeState) => nodeState.isBeingDragged); const handleDrop = useCallback((item) => specState.addGenerationTreeNode(new GenerationTreeNode(PackageableElementExplicitReference.create(item.data.packageableElement))), [specState]); const [{ isPropertyDragOver }, dropConnector] = useDrop(() => ({ accept: extraModelGenerationSpecificationElementDnDTypes, drop: (item, monitor) => { if (!monitor.didDrop()) { handleDrop(item); } // prevent drop event propagation to accomondate for nested DnD }, collect: (monitor) => ({ isPropertyDragOver: monitor.isOver({ shallow: true }), }), }), [handleDrop]); return (_jsxs("div", { className: "panel", children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "Model Generations" }) }), _jsx("div", { className: "panel__header__actions", children: _jsx("button", { className: "panel__header__action", onClick: addModelGeneration, disabled: !modelGenerationElementsInGraph.length, tabIndex: -1, title: "Add File Generation", children: _jsx(PlusIcon, {}) }) })] }), _jsxs("div", { className: "panel__content dnd__overlay__container", ref: dropConnector, children: [_jsx("div", { className: clsx({ dnd__overlay: isPropertyDragOver }) }), specNodesStates.length ? (_jsxs("div", { className: "generation-spec-model-generation-editor__items", children: [_jsx(ModelGenerationDragLayer, {}), specNodesStates.map((nodeState) => (_jsx(ModelGenerationItem, { isRearrangingNodes: isRearrangingNodes, 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 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.openElement(fileGeneration); return (_jsxs("div", { className: "panel__content__form__section__list__item 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: true }), _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 = getNullableFirstElement(fileGenerationsOptions); 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 [{ isPropertyDragOver }, dropConnector] = useDrop(() => ({ accept: [CORE_DND_TYPE.PROJECT_EXPLORER_FILE_GENERATION], drop: (item, monitor) => { if (!monitor.didDrop()) { handleDrop(item); } }, collect: (monitor) => ({ isPropertyDragOver: monitor.isOver({ shallow: true }), }), }), [handleDrop]); return (_jsxs("div", { className: "panel generation-spec-file-generation-editor", children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "File Generations" }) }), _jsx("div", { className: "panel__header__actions", children: _jsx("button", { className: "panel__header__action", onClick: addFileGeneration, disabled: !fileGenerationsOptions.length, tabIndex: -1, title: "Add File Generation", children: _jsx(PlusIcon, {}) }) })] }), _jsxs("div", { className: "panel__content dnd__overlay__container", ref: dropConnector, children: [_jsx("div", { className: clsx({ dnd__overlay: isPropertyDragOver }) }), 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.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("div", { className: "panel", children: [_jsxs("div", { className: "panel__header", 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("div", { className: "panel__header__actions", children: [_jsx("button", { className: clsx('editor__status-bar__action editor__status-bar__generate-btn', { 'editor__status-bar__generate-btn--wiggling': modelGenerationState.isRunningGlobalGenerate, }), tabIndex: -1, onClick: generate, title: 'Generate', children: _jsx(FireIcon, {}) }), _jsx("button", { className: clsx('editor__status-bar__action editor__status-bar__generate-btn', { 'local-changes__refresh-btn--loading': modelGenerationState.isClearingGenerationEntities, }), onClick: emptyGenerationEntities, tabIndex: -1, title: "Clear generation entities", children: _jsx(RefreshIcon, {}) })] })] }), _jsx("div", { className: "panel__content 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