Skip to content

Commit

Permalink
moved diagram editor from legend-studio to core vscode client (finos#100
Browse files Browse the repository at this point in the history
)
  • Loading branch information
sameersaini authored Sep 6, 2024
1 parent 17cd3ec commit 03821db
Show file tree
Hide file tree
Showing 10 changed files with 995 additions and 205 deletions.
383 changes: 201 additions & 182 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,15 @@
"@ag-grid-enterprise/menu": "30.2.0",
"@ag-grid-enterprise/row-grouping": "30.2.0",
"@ag-grid-enterprise/server-side-row-model": "30.2.0",
"@finos/legend-vscode-extension-dependencies": "3.0.0",
"@finos/legend-vscode-extension-dependencies": "4.0.0",
"@types/vscode": "1.83.0",
"glob": "11.0.0",
"path": "0.12.7",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-select": "5.8.0",
"mobx": "6.13.1",
"mobx-react-lite": "4.0.7",
"serializr": "3.0.2",
"vscode-languageclient": "9.0.1"
},
Expand Down Expand Up @@ -358,7 +360,7 @@
"typescript": "5.2.2",
"util": "0.12.5",
"vm-browserify": "1.1.2",
"webpack": "5.93.0",
"webpack": "5.94.0",
"webpack-cli": "5.1.4",
"yo": "4.3.1"
},
Expand Down
112 changes: 112 additions & 0 deletions src/components/diagram/DiagramEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2023-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 {
getDiagram,
getPureGraph,
type Entity,
LegendStyleProvider,
} from '@finos/legend-vscode-extension-dependencies';
import { useRef, useState, useEffect } from 'react';
import {
GET_PROJECT_ENTITIES,
GET_PROJECT_ENTITIES_RESPONSE,
} from '../../utils/Const';
import { observer } from 'mobx-react-lite';
import { postMessage } from '../../utils/VsCodeUtils';

import type { DiagramEditorState } from '../../stores/DiagramEditorState';
import { DiagramEditorHeader } from './DiagramEditorHeader';
import { DiagramEditorToolPanel } from './DiagramEditorToolPanel';
import { DiagramEditorCanvas } from './DiagramEditorCanvas';

export const DiagramEditor = observer(
({ diagramEditorState }: { diagramEditorState: DiagramEditorState }) => {
const diagramCanvasRef = useRef<HTMLDivElement>(null);
const { diagramId } = diagramEditorState;
const [entities, setEntities] = useState<Entity[]>([]);
const [error, setError] = useState<string | null>();

useEffect(() => {
postMessage({
command: GET_PROJECT_ENTITIES,
});
}, [diagramId]);

window.addEventListener(
'message',
(event: MessageEvent<{ result: Entity[]; command: string }>) => {
const message = event.data;
if (message.command === GET_PROJECT_ENTITIES_RESPONSE) {
const es: Entity[] = message.result;
setEntities(es);
diagramEditorState.setEntities(es);
}
},
);

useEffect(() => {
if (entities.length && diagramId) {
getPureGraph(entities, [])
.then((pureModel) => {
const diagram = getDiagram(diagramId, pureModel);
diagramEditorState.setDiagram(diagram);
diagramEditorState.setGraph(pureModel);
setError(null);
})
.catch((e) => {
setError(e.message);
});
}
}, [entities, diagramId, diagramEditorState]);

return (
<LegendStyleProvider>
<div className="diagram-editor">
{error ? (
<div className="diagram-editor__error">
<span>Something went wrong. Diagram cannot be created.</span>
<span
className="diagram-editor__error__details"
title={`${error}`}
>
Error Details.
</span>
</div>
) : (
<>
{diagramEditorState.isDiagramRendererInitialized && (
<DiagramEditorHeader diagramEditorState={diagramEditorState} />
)}
<div className="diagram-editor__content">
<div className="diagram-editor__stage">
{diagramEditorState.isDiagramRendererInitialized && (
<DiagramEditorToolPanel
diagramEditorState={diagramEditorState}
/>
)}
<DiagramEditorCanvas
diagramEditorState={diagramEditorState}
ref={diagramCanvasRef}
/>
</div>
</div>
</>
)}
</div>
</LegendStyleProvider>
);
},
);
151 changes: 151 additions & 0 deletions src/components/diagram/DiagramEditorCanvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* Copyright (c) 2023-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 {
type Diagram,
V1_diagramModelSchema,
V1_transformDiagram,
DiagramRenderer,
Point,
Class,
useResizeDetector,
clsx,
} from '@finos/legend-vscode-extension-dependencies';
import {
forwardRef,
useEffect,
type DragEvent,
type KeyboardEvent,
} from 'react';
import { WRITE_ENTITY, DIAGRAM_DROP_CLASS_ERROR } from '../../utils/Const';
import { observer } from 'mobx-react-lite';
import { flowResult } from 'mobx';
import { serialize } from 'serializr';
import { postMessage } from '../../utils/VsCodeUtils';
import type { DiagramEditorState } from '../../stores/DiagramEditorState';

export const DiagramEditorCanvas = observer(
forwardRef<
HTMLDivElement,
{
diagramEditorState: DiagramEditorState;
}
>(function DiagramEditorDiagramCanvas(props, ref) {
const { diagramEditorState } = props;
const diagram = diagramEditorState.diagram;
const diagramCanvasRef = ref as React.MutableRefObject<HTMLDivElement>;

const { width, height } = useResizeDetector<HTMLDivElement>({
refreshMode: 'debounce',
refreshRate: 50,
targetRef: diagramCanvasRef,
});

useEffect(() => {
if (diagram) {
const renderer = new DiagramRenderer(diagramCanvasRef.current, diagram);
diagramEditorState.setRenderer(renderer);
diagramEditorState.setupRenderer();
renderer.render({ initial: true });
}
}, [diagramCanvasRef, diagramEditorState, diagram]);

useEffect(() => {
// since after the diagram render is initialized, we start
// showing the toolbar and the header, which causes the auto-zoom fit
// to be off, we need to call this method again
if (diagramEditorState.isDiagramRendererInitialized) {
diagramEditorState.renderer.render({ initial: true });
}
}, [diagramEditorState, diagramEditorState.isDiagramRendererInitialized]);

useEffect(() => {
if (diagramEditorState.isDiagramRendererInitialized) {
diagramEditorState.renderer.refresh();
}
}, [diagramEditorState, width, height]);

const dropTarget = document.getElementById('root') ?? document.body;

dropTarget.addEventListener('dragover', (event) => {
// accept any DnD
event.preventDefault();
});

const drop = (event: DragEvent) => {

Check warning on line 87 in src/components/diagram/DiagramEditorCanvas.tsx

View workflow job for this annotation

GitHub Actions / Run Code Checks

Missing return type on function
event.preventDefault();
const droppedEntityIds: string[] = (
JSON.parse(
event.dataTransfer.getData(
'application/vnd.code.tree.legendConceptTree',
),
).itemHandles as string[]
).map((item) => item.split('/')[1] ?? '');
const position =
diagramEditorState.renderer.canvasCoordinateToModelCoordinate(
diagramEditorState.renderer.eventCoordinateToCanvasCoordinate(
new Point(event.clientX, event.clientY),
),
);

droppedEntityIds
.filter((entityId) => {
const isClassInstance =
diagramEditorState.graph?.getElement(entityId) instanceof Class;
if (!isClassInstance) {
postMessage({
command: DIAGRAM_DROP_CLASS_ERROR,
});
}
return isClassInstance;
})
.forEach((entityId) => {
flowResult(diagramEditorState.addClassView(entityId, position)).catch(
// eslint-disable-next-line no-console
(error: unknown) => console.error(error),
);
});
};

const handleKeyDown = (event: KeyboardEvent) => {

Check warning on line 122 in src/components/diagram/DiagramEditorCanvas.tsx

View workflow job for this annotation

GitHub Actions / Run Code Checks

Missing return type on function
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
event.preventDefault();

postMessage({
command: WRITE_ENTITY,
msg: serialize(
V1_diagramModelSchema,
V1_transformDiagram(
diagramEditorState._renderer?.diagram as Diagram,
),
),
});
}
};

return (
<div
onDrop={drop}
onKeyDown={handleKeyDown}
ref={diagramCanvasRef}
className={clsx(
'diagram-canvas diagram-editor__canvas',
diagramEditorState.diagramCursorClass,
)}
tabIndex={0}
/>
);
}),
);
Loading

0 comments on commit 03821db

Please sign in to comment.