@finos/legend-application-studio
Version:
Legend Studio application core
203 lines • 16.3 kB
JavaScript
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