@finos/legend-application-studio
Version:
Legend Studio application core
195 lines • 10 kB
JavaScript
/**
* 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 { Association, Class, ConcreteFunctionDefinition, Database, DataElement, Enumeration, FileGenerationSpecification, FlatData, GenerationSpecification, Mapping, Measure, PackageableConnection, PackageableRuntime, PrimitiveType, Profile, Service, INTERNAL__UnknownFunctionActivator, SnowflakeApp, HostedService, } from '@finos/legend-graph';
import { guaranteeType, isNonNullable, UnsupportedOperationError, } from '@finos/legend-shared';
import { makeObservable, action, observable } from 'mobx';
import { ClassEditorState } from './editor-state/element-editor-state/ClassEditorState.js';
import { PackageableConnectionEditorState } from './editor-state/element-editor-state/connection/ConnectionEditorState.js';
import { PackageableDataEditorState } from './editor-state/element-editor-state/data/DataEditorState.js';
import { ElementEditorState } from './editor-state/element-editor-state/ElementEditorState.js';
import { FileGenerationEditorState } from './editor-state/element-editor-state/FileGenerationEditorState.js';
import { FunctionEditorState } from './editor-state/element-editor-state/FunctionEditorState.js';
import { MappingEditorState } from './editor-state/element-editor-state/mapping/MappingEditorState.js';
import { PackageableRuntimeEditorState } from './editor-state/element-editor-state/RuntimeEditorState.js';
import { ServiceEditorState } from './editor-state/element-editor-state/service/ServiceEditorState.js';
import { UMLEditorState } from './editor-state/element-editor-state/UMLEditorState.js';
import { EntityDiffViewerState } from './editor-state/entity-diff-editor-state/EntityDiffEditorState.js';
import { GenerationSpecificationEditorState } from './editor-state/GenerationSpecificationEditorState.js';
import { UnsupportedElementEditorState } from './editor-state/UnsupportedElementEditorState.js';
import { TabManagerState } from '@finos/legend-lego/application';
import { INTERNAL__UnknownFunctionActivatorEdtiorState } from './editor-state/element-editor-state/function-activator/INTERNAL__UnknownFunctionActivatorEditorState.js';
import { SnowflakeAppFunctionActivatorEdtiorState } from './editor-state/element-editor-state/function-activator/SnowflakeAppFunctionActivatorEditorState.js';
import { HostedServiceFunctionActivatorEditorState } from './editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.js';
import { ArtifactGenerationViewerState } from './editor-state/ArtifactGenerationViewerState.js';
export class EditorTabManagerState extends TabManagerState {
editorStore;
cachedTabs;
constructor(editorStore) {
super();
makeObservable(this, {
refreshCurrentEntityDiffViewer: action,
cachedTabs: observable,
recoverTabs: action,
clearTabCache: action,
cacheAndClose: action,
});
this.editorStore = editorStore;
}
get dndType() {
return 'editor.tab-manager.tab';
}
/**
* Here we store the element paths of the
* elements editors as element paths don't refer to the actual graph. We can find the element
* from the new graph that is built by using element path and can reprocess the element editor states.
* The other kind of editors we reprocess are file generation editors, we store them as is as they don't
* hold any reference to the actual graph.
*/
cacheAndClose(options) {
const openedTabPaths = [];
this.tabs.forEach((state) => {
if (state instanceof ElementEditorState) {
openedTabPaths.push(state.elementPath);
}
});
// Only stores editor state for file generation editors as they don't hold any references to the
// actual graph.
const currentTabState = this.currentTab instanceof ElementEditorState ||
(options?.cacheGeneration &&
this.currentTab instanceof ArtifactGenerationViewerState)
? undefined
: this.currentTab;
const currentTabElementPath = this.currentTab instanceof ElementEditorState
? this.currentTab.elementPath
: undefined;
this.cachedTabs = {
openedTabEditorPaths: openedTabPaths,
currentTabState,
currentTabElementPath,
};
this.closeAllTabs();
}
clearTabCache() {
this.cachedTabs = undefined;
}
getCurrentEditorState(clazz) {
return guaranteeType(this.currentTab, clazz, `Current editor state is not of the specified type (this is likely caused by calling this method at the wrong place)`);
}
createElementEditorState(element) {
if (element instanceof PrimitiveType) {
throw new UnsupportedOperationError(`Can't create element state for primitive type`);
}
else if (element instanceof Class) {
return new ClassEditorState(this.editorStore, element);
}
else if (element instanceof Association ||
element instanceof Enumeration ||
element instanceof Profile) {
return new UMLEditorState(this.editorStore, element);
}
else if (element instanceof ConcreteFunctionDefinition) {
return new FunctionEditorState(this.editorStore, element);
}
else if (element instanceof Measure ||
element instanceof Database ||
element instanceof FlatData) {
return new UnsupportedElementEditorState(this.editorStore, element);
}
else if (element instanceof PackageableRuntime) {
return new PackageableRuntimeEditorState(this.editorStore, element);
}
else if (element instanceof PackageableConnection) {
return new PackageableConnectionEditorState(this.editorStore, element);
}
else if (element instanceof Mapping) {
return new MappingEditorState(this.editorStore, element);
}
else if (element instanceof Service) {
return new ServiceEditorState(this.editorStore, element);
}
else if (element instanceof GenerationSpecification) {
return new GenerationSpecificationEditorState(this.editorStore, element);
}
else if (element instanceof FileGenerationSpecification) {
return new FileGenerationEditorState(this.editorStore, element);
}
else if (element instanceof DataElement) {
return new PackageableDataEditorState(this.editorStore, element);
}
else if (element instanceof SnowflakeApp) {
return new SnowflakeAppFunctionActivatorEdtiorState(this.editorStore, element);
}
else if (element instanceof HostedService) {
return new HostedServiceFunctionActivatorEditorState(this.editorStore, element);
}
else if (element instanceof INTERNAL__UnknownFunctionActivator) {
return new INTERNAL__UnknownFunctionActivatorEdtiorState(this.editorStore, element);
}
const extraElementEditorStateCreators = this.editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraElementEditorStateCreators?.() ?? []);
for (const creator of extraElementEditorStateCreators) {
const elementEditorState = creator(this.editorStore, element);
if (elementEditorState) {
return elementEditorState;
}
}
return new UnsupportedElementEditorState(this.editorStore, element);
}
refreshCurrentEntityDiffViewer() {
if (this.currentTab instanceof EntityDiffViewerState) {
this.currentTab.refresh();
}
}
/**
* After we do graph processing, we want to recover the tabs
*
* NOTE: we have to flush old tab states to ensure, we have no reference
* to the old graph to avoid memory leak. Here, we only recreate element
* editor tabs, and keep special editors open. Some tabs will be closed.
* But we **ABSOLUTELY** must never make this behavior customizable by extension
* i.e. we should not allow extension control if a tab should be kept open, because
* the plugin implementation could accidentally reference older graph and therefore
* cause memory issues
*
* See https://github.com/finos/legend-studio/issues/1713
*/
recoverTabs = () => {
if (this.cachedTabs) {
this.tabs = this.cachedTabs.openedTabEditorPaths
.map((editorPath) => {
const correspondingElement = this.editorStore.graphManagerState.graph.getNullableElement(editorPath);
if (correspondingElement) {
return this.createElementEditorState(correspondingElement);
}
return undefined;
})
.filter(isNonNullable);
this.setCurrentTab(this.findCurrentTab(this.cachedTabs.currentTabState, this.cachedTabs.currentTabElementPath));
this.clearTabCache();
}
};
findCurrentTab = (tab, tabElementPath) => {
if (tab) {
return tab;
}
else {
return this.tabs.find((editorState) => editorState instanceof ElementEditorState &&
editorState.elementPath === tabElementPath);
}
};
}
//# sourceMappingURL=EditorTabManagerState.js.map