@finos/legend-studio
Version:
398 lines • 40.2 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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, useMemo, useCallback, Fragment } from 'react';
import { observer } from 'mobx-react-lite';
import { flowResult, runInAction } from 'mobx';
import { getElementIcon } from '../../../shared/ElementIconUtils.js';
import { useDrop } from 'react-dnd';
import { getTextContent, getEditorLanguageFromFormat, } from '../../../../stores/editor-state/FileGenerationViewerState.js';
import { FileGenerationEditorState } from '../../../../stores/editor-state/element-editor-state/FileGenerationEditorState.js';
import { UnsupportedOperationError, debounce, guaranteeNonNullable, } from '@finos/legend-shared';
import { ResizablePanelGroup, ResizablePanel, ResizablePanelSplitter, ResizablePanelSplitterLine, clsx, TreeView, BlankPanelContent, PanelLoadingIndicator, CustomSelectorInput, PencilIcon, RefreshIcon, TimesIcon, CheckSquareIcon, SquareIcon, ChevronDownIcon, ChevronRightIcon, FolderOpenIcon, FolderIcon, FileCodeIcon, LockIcon, SaveIcon, } from '@finos/legend-art';
import { CORE_DND_TYPE, } from '../../../../stores/shared/DnDUtil.js';
import { GenerationDirectory, GenerationFile, getFileGenerationChildNodes, } from '../../../../stores/shared/FileGenerationTreeUtil.js';
import { LEGEND_STUDIO_TEST_ID } from '../../../LegendStudioTestID.js';
import { useEditorStore } from '../../EditorStoreProvider.js';
import { GenerationPropertyItemType, PackageableElementReference, PackageableElementExplicitReference, isValidFullPath, resolvePackagePathAndElementName, getNullableFileGenerationConfig, } from '@finos/legend-graph';
import { useApplicationStore } from '@finos/legend-application';
import { StudioTextInputEditor } from '../../../shared/StudioTextInputEditor.js';
import { fileGeneration_addScopeElement, fileGeneration_changeScopeElement, fileGeneration_deleteScopeElement, fileGeneration_setGenerationOutputPath, } from '../../../../stores/graphModifier/DSLGeneration_GraphModifierHelper.js';
export const FileGenerationTreeNodeContainer = (props) => {
const { node, level, stepPaddingInRem, onNodeSelect, innerProps } = props;
const { selectedNode } = innerProps;
const isSelected = selectedNode === node;
const isDirectory = node.fileNode instanceof GenerationDirectory;
const expandIcon = !isDirectory ? (_jsx("div", {})) : node.isOpen ? (_jsx(ChevronDownIcon, {})) : (_jsx(ChevronRightIcon, {}));
const iconPackageColor = 'color--generated';
const nodeIcon = isDirectory ? (node.isOpen ? (_jsx("div", { className: iconPackageColor, children: _jsx(FolderOpenIcon, {}) })) : (_jsx("div", { className: iconPackageColor, children: _jsx(FolderIcon, {}) }))) : (_jsx("div", { className: "icon", children: _jsx(FileCodeIcon, {}) }));
const selectNode = (event) => onNodeSelect?.(node);
return (_jsxs("div", { className: clsx('tree-view__node__container generation-result-viewer__explorer__package-tree__node__container', {
'generation-result-viewer__explorer__package-tree__node__container--selected': isSelected,
}), onClick: selectNode, style: {
paddingLeft: `${level * (stepPaddingInRem ?? 1)}rem`,
display: 'flex',
}, children: [_jsxs("div", { className: "tree-view__node__icon generation-result-viewer__explorer__package-tree__node__icon", children: [_jsx("div", { className: "generation-result-viewer__explorer__package-tree__node__icon__expand", children: expandIcon }), _jsx("div", { className: "generation-result-viewer__explorer__package-tree__node__icon__type", children: nodeIcon })] }), _jsx("button", { className: "tree-view__node__label generation-result-viewer__explorer__package-tree__node__label", tabIndex: -1, title: node.fileNode.path, children: node.label })] }));
};
export const FileGenerationTree = observer((props) => {
const { directoryTreeData, onNodeSelect, getFileElementTreeChildNodes, selectedNode, } = props;
return (_jsx(TreeView, { components: {
TreeNodeContainer: FileGenerationTreeNodeContainer,
}, treeData: directoryTreeData, onNodeSelect: onNodeSelect, getChildNodes: getFileElementTreeChildNodes, innerProps: {
selectedNode: selectedNode,
} }));
});
export const GenerationResultExplorer = observer((props) => {
const { fileGenerationState } = props;
const treeData = guaranteeNonNullable(fileGenerationState.directoryTreeData);
const onNodeSelect = (node) => fileGenerationState.onTreeNodeSelect(node, treeData);
const getMappingElementTreeChildNodes = (node) => getFileGenerationChildNodes(node, treeData);
if (!treeData.nodes.size) {
return _jsx(BlankPanelContent, { children: "No content" });
}
return (_jsx("div", { className: "generation-result-viewer__explorer__content", children: _jsx(FileGenerationTree, { selectedNode: fileGenerationState.selectedNode, directoryTreeData: treeData, onNodeSelect: onNodeSelect, getFileElementTreeChildNodes: getMappingElementTreeChildNodes }) }));
});
export const GenerationResultViewer = observer((props) => {
const { fileGenerationState } = props;
const applicationStore = useApplicationStore();
const selectedNode = fileGenerationState.selectedNode;
const fileNode = selectedNode?.fileNode;
const regenerate = applicationStore.guardUnhandledError(() => flowResult(fileGenerationState.generate()));
const extraFileGenerationResultViewerActions = fileNode instanceof GenerationFile
? fileGenerationState.editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraFileGenerationResultViewerActionConfigurations?.() ??
[])
.map((config) => (_jsx(Fragment, { children: config.renderer(fileGenerationState) }, config.key)))
: null;
return (_jsxs(ResizablePanelGroup, { orientation: "vertical", children: [_jsx(ResizablePanel, { size: 250, minSize: 250, children: _jsx("div", { className: "generation-result-viewer__side-bar", children: _jsxs("div", { className: "panel generation-result-viewer__explorer", children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "result" }) }), _jsx("div", { className: "panel__header__actions", children: _jsx("button", { className: clsx('panel__header__action generation-result-viewer__regenerate-btn', {
' generation-result-viewer__regenerate-btn--loading': fileGenerationState.isGenerating,
}), tabIndex: -1, disabled: fileGenerationState.isGenerating, onClick: regenerate, title: 'Re-generate', children: _jsx(RefreshIcon, {}) }) })] }), _jsxs("div", { className: "panel__content", children: [_jsx(PanelLoadingIndicator, { isLoading: fileGenerationState.isGenerating }), Boolean(fileGenerationState.directoryTreeData) && (_jsx(GenerationResultExplorer, { fileGenerationState: fileGenerationState })), Boolean(!fileGenerationState.directoryTreeData) && (_jsx(BlankPanelContent, { children: "Generation result not available" }))] })] }) }) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-200)" }) }), _jsx(ResizablePanel, { children: _jsxs("div", { className: "panel generation-result-viewer__file", children: [_jsxs("div", { className: "panel__header", children: [fileNode && !(fileNode instanceof GenerationDirectory) && (_jsxs("div", { className: "panel__header__title", children: [_jsx("div", { className: "panel__header__title__label", children: "file" }), _jsx("div", { className: "panel__header__title__content generation-result-viewer__file__header__name", children: fileNode.name })] })), _jsx("div", { className: "panel__header__actions", children: extraFileGenerationResultViewerActions })] }), _jsxs("div", { className: "panel__content", children: [fileNode instanceof GenerationFile && (_jsx(StudioTextInputEditor, { inputValue: getTextContent(fileNode.content, fileNode.format), isReadOnly: true, language: getEditorLanguageFromFormat(fileNode.format) })), !(fileNode instanceof GenerationFile) && (_jsx(BlankPanelContent, { children: "No file selected" }))] })] }) })] }));
});
const FileGenerationScopeEditor = observer((props) => {
const { isReadOnly, fileGenerationState, regenerate } = props;
const editorStore = useEditorStore();
const applicationStore = useApplicationStore();
const fileGeneration = fileGenerationState.fileGeneration;
const scopeElements = fileGeneration.scopeElements;
const scopeElementPath = (element) => element instanceof PackageableElementReference
? element.value.path
: element;
// NOTE: `showEditInput` is either boolean (to hide/show the add value button) or a number (index of the item being edited)
const [showEditInput, setShowEditInput] = useState(false);
const [itemValue, setItemValue] = useState(undefined);
const hideAddOrEditItemInput = () => {
setShowEditInput(false);
setItemValue('');
};
const showEditItemInput = (value, idx) => () => {
setItemValue(scopeElementPath(value));
setShowEditInput(idx);
};
const showAddItemInput = () => {
setShowEditInput(true);
setItemValue('');
};
const deleteScopeElement = (scopeElement) => {
fileGeneration_deleteScopeElement(fileGeneration, scopeElement);
regenerate()?.catch(applicationStore.alertUnhandledError);
};
const changeItemInputValue = (event) => setItemValue(event.target.value);
const isDisabled = Boolean(isReadOnly ||
!itemValue ||
scopeElements
.map((element) => scopeElementPath(element))
.includes(itemValue));
const addValue = () => {
if (itemValue && !isReadOnly) {
regenerate.cancel();
const element = editorStore.graphManagerState.graph.getNullableElement(itemValue, true);
fileGenerationState.addScopeElement(element ?? itemValue);
regenerate()?.catch(applicationStore.alertUnhandledError);
hideAddOrEditItemInput();
}
};
const updateValue = (value) => () => {
if (itemValue &&
!isReadOnly &&
!scopeElements
.map((element) => scopeElementPath(element))
.includes(itemValue)) {
const element = editorStore.graphManagerState.graph.getNullableElement(itemValue, true);
if (element) {
regenerate.cancel();
fileGeneration_changeScopeElement(fileGeneration, value, PackageableElementExplicitReference.create(element));
regenerate()?.catch(applicationStore.alertUnhandledError);
}
}
hideAddOrEditItemInput();
};
return (_jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Scope" }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: "Specifies the list of packages and elements that will determine elements to get generated" }), _jsxs("div", { className: "panel__content__form__section__list", children: [_jsxs("div", { className: "panel__content__form__section__list__items", "data-testid": LEGEND_STUDIO_TEST_ID.PANEL_CONTENT_FORM_SECTION_LIST_ITEMS, children: [scopeElements.map((value, idx) => (
// NOTE: since the value must be unique, we will use it as the key
_jsx("div", { className: showEditInput === idx
? 'panel__content__form__section__list__new-item'
: 'panel__content__form__section__list__item', children: showEditInput === idx ? (_jsxs(_Fragment, { children: [_jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemValue, onChange: changeItemInputValue }), _jsxs("div", { className: "panel__content__form__section__list__new-item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isDisabled, onClick: updateValue(value), tabIndex: -1, children: "Save" }), _jsx("button", { className: "panel__content__form__section__list__new-item__cancel-btn btn btn--dark", disabled: isReadOnly, onClick: hideAddOrEditItemInput, tabIndex: -1, children: "Cancel" })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "panel__content__form__section__list__item__value file-generation-editor__configuration__content__scope", children: [_jsx("div", { className: "file-generation-editor__configuration__content__scope__icon", children: getElementIcon(editorStore, value instanceof PackageableElementReference
? value.value
: undefined) }), _jsx("div", { className: "file-generation-editor__configuration__content__scope__path", children: scopeElementPath(value) })] }), _jsxs("div", { className: "panel__content__form__section__list__item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__item__edit-btn", disabled: isReadOnly, onClick: showEditItemInput(value, idx), tabIndex: -1, children: _jsx(PencilIcon, {}) }), _jsx("button", { className: "panel__content__form__section__list__item__remove-btn", disabled: isReadOnly, onClick: () => deleteScopeElement(value), tabIndex: -1, children: _jsx(TimesIcon, {}) })] })] })) }, scopeElementPath(value)))), showEditInput === true && (_jsxs("div", { className: "panel__content__form__section__list__new-item", children: [_jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemValue, onChange: changeItemInputValue }), _jsxs("div", { className: "panel__content__form__section__list__new-item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isDisabled, onClick: addValue, tabIndex: -1, children: "Save" }), _jsx("button", { className: "panel__content__form__section__list__new-item__cancel-btn btn btn--dark", disabled: isReadOnly, onClick: hideAddOrEditItemInput, tabIndex: -1, children: "Cancel" })] })] }))] }), !scopeElements.length && (_jsx("div", { className: "panel__content__form__section__list__empty", children: "No path specified" })), showEditInput !== true && (_jsx("div", { className: "panel__content__form__section__list__new-item__add", children: _jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isReadOnly, onClick: showAddItemInput, tabIndex: -1, children: "Add Value" }) }))] })] }));
});
const GenerationStringPropertyEditor = observer((props) => {
const { property, getConfigValue, isReadOnly, update } = props;
// If there is no default value the string will be 'null'. We will treat it as an empty string
const defaultValue = property.defaultValue === 'null' ? '' : property.defaultValue;
const value = getConfigValue(property.name) ?? defaultValue;
const changeValue = (event) => update(property, event.target.value);
return (_jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: property.name }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: property.description }), _jsx("input", { className: "panel__content__form__section__input", spellCheck: false, disabled: isReadOnly, value: value, onChange: changeValue })] }));
});
const GenerationIntegerPropertyEditor = observer((props) => {
const { property, getConfigValue, isReadOnly, update } = props;
const defaultValue = JSON.parse(property.defaultValue);
const value = getConfigValue(property.name) ?? defaultValue;
const changeValue = (event) => update(property, event.target.value);
return (_jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: property.name }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: property.description }), _jsx("input", { className: "panel__content__form__section__input panel__content__form__section__number-input", spellCheck: false, type: "number", disabled: isReadOnly, value: value, onChange: changeValue })] }));
});
const GenerationBooleanPropertyEditor = observer((props) => {
const { property, getConfigValue, isReadOnly, update } = props;
const defaultValue = JSON.parse(property.defaultValue);
const value = getConfigValue(property.name) ?? defaultValue;
const toggle = () => {
if (!isReadOnly) {
update(property, !value);
}
};
return (_jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: property.name }), _jsxs("div", { className: clsx('panel__content__form__section__toggler', {
'panel__content__form__section__toggler--disabled': isReadOnly,
}), onClick: toggle, children: [_jsx("button", { className: clsx('panel__content__form__section__toggler__btn', {
'panel__content__form__section__toggler__btn--toggled': value,
}), disabled: isReadOnly, tabIndex: -1, children: value ? _jsx(CheckSquareIcon, {}) : _jsx(SquareIcon, {}) }), _jsx("div", { className: "panel__content__form__section__toggler__prompt", children: property.description })] })] }));
});
const GenerationEnumPropertyEditor = observer((props) => {
const { property, getConfigValue, isReadOnly, update } = props;
const getEnumLabel = (_enum) => isValidFullPath(_enum)
? resolvePackagePathAndElementName(_enum)[1]
: _enum;
const options = guaranteeNonNullable(property.items, 'Generation configuration description items for enum property item type is missing').enums.map((_enum) => ({ label: getEnumLabel(_enum), value: _enum }));
const value = getConfigValue(property.name) ??
property.defaultValue;
const onChange = (val) => {
if (val !== null && val.value !== value) {
update(property, val.value);
}
};
return (_jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: property.name }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: property.description }), _jsx(CustomSelectorInput, { className: "panel__content__form__section__dropdown", options: options, onChange: onChange, value: { label: getEnumLabel(value), value }, isClearable: true, escapeClearsValue: true, darkMode: true, disable: isReadOnly })] }));
});
const GenerationArrayPropertyEditor = observer((props) => {
const { property, getConfigValue, isReadOnly, update } = props;
let defaultValue = [];
// NOTE: hacking this because the backend send corrupted array string
if (property.defaultValue !== '' && property.defaultValue !== '[]') {
defaultValue = property.defaultValue
.substring(1, property.defaultValue.length - 1)
.split(',');
}
const arrayValues = getConfigValue(property.name) ?? defaultValue;
// NOTE: `showEditInput` is either boolean (to hide/show the add value button) or a number (index of the item being edited)
const [showEditInput, setShowEditInput] = useState(false);
const [itemValue, setItemValue] = useState('');
const showAddItemInput = () => setShowEditInput(true);
const showEditItemInput = (value, idx) => () => {
setItemValue(value);
setShowEditInput(idx);
};
const hideAddOrEditItemInput = () => {
setShowEditInput(false);
setItemValue('');
};
const changeItemInputValue = (event) => setItemValue(event.target.value);
const addValue = () => {
if (itemValue && !isReadOnly && !arrayValues.includes(itemValue)) {
update(property, arrayValues.concat([itemValue]));
}
hideAddOrEditItemInput();
};
const updateValue = (idx) => () => {
if (itemValue && !isReadOnly && !arrayValues.includes(itemValue)) {
runInAction(() => {
arrayValues[idx] = itemValue;
});
update(property, arrayValues);
}
hideAddOrEditItemInput();
};
const deleteValue = (idx) => () => {
if (!isReadOnly) {
runInAction(() => arrayValues.splice(idx, 1));
update(property, [...arrayValues]);
// Since we keep track of the value currently being edited using the index, we have to account for it as we delete entry
if (typeof showEditInput === 'number' && showEditInput > idx) {
setShowEditInput(showEditInput - 1);
}
}
};
return (_jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: property.name }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: property.description }), _jsxs("div", { className: "panel__content__form__section__list", children: [_jsxs("div", { className: "panel__content__form__section__list__items", "data-testid": LEGEND_STUDIO_TEST_ID.PANEL_CONTENT_FORM_SECTION_LIST_ITEMS, children: [arrayValues.map((value, idx) => (
// NOTE: since the value must be unique, we will use it as the key
_jsx("div", { className: showEditInput === idx
? 'panel__content__form__section__list__new-item'
: 'panel__content__form__section__list__item', children: showEditInput === idx ? (_jsxs(_Fragment, { children: [_jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemValue, onChange: changeItemInputValue }), _jsxs("div", { className: "panel__content__form__section__list__new-item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isReadOnly || arrayValues.includes(itemValue), onClick: updateValue(idx), tabIndex: -1, children: "Save" }), _jsx("button", { className: "panel__content__form__section__list__new-item__cancel-btn btn btn--dark", disabled: isReadOnly, onClick: hideAddOrEditItemInput, tabIndex: -1, children: "Cancel" })] })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__list__item__value", children: value }), _jsxs("div", { className: "panel__content__form__section__list__item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__item__edit-btn", disabled: isReadOnly, onClick: showEditItemInput(value, idx), tabIndex: -1, children: _jsx(PencilIcon, {}) }), _jsx("button", { className: "panel__content__form__section__list__item__remove-btn", disabled: isReadOnly, onClick: deleteValue(idx), tabIndex: -1, children: _jsx(TimesIcon, {}) })] })] })) }, value))), showEditInput === true && (_jsxs("div", { className: "panel__content__form__section__list__new-item", children: [_jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemValue, onChange: changeItemInputValue }), _jsxs("div", { className: "panel__content__form__section__list__new-item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isReadOnly || arrayValues.includes(itemValue), onClick: addValue, tabIndex: -1, children: "Save" }), _jsx("button", { className: "panel__content__form__section__list__new-item__cancel-btn btn btn--dark", disabled: isReadOnly, onClick: hideAddOrEditItemInput, tabIndex: -1, children: "Cancel" })] })] }))] }), showEditInput !== true && (_jsx("div", { className: "panel__content__form__section__list__new-item__add", children: _jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isReadOnly, onClick: showAddItemInput, tabIndex: -1, children: "Add Value" }) }))] })] }));
});
const GenerationMapPropertyEditor = observer((props) => {
const { property, getConfigValue, isReadOnly, update } = props;
// Right now, always assume this is a map between STRING and STRING (might need to support STRING - INTEGER and STRING - BOOLEAN)
const nonNullableDefaultValue = property.defaultValue === 'null' ? '{}' : property.defaultValue;
const mapValues = (getConfigValue(property.name) ?? JSON.parse(nonNullableDefaultValue));
// NOTE: `showEditInput` is either boolean (to hide/show the add value button) or a number (index of the item being edited)
const [showEditInput, setShowEditInput] = useState(false);
const [itemKey, setItemKey] = useState('');
const [itemValue, setItemValue] = useState('');
const showAddItemInput = () => setShowEditInput(true);
const showEditItemInput = (key, value, idx) => () => {
setItemKey(key);
setItemValue(value);
setShowEditInput(idx);
};
const hideAddOrEditItemInput = () => {
setShowEditInput(false);
setItemKey('');
setItemValue('');
};
const changeItemInputKey = (event) => setItemKey(event.target.value);
const changeItemInputValue = (event) => setItemValue(event.target.value);
const addValue = () => {
if (itemValue &&
itemKey &&
!isReadOnly &&
!Array.from(Object.keys(mapValues)).includes(itemKey)) {
runInAction(() => {
mapValues[itemKey] = itemValue;
});
update(property, mapValues);
}
hideAddOrEditItemInput();
};
const updateValue = (key) => () => {
if (itemValue && !isReadOnly) {
runInAction(() => {
delete mapValues[key];
mapValues[itemKey] = itemValue;
});
update(property, mapValues);
}
hideAddOrEditItemInput();
};
const deleteValue = (key, idx) => () => {
if (!isReadOnly) {
runInAction(() => delete mapValues[key]);
update(property, mapValues);
// Since we keep track of the value currently being edited using the index, we have to account for it as we delete entry
if (typeof showEditInput === 'number' && showEditInput > idx) {
setShowEditInput(showEditInput - 1);
}
}
};
return (_jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: property.name }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: property.description }), _jsxs("div", { className: "panel__content__form__section__list", children: [_jsxs("div", { className: "panel__content__form__section__list__items", "data-testid": LEGEND_STUDIO_TEST_ID.PANEL_CONTENT_FORM_SECTION_LIST_ITEMS, children: [Array.from(Object.entries(mapValues)).map(([key, value], idx) => (
// NOTE: since the key must be unique, we will use it to generate the key
_jsx("div", { className: showEditInput === idx
? 'panel__content__form__section__list__new-item'
: 'panel__content__form__section__list__item', children: showEditInput === idx ? (_jsxs(_Fragment, { children: [_jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemKey, onChange: changeItemInputKey }), _jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemValue, onChange: changeItemInputValue }), _jsxs("div", { className: "panel__content__form__section__list__new-item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isReadOnly, onClick: updateValue(key), tabIndex: -1, children: "Save" }), _jsx("button", { className: "panel__content__form__section__list__new-item__cancel-btn btn btn--dark", disabled: isReadOnly, onClick: hideAddOrEditItemInput, tabIndex: -1, children: "Cancel" })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "panel__content__form__section__list__item__value panel__content__form__section__list__item__value__map-item", children: [_jsx("span", { className: "panel__content__form__section__list__item__value__map-item__key", children: key }), _jsx("span", { className: "panel__content__form__section__list__item__value__map-item__separator", children: ":" }), _jsx("span", { className: "panel__content__form__section__list__item__value__map-item__value", children: value })] }), _jsxs("div", { className: "panel__content__form__section__list__item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__item__edit-btn", disabled: isReadOnly, onClick: showEditItemInput(key, value, idx), tabIndex: -1, children: _jsx(PencilIcon, {}) }), _jsx("button", { className: "panel__content__form__section__list__item__remove-btn", disabled: isReadOnly, onClick: deleteValue(key, idx), tabIndex: -1, children: _jsx(TimesIcon, {}) })] })] })) }, key))), showEditInput === true && (_jsxs("div", { className: "panel__content__form__section__list__new-item", children: [_jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemKey, onChange: changeItemInputKey }), _jsx("input", { className: "panel__content__form__section__input panel__content__form__section__list__new-item__input", spellCheck: false, disabled: isReadOnly, value: itemValue, onChange: changeItemInputValue }), _jsxs("div", { className: "panel__content__form__section__list__new-item__actions", children: [_jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isReadOnly ||
Array.from(Object.keys(mapValues)).includes(itemKey), onClick: addValue, tabIndex: -1, children: "Save" }), _jsx("button", { className: "panel__content__form__section__list__new-item__cancel-btn btn btn--dark", disabled: isReadOnly, onClick: hideAddOrEditItemInput, tabIndex: -1, children: "Cancel" })] })] }))] }), showEditInput !== true && (_jsx("div", { className: "panel__content__form__section__list__new-item__add", children: _jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isReadOnly, onClick: showAddItemInput, tabIndex: -1, children: "Add Value" }) }))] })] }));
});
export const GenerationPropertyEditor = observer((props) => {
const { property, getConfigValue, isReadOnly, update } = props;
switch (property.type) {
case GenerationPropertyItemType.STRING:
return (_jsx(GenerationStringPropertyEditor, { getConfigValue: getConfigValue, isReadOnly: isReadOnly, update: update, property: property }));
case GenerationPropertyItemType.INTEGER:
return (_jsx(GenerationIntegerPropertyEditor, { getConfigValue: getConfigValue, isReadOnly: isReadOnly, update: update, property: property }));
case GenerationPropertyItemType.BOOLEAN:
return (_jsx(GenerationBooleanPropertyEditor, { getConfigValue: getConfigValue, isReadOnly: isReadOnly, update: update, property: property }));
case GenerationPropertyItemType.ENUM:
return (_jsx(GenerationEnumPropertyEditor, { getConfigValue: getConfigValue, isReadOnly: isReadOnly, update: update, property: property }));
case GenerationPropertyItemType.ARRAY:
return (_jsx(GenerationArrayPropertyEditor, { getConfigValue: getConfigValue, isReadOnly: isReadOnly, update: update, property: property }));
case GenerationPropertyItemType.MAP:
return (_jsx(GenerationMapPropertyEditor, { getConfigValue: getConfigValue, isReadOnly: isReadOnly, update: update, property: property }));
default:
throw new UnsupportedOperationError(`Can't generate editor for artifact generation property of type '${property.type}'`);
}
});
export const FileGenerationConfigurationEditor = observer((props) => {
const { isReadOnly, fileGenerationState, elementGenerationState } = props;
const editorStore = useEditorStore();
const applicationStore = useApplicationStore();
const fileGeneration = fileGenerationState.fileGeneration;
const fileGenerationConfiguration = editorStore.graphState.graphGenerationState.getFileGenerationConfiguration(fileGeneration.type).properties;
const debouncedRegenerate = useMemo(() => debounce(() => flowResult(fileGenerationState.generate()), 500), [fileGenerationState]);
const update = (generationProperty, newValue) => {
debouncedRegenerate.cancel();
fileGenerationState.updateFileGenerationParameters(fileGeneration, generationProperty, newValue);
debouncedRegenerate()?.catch(applicationStore.alertUnhandledError);
};
const showFileGenerationModal = () => {
elementGenerationState?.setShowNewFileGenerationModal(true);
};
const resetDefaultConfiguration = () => {
debouncedRegenerate.cancel();
fileGenerationState.resetFileGeneration();
debouncedRegenerate()?.catch(applicationStore.alertUnhandledError);
};
const changeValue = (event) => {
const val = event.target.value;
fileGeneration_setGenerationOutputPath(fileGeneration, val || undefined);
};
// Drag and Drop
const handleDrop = useCallback((item) => {
const element = item.data.packageableElement;
if (!isReadOnly &&
!elementGenerationState &&
!fileGenerationState.getScopeElement(element)) {
debouncedRegenerate.cancel();
fileGeneration_addScopeElement(fileGeneration, PackageableElementExplicitReference.create(element));
debouncedRegenerate()?.catch(applicationStore.alertUnhandledError);
}
}, [
applicationStore.alertUnhandledError,
debouncedRegenerate,
elementGenerationState,
fileGeneration,
fileGenerationState,
isReadOnly,
]);
const [{ isScopeElementDragOver }, scopeElementDropRef] = useDrop(() => ({
accept: [
CORE_DND_TYPE.PROJECT_EXPLORER_PACKAGE,
CORE_DND_TYPE.PROJECT_EXPLORER_CLASS,
CORE_DND_TYPE.PROJECT_EXPLORER_ENUMERATION,
],
drop: (item) => handleDrop(item),
collect: (monitor) => ({
isScopeElementDragOver: monitor.isOver({ shallow: true }),
}),
}), [handleDrop]);
const getConfigValue = (name) => getNullableFileGenerationConfig(fileGeneration, name)?.value;
return (_jsxs("div", { className: "panel file-generation-editor__configuration", children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: `${fileGeneration.type} configuration` }) }), _jsxs("div", { className: "panel__header__actions", children: [_jsx("button", { className: "panel__header__action file-generation-editor__configuration__reset-btn", tabIndex: -1, disabled: isReadOnly || !fileGeneration.configurationProperties.length, onClick: resetDefaultConfiguration, title: 'Reset to default configuration', children: _jsx(RefreshIcon, {}) }), Boolean(elementGenerationState) && (_jsx("button", { className: "panel__header__action", tabIndex: -1, disabled: isReadOnly, onClick: showFileGenerationModal, title: 'Promote File Generation Specification...', children: _jsx(SaveIcon, {}) }))] })] }), _jsxs("div", { ref: scopeElementDropRef, className: "panel__content dnd__dropzone", children: [_jsx("div", { className: clsx({
dnd__overlay: isScopeElementDragOver &&
!elementGenerationState &&
!isReadOnly,
}) }), _jsxs("div", { className: "file-generation-editor__configuration__content", children: [_jsx(FileGenerationScopeEditor, { fileGenerationState: fileGenerationState, regenerate: debouncedRegenerate, isReadOnly: isReadOnly || Boolean(elementGenerationState) }), _jsxs("div", { className: "panel__content__form__section", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Generation Output Path" }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: "Specifies the root path where files will be generated. Defaults to the file specification path" }), _jsx("input", { className: "panel__content__form__section__input", spellCheck: false, disabled: isReadOnly, value: fileGeneration.generationOutputPath ?? '', onChange: changeValue })] }), fileGenerationConfiguration.map((abstractGenerationProperty) => (_jsx(GenerationPropertyEditor, { update: update, isReadOnly: isReadOnly, getConfigValue: getConfigValue, property: abstractGenerationProperty }, abstractGenerationProperty.name +
abstractGenerationProperty.type)))] })] })] }));
});
export const FileGenerationEditor = observer(() => {
const editorStore = useEditorStore();
const fileGenerationEditorState = editorStore.getCurrentEditorState(FileGenerationEditorState);
const fileGeneration = fileGenerationEditorState.fileGeneration;
const isReadOnly = fileGenerationEditorState.isReadOnly;
return (_jsx("div", { className: "file-generation-editor", children: _jsxs("div", { className: "panel", children: [_jsx("div", { className: "panel__header", children: _jsxs("div", { className: "panel__header__title", children: [isReadOnly && (_jsx("div", { className: "uml-element-editor__header__lock", children: _jsx(LockIcon, {}) })), _jsx("div", { className: "panel__header__title__label", children: "file generation" }), _jsx("div", { className: "panel__header__title__content", children: fileGeneration.name })] }) }), _jsx("div", { className: "panel__content file-generation-editor__content", children: _jsxs(ResizablePanelGroup, { orientation: "vertical", children: [_jsx(ResizablePanel, { size: 400, minSize: 300, children: _jsx(FileGenerationConfigurationEditor, { isReadOnly: isReadOnly, fileGenerationState: fileGenerationEditorState.fileGenerationState }) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-200)" }) }), _jsx(ResizablePanel, { children: _jsx(GenerationResultViewer, { fileGenerationState: fileGenerationEditorState.fileGenerationState }) })] }) })] }) }));
});
//# sourceMappingURL=FileGenerationEditor.js.map