@finos/legend-studio
Version:
216 lines • 17.5 kB
JavaScript
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