mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Improve graph UI
This commit is contained in:
parent
849ec42bdf
commit
12dfd1c3cb
89
rsconcept/frontend/package-lock.json
generated
89
rsconcept/frontend/package-lock.json
generated
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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<MouseEvent>;
|
||||
export type GraphPointerEvent = ThreeEvent<PointerEvent>;
|
||||
|
||||
export default GraphUI;
|
||||
|
|
|
@ -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 ? (
|
||||
<Overlay
|
||||
layer='z-tooltip'
|
||||
position='top-[1.6rem] left-[2.6rem]'
|
||||
position={clsx('top-[1.6rem]', { 'left-[2.6rem]': hoverLeft, 'right-[2.6rem]': !hoverLeft })}
|
||||
className={clsx('w-[25rem]', 'px-3', 'overflow-y-auto', 'border shadow-md', 'clr-app')}
|
||||
>
|
||||
<InfoConstituenta className='pt-1 pb-2' data={hoverCstDebounced} />
|
||||
|
|
|
@ -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 (
|
||||
<div
|
||||
className='outline-none'
|
||||
tabIndex={-1}
|
||||
// TODO: fix hacky way of tracking CTRL. Expose event from onNodeClick instead
|
||||
onKeyUp={event => (ctrlKey = event.ctrlKey)}
|
||||
onKeyDown={event => (ctrlKey = event.ctrlKey)}
|
||||
>
|
||||
<div className='outline-none'>
|
||||
<div className='relative' style={{ width: canvasWidth, height: canvasHeight }}>
|
||||
<GraphUI
|
||||
nodes={nodes}
|
||||
|
|
|
@ -15,9 +15,12 @@ export const buildConstants = {
|
|||
export const PARAMETER = {
|
||||
smallScreen: 640, // == tailwind:xs
|
||||
smallTreeNodes: 50, // amount of nodes threshold for size increase for large graphs
|
||||
graphPopupDelay: 500, // milliseconds delay for graph popup selections
|
||||
refreshTimeout: 100, // milliseconds delay for post-refresh actions
|
||||
minimalTimeout: 10, // milliseconds delay for fast updates
|
||||
|
||||
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
|
||||
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be
|
||||
graphPopupDelay: 500, // milliseconds delay for graph popup selections
|
||||
graphRefreshDelay: 10, // milliseconds delay for graph viewpoint reset
|
||||
|
||||
logicLabel: 'LOGIC'
|
||||
|
|
Loading…
Reference in New Issue
Block a user