ConceptPortal-public/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx

113 lines
3.6 KiB
TypeScript
Raw Normal View History

import { useCallback, useMemo, useRef, useState } from 'react';
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, lightTheme, useSelection } from 'reagraph';
2023-07-29 23:00:03 +03:00
import Button from '../../components/Common/Button';
import Checkbox from '../../components/Common/Checkbox';
import ConceptSelect from '../../components/Common/ConceptSelect';
2023-07-29 21:23:18 +03:00
import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
2023-07-30 00:47:07 +03:00
import useLocalStorage from '../../hooks/useLocalStorage';
import { resources } from '../../utils/constants';
import { GraphLayoutSelector,mapLayoutLabels } from '../../utils/staticUI';
2023-07-29 21:23:18 +03:00
function EditorTermGraph() {
const { schema } = useRSForm();
const { darkMode } = useConceptTheme();
2023-07-30 00:47:07 +03:00
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'forceatlas2');
const [ orbit, setOrbit ] = useState(false);
2023-07-30 00:47:07 +03:00
const graphRef = useRef<GraphCanvasRef | null>(null);
2023-07-29 23:00:03 +03:00
const nodes: GraphNode[] = useMemo(() => {
return schema?.items.map(cst => {
return {
id: String(cst.id),
label: (cst.term.resolved || cst.term.raw) ? `${cst.alias}: ${cst.term.resolved || cst.term.raw}` : cst.alias
}}
) ?? [];
}, [schema?.items]);
2023-07-29 21:23:18 +03:00
2023-07-30 00:47:07 +03:00
const edges: GraphEdge[] = useMemo(() => {
2023-07-29 23:00:03 +03:00
const result: GraphEdge[] = [];
let edgeID = 1;
schema?.graph.nodes.forEach(source => {
source.outputs.forEach(target => {
2023-07-29 23:00:03 +03:00
result.push({
id: String(edgeID),
source: String(source.id),
target: String(target)
});
edgeID += 1;
});
});
return result;
}, [schema?.graph]);
2023-07-29 21:23:18 +03:00
const handleCenter = useCallback(() => {
graphRef.current?.resetControls();
graphRef.current?.centerGraph();
}, []);
2023-07-30 00:47:07 +03:00
const {
selections, actives,
onNodeClick,
onCanvasClick,
onNodePointerOver,
onNodePointerOut
} = useSelection({
ref: graphRef,
nodes,
edges,
type: 'multi', // 'single' | 'multi' | 'multiModifier'
2023-07-30 00:47:07 +03:00
pathSelectionType: 'all',
focusOnSelect: false
2023-07-30 00:47:07 +03:00
});
return (<>
2023-07-30 00:47:07 +03:00
<div className='relative w-full'>
<div className='absolute top-0 left-0 z-20 px-3 py-2 w-[12rem] flex flex-col gap-2'>
<ConceptSelect
2023-07-30 00:47:07 +03:00
options={GraphLayoutSelector}
placeholder='Выберите тип'
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
onChange={data => { setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value); }}
/>
<Checkbox
label='Анимация вращения'
widthClass='w-full'
value={orbit}
onChange={ event => setOrbit(event.target.checked) }/>
<Button
text='Центрировать'
dense
onClick={handleCenter}
2023-07-30 00:47:07 +03:00
/>
</div>
</div>
<div className='flex-wrap w-full h-full overflow-auto'>
<div className='relative w-[1240px] h-[800px] 2xl:w-[1880px] 2xl:h-[800px]'>
<GraphCanvas
draggable
ref={graphRef}
nodes={nodes}
edges={edges}
layoutType={layout}
selections={selections}
actives={actives}
onNodeClick={onNodeClick}
onCanvasClick={onCanvasClick}
defaultNodeSize={5}
onNodePointerOver={onNodePointerOver}
onNodePointerOut={onNodePointerOut}
cameraMode={ orbit ? 'orbit' : layout.includes('3d') ? 'rotate' : 'pan'}
layoutOverrides={ layout.includes('tree') ? { nodeLevelRatio: 1 } : undefined }
labelFontUrl={resources.graph_font}
theme={darkMode ? darkTheme : lightTheme}
2023-07-29 23:00:03 +03:00
/>
2023-07-29 21:23:18 +03:00
</div>
</div>
</>);
2023-07-29 21:23:18 +03:00
}
2023-07-29 23:00:03 +03:00
2023-07-29 21:23:18 +03:00
export default EditorTermGraph;