diff --git a/package-lock.json b/package-lock.json index f49e14a6db..40a8b17735 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5827,6 +5827,11 @@ "react-dom": ">=17.0" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6572,6 +6577,22 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-window": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", + "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -8515,6 +8536,7 @@ "react-dom": "18.3.1", "react-draggable": "4.4.6", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "subscriptions-transport-ws": "0.11.0", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -8586,6 +8608,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -8631,6 +8654,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", "xstate": "4.32.1" @@ -8685,6 +8709,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -8731,6 +8756,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", "xstate": "4.32.1" @@ -8785,6 +8811,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -8831,6 +8858,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", "xstate": "4.32.1" @@ -9733,6 +9761,7 @@ "react-dom": "18.3.1", "react-draggable": "4.4.6", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "subscriptions-transport-ws": "0.11.0", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -9788,6 +9817,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -9843,6 +9873,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -9898,6 +9929,7 @@ "react-draggable": "4.4.6", "react-grid-layout": "1.4.4", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "svg-path-parser": "1.1.0", "tss-react": "4.9.7", @@ -13692,6 +13724,11 @@ "highlight-words": "1.2.2" } }, + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -14217,6 +14254,15 @@ "prop-types": "^15.6.2" } }, + "react-window": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", + "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", + "requires": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + } + }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", diff --git a/packages/sirius-web/frontend/sirius-web-application/package.json b/packages/sirius-web/frontend/sirius-web-application/package.json index 49e163504c..0495b17809 100644 --- a/packages/sirius-web/frontend/sirius-web-application/package.json +++ b/packages/sirius-web/frontend/sirius-web-application/package.json @@ -67,6 +67,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "@xyflow/react": "12.2.1", "tss-react": "4.9.7", "xstate": "4.32.1" @@ -116,6 +117,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "xstate": "4.32.1", "typescript": "5.4.5", diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.tsx index e31264a90e..4287b54500 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.tsx @@ -23,6 +23,7 @@ import { SxProps, Theme, useTheme } from '@mui/material/styles'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { ComponentType, useState } from 'react'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; import { ExpressionAreaProps, ExpressionAreaState, @@ -37,9 +38,10 @@ import { GQLObjectsExpressionResult, GQLStringExpressionResult, } from './useEvaluateExpression.types'; +import { useResultAreaSize } from './useResultAreaSize'; export const QueryView = ({ editingContextId, readOnly }: WorkbenchViewComponentProps) => { - const interpreterStyle: SxProps = (theme) => ({ + const queryViewStyle: SxProps = (theme) => ({ display: 'flex', flexDirection: 'column', gap: theme.spacing(2), @@ -47,13 +49,14 @@ export const QueryView = ({ editingContextId, readOnly }: WorkbenchViewComponent }); const { evaluateExpression, loading, result } = useEvaluateExpression(); - const handleEvaluateExpression = (expression: string) => evaluateExpression(editingContextId, expression); + const { ref, width, height } = useResultAreaSize(); + return ( - + - + ); }; @@ -84,7 +87,7 @@ const ExpressionArea = ({ onEvaluateExpression, disabled }: ExpressionAreaProps) }; return ( -
+
Expression @@ -143,35 +146,51 @@ const ObjectExpressionResultViewer = ({ result }: ExpressionResultViewerProps) = ); }; -const ObjectsExpressionResultViewer = ({ result }: ExpressionResultViewerProps) => { +const ObjectRow = ({ data, index, style }: ListChildComponentProps) => { + const listItemStyle: SxProps = (theme) => ({ + gap: theme.spacing(2), + }); + const listItemIconStyle: SxProps = () => ({ + minWidth: '0px', + }); + + const object = data[index]; + + return ( + + + + + + + ); +}; + +const ObjectsExpressionResultViewer = ({ result, width, height }: ExpressionResultViewerProps) => { if (result.__typename !== 'ObjectsExpressionResult') { return null; } const { objectsValue } = result as GQLObjectsExpressionResult; - const listItemStyle: SxProps = (theme) => ({ - gap: theme.spacing(2), - }); - const listItemIconStyle: SxProps = () => ({ - minWidth: '0px', + const listStyle: SxProps = (theme) => ({ + border: `1px solid ${theme.palette.divider}`, }); + return ( A collection of {objectsValue.length} object{objectsValue.length > 1 ? 's' : ''} has been returned - - {objectsValue.map((object) => { - return ( - - - - - - - ); - })} + + + {ObjectRow} + ); @@ -276,7 +295,7 @@ const LoadingViewer = () => { ); }; -const ResultArea = ({ loading, payload }: ResultAreaProps) => { +const ResultArea = ({ loading, payload, width, height }: ResultAreaProps) => { const resultAreaToolbarStyle: SxProps = { display: 'flex', flexDirection: 'row', @@ -295,12 +314,12 @@ const ResultArea = ({ loading, payload }: ResultAreaProps) => { } else if (payload) { const Viewer = resultType2viewer[payload.result.__typename]; if (Viewer) { - content = ; + content = ; } } return ( -
+
Evaluation result diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.types.ts b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.types.ts index ed41352146..14f3159a0c 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.types.ts +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/QueryView.types.ts @@ -25,8 +25,12 @@ export interface ExpressionAreaState { export interface ResultAreaProps { loading: boolean; payload: GQLEvaluateExpressionSuccessPayload | null; + height: number | null; + width: number | null; } export interface ExpressionResultViewerProps { result: GQLExpressionResult; + height: number | null; + width: number | null; } diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/useResultAreaSize.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/useResultAreaSize.tsx new file mode 100644 index 0000000000..9ec49ec59e --- /dev/null +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/useResultAreaSize.tsx @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2025 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +import { useEffect, useRef, useState } from 'react'; +import { UseResultAreaSizeState, UseResultAreaSizeValue } from './useResultAreaSize.types'; + +export const useResultAreaSize = (): UseResultAreaSizeValue => { + const [state, setState] = useState({ + height: null, + width: null, + }); + + const ref = useRef(null); + + useEffect(() => { + if (!ref.current) { + return () => {}; + } + const parentDiv = ref.current; + const expressionDiv: HTMLDivElement | null = parentDiv.querySelector('[data-role="expression"]'); + const resultDiv: HTMLDivElement | null = parentDiv.querySelector('[data-role="result"]'); + + const resizeObserver = new ResizeObserver(() => { + if (expressionDiv && resultDiv) { + const height = (parentDiv.offsetHeight - expressionDiv.offsetHeight) * 0.85; + const width = expressionDiv.offsetWidth; + + setState((prevState) => ({ + ...prevState, + height, + width, + })); + } + }); + + resizeObserver.observe(ref.current); + + return () => resizeObserver.disconnect(); + }, [ref.current]); + + return { + ref, + height: state.height, + width: state.width, + }; +}; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/useResultAreaSize.types.ts b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/useResultAreaSize.types.ts new file mode 100644 index 0000000000..ae2963aecf --- /dev/null +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/workbench-views/query/useResultAreaSize.types.ts @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2025 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +import { MutableRefObject } from 'react'; + +export interface UseResultAreaSizeValue { + ref: MutableRefObject; + height: number | null; + width: number | null; +} + +export interface UseResultAreaSizeState { + height: number | null; + width: number | null; +} diff --git a/packages/sirius-web/frontend/sirius-web-papaya/package.json b/packages/sirius-web/frontend/sirius-web-papaya/package.json index 99e537de75..fce35f01f4 100644 --- a/packages/sirius-web/frontend/sirius-web-papaya/package.json +++ b/packages/sirius-web/frontend/sirius-web-papaya/package.json @@ -68,6 +68,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "@xyflow/react": "12.2.1", "tss-react": "4.9.7", "xstate": "4.32.1" @@ -118,6 +119,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "xstate": "4.32.1", "tss-react": "4.9.7", diff --git a/packages/sirius-web/frontend/sirius-web-table/package.json b/packages/sirius-web/frontend/sirius-web-table/package.json index 12256c071f..2f3ba0ff06 100644 --- a/packages/sirius-web/frontend/sirius-web-table/package.json +++ b/packages/sirius-web/frontend/sirius-web-table/package.json @@ -68,6 +68,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "@xyflow/react": "12.2.1", "tss-react": "4.9.7", "xstate": "4.32.1" @@ -118,6 +119,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "rollup-plugin-peer-deps-external": "2.2.4", "xstate": "4.32.1", "tss-react": "4.9.7", diff --git a/packages/sirius-web/frontend/sirius-web/package.json b/packages/sirius-web/frontend/sirius-web/package.json index 75ace18ed6..e660beced8 100644 --- a/packages/sirius-web/frontend/sirius-web/package.json +++ b/packages/sirius-web/frontend/sirius-web/package.json @@ -50,6 +50,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "6.26.0", + "react-window": "1.8.11", "@xyflow/react": "12.2.1", "subscriptions-transport-ws": "0.11.0", "tss-react": "4.9.7",