From 12dfd1c3cb82cba2ec9c3fc5b8cdd7945d35deaf Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:58:44 +0300 Subject: [PATCH] Improve graph UI --- rsconcept/frontend/package-lock.json | 89 +++++++++---------- rsconcept/frontend/package.json | 10 +-- .../frontend/src/components/ui/GraphUI.tsx | 14 ++- .../EditorTermGraph/EditorTermGraph.tsx | 4 +- .../RSFormPage/EditorTermGraph/TermGraph.tsx | 36 +++++--- rsconcept/frontend/src/utils/constants.ts | 5 +- 6 files changed, 88 insertions(+), 70 deletions(-) diff --git a/rsconcept/frontend/package-lock.json b/rsconcept/frontend/package-lock.json index 6dbf58b3..cff4db6a 100644 --- a/rsconcept/frontend/package-lock.json +++ b/rsconcept/frontend/package-lock.json @@ -20,7 +20,7 @@ "react-dom": "^18.2.0", "react-error-boundary": "^4.0.13", "react-icons": "^4.12.0", - "react-intl": "^6.6.4", + "react-intl": "^6.6.5", "react-loader-spinner": "^5.4.5", "react-pdf": "^7.7.1", "react-router-dom": "^6.22.3", @@ -28,14 +28,14 @@ "react-tabs": "^6.0.2", "react-toastify": "^9.1.3", "react-tooltip": "^5.26.3", - "reagraph": "^4.15.27", + "reagraph": "^4.16.0", "use-debounce": "^10.0.0" }, "devDependencies": { "@lezer/generator": "^1.7.0", "@types/jest": "^29.5.12", - "@types/node": "^20.12.4", - "@types/react": "^18.2.74", + "@types/node": "^20.12.7", + "@types/react": "^18.2.75", "@types/react-dom": "^18.2.24", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -50,7 +50,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "ts-jest": "^29.1.2", - "typescript": "^5.4.3", + "typescript": "^5.4.4", "vite": "^4.5.3" } }, @@ -691,9 +691,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.1.tgz", - "integrity": "sha512-wLw0t3R9AwOSQThdZ5Onw8QQtem5asE7+bPlnzc57eubPqiuJKIzwjMZ+C42vQett+iva+J8VgFV4RYWDBh5FA==", + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.2.tgz", + "integrity": "sha512-j6V48PlFC/O7ERAR5vRW5QKDdchzmyyfojDdt+zPsB0YXoWgcjlC1IWjmlYfx08aQZ3HN5BtALcgGgtSKGMe7A==", "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", @@ -2804,9 +2804,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -2828,9 +2828,9 @@ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { - "version": "18.2.74", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.74.tgz", - "integrity": "sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==", + "version": "18.2.75", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz", + "integrity": "sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2891,9 +2891,9 @@ } }, "node_modules/@types/webxr": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.14.tgz", - "integrity": "sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA==" + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.15.tgz", + "integrity": "sha512-nC9116Gd4N+CqTxqo6gvCfhAMAzgRcfS8ZsciNodHq8uwW4JCVKwhagw8yN0XmC7mHrLnWqniJpoVEiR+72Drw==" }, "node_modules/@types/yargs": { "version": "17.0.32", @@ -3779,9 +3779,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001605", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", - "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", + "version": "1.0.30001608", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz", + "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==", "funding": [ { "type": "opencollective", @@ -4511,9 +4511,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.726", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz", - "integrity": "sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ==" + "version": "1.4.731", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.731.tgz", + "integrity": "sha512-+TqVfZjpRz2V/5SPpmJxq9qK620SC5SqCnxQIOi7i/U08ZDcTpKbT7Xjj9FU5CbXTMUb4fywbIr8C7cGv4hcjw==" }, "node_modules/ellipsize": { "version": "0.5.1", @@ -5929,22 +5929,15 @@ } }, "node_modules/its-fine": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.3.tgz", - "integrity": "sha512-mncCA+yb6tuh5zK26cHqKlsSyxm4zdm4YgJpxycyx6p9fgxgK5PLu3iDVpKhzTn57Yrv3jk/r0aK0RFTT1OjFw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.0.tgz", + "integrity": "sha512-518vLgHK/dgGxyZj4MdLrDRryziqR1M+JbVtjw1tmdgvZQYsJvB2Leoe2qFOHPalZ5KiAOK18wTmIC0XszYc0w==", "dependencies": { - "@types/react-reconciler": "^0.28.0" + "@types/react": "*", + "@types/react-reconciler": "*" }, "peerDependencies": { - "react": ">=18.0" - } - }, - "node_modules/its-fine/node_modules/@types/react-reconciler": { - "version": "0.28.8", - "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.8.tgz", - "integrity": "sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==", - "dependencies": { - "@types/react": "*" + "react": ">=16.8" } }, "node_modules/jackspeak": { @@ -7622,9 +7615,9 @@ } }, "node_modules/just-types/node_modules/@types/node": { - "version": "18.19.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz", - "integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==", + "version": "18.19.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", + "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", "dependencies": { "undici-types": "~5.26.4" } @@ -8753,9 +8746,9 @@ } }, "node_modules/react-intl": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.4.tgz", - "integrity": "sha512-bD+7hVTX3zBFI3z/ffIVZrNkCVK0/sQguQ/DqW5uZ6JFWsuiwOieVcLnmtFiUgMQZLdmwNl2Ur5c10RaO7NWBQ==", + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.5.tgz", + "integrity": "sha512-OErDPbGqus0QKVj77MGCC9Plbnys3CDQrq6Lw41c60pmeTdn41AhoS1SIzXG6SUlyF7qNN2AVqfrrIvHUgSyLQ==", "dependencies": { "@formatjs/ecma402-abstract": "1.18.2", "@formatjs/icu-messageformat-parser": "2.7.6", @@ -9034,9 +9027,9 @@ } }, "node_modules/reagraph": { - "version": "4.15.27", - "resolved": "https://registry.npmjs.org/reagraph/-/reagraph-4.15.27.tgz", - "integrity": "sha512-BoYWSFdbxeLkEL4lAM9Y/ey0L+liY1is/3+dxZ4pvhlVj4f9RIWZh1IqILY+7t2mRYts5RInsgz0ZAV4t4tIJw==", + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/reagraph/-/reagraph-4.16.0.tgz", + "integrity": "sha512-PCmEfq/UOPies6EqXJQGGi7N1n+687rCQ/WsdhuuglWHgkEhgSVGmtpxWVMOqkCs2P7fp0UzAQOyPy4YhdbQtw==", "dependencies": { "@react-spring/three": "9.6.1", "@react-three/fiber": "8.13.5", @@ -10023,9 +10016,9 @@ } }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/rsconcept/frontend/package.json b/rsconcept/frontend/package.json index c36f8afb..ba401204 100644 --- a/rsconcept/frontend/package.json +++ b/rsconcept/frontend/package.json @@ -24,7 +24,7 @@ "react-dom": "^18.2.0", "react-error-boundary": "^4.0.13", "react-icons": "^4.12.0", - "react-intl": "^6.6.4", + "react-intl": "^6.6.5", "react-loader-spinner": "^5.4.5", "react-pdf": "^7.7.1", "react-router-dom": "^6.22.3", @@ -32,14 +32,14 @@ "react-tabs": "^6.0.2", "react-toastify": "^9.1.3", "react-tooltip": "^5.26.3", - "reagraph": "^4.15.27", + "reagraph": "^4.16.0", "use-debounce": "^10.0.0" }, "devDependencies": { "@lezer/generator": "^1.7.0", "@types/jest": "^29.5.12", - "@types/node": "^20.12.4", - "@types/react": "^18.2.74", + "@types/node": "^20.12.7", + "@types/react": "^18.2.75", "@types/react-dom": "^18.2.24", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -54,7 +54,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "ts-jest": "^29.1.2", - "typescript": "^5.4.3", + "typescript": "^5.4.4", "vite": "^4.5.3" }, "jest": { diff --git a/rsconcept/frontend/src/components/ui/GraphUI.tsx b/rsconcept/frontend/src/components/ui/GraphUI.tsx index 5be724fe..ba956a69 100644 --- a/rsconcept/frontend/src/components/ui/GraphUI.tsx +++ b/rsconcept/frontend/src/components/ui/GraphUI.tsx @@ -3,7 +3,19 @@ import { GraphCanvas as GraphUI } from 'reagraph'; -export { type GraphEdge, type GraphNode, type GraphCanvasRef, Sphere, useSelection } from 'reagraph'; +export { + type GraphEdge, + type GraphNode, + type GraphCanvasRef, + Sphere, + useSelection, + type CollapseProps +} from 'reagraph'; export { type LayoutTypes as GraphLayout } from 'reagraph'; +import { ThreeEvent } from '@react-three/fiber'; + +export type GraphMouseEvent = ThreeEvent; +export type GraphPointerEvent = ThreeEvent; + export default GraphUI; diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx index 3e149875..6b025cff 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx @@ -75,6 +75,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { return hoverID && controller.schema?.cstByID.get(hoverID); }, [controller.schema?.cstByID, hoverID]); const [hoverCstDebounced] = useDebounce(hoverCst, PARAMETER.graphPopupDelay); + const [hoverLeft, setHoverLeft] = useState(true); const [toggleResetView, setToggleResetView] = useState(false); @@ -227,6 +228,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { onEdit={onOpenEdit} onSelectCentral={handleSetFocus} toggleResetView={toggleResetView} + setHoverLeft={setHoverLeft} /> ), [ @@ -320,7 +322,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { {hoverCst && hoverCstDebounced && hoverCst === hoverCstDebounced ? ( diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx index 9b0887e1..6f64a7df 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/TermGraph.tsx @@ -2,7 +2,16 @@ import { RefObject, useCallback, useLayoutEffect, useMemo } from 'react'; -import GraphUI, { GraphCanvasRef, GraphEdge, GraphLayout, GraphNode, useSelection } from '@/components/ui/GraphUI'; +import GraphUI, { + CollapseProps, + GraphCanvasRef, + GraphEdge, + GraphLayout, + GraphMouseEvent, + GraphNode, + GraphPointerEvent, + useSelection +} from '@/components/ui/GraphUI'; import { useConceptOptions } from '@/context/OptionsContext'; import { ConstituentaID } from '@/models/rsform'; import { graphDarkT, graphLightT } from '@/styling/color'; @@ -19,6 +28,7 @@ interface TermGraphProps { orbit: boolean; setHoverID: (newID: ConstituentaID | undefined) => void; + setHoverLeft: (value: boolean) => void; onEdit: (cstID: ConstituentaID) => void; onSelectCentral: (selectedID: ConstituentaID) => void; onSelect: (newID: ConstituentaID) => void; @@ -37,12 +47,12 @@ function TermGraph({ orbit, toggleResetView, setHoverID, + setHoverLeft, onEdit, onSelectCentral, onSelect, onDeselect }: TermGraphProps) { - let ctrlKey: boolean = false; const { calculateHeight, darkMode } = useConceptOptions(); const { selections, setSelections } = useSelection({ @@ -54,10 +64,14 @@ function TermGraph({ }); const handleHoverIn = useCallback( - (node: GraphNode) => { + (node: GraphNode, event: GraphPointerEvent) => { setHoverID(Number(node.id)); + setHoverLeft( + event.clientX / window.innerWidth >= PARAMETER.graphHoverXLimit || + event.clientY / window.innerHeight >= PARAMETER.graphHoverYLimit + ); }, - [setHoverID] + [setHoverID, setHoverLeft] ); const handleHoverOut = useCallback(() => { @@ -65,8 +79,8 @@ function TermGraph({ }, [setHoverID]); const handleNodeClick = useCallback( - (node: GraphNode) => { - if (ctrlKey) { + (node: GraphNode, _?: CollapseProps, event?: GraphMouseEvent) => { + if (event?.ctrlKey) { onSelectCentral(Number(node.id)); } else if (selections.includes(node.id)) { onDeselect(Number(node.id)); @@ -74,7 +88,7 @@ function TermGraph({ onSelect(Number(node.id)); } }, - [onSelect, selections, onDeselect, onSelectCentral, ctrlKey] + [onSelect, selections, onDeselect, onSelectCentral] ); const handleNodeDoubleClick = useCallback( @@ -101,13 +115,7 @@ function TermGraph({ const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]); return ( -
(ctrlKey = event.ctrlKey)} - onKeyDown={event => (ctrlKey = event.ctrlKey)} - > +