2023-07-31 22:38:58 +03:00
|
|
|
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
|
|
|
|
2023-07-31 22:38:58 +03:00
|
|
|
import Button from '../../components/Common/Button';
|
|
|
|
import Checkbox from '../../components/Common/Checkbox';
|
2023-07-30 15:49:30 +03:00
|
|
|
import ConceptSelect from '../../components/Common/ConceptSelect';
|
2023-07-29 21:23:18 +03:00
|
|
|
import { useRSForm } from '../../context/RSFormContext';
|
2023-07-31 22:38:58 +03:00
|
|
|
import { useConceptTheme } from '../../context/ThemeContext';
|
2023-07-30 00:47:07 +03:00
|
|
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
2023-07-31 22:38:58 +03:00
|
|
|
import { resources } from '../../utils/constants';
|
|
|
|
import { GraphLayoutSelector,mapLayoutLabels } from '../../utils/staticUI';
|
2023-07-29 21:23:18 +03:00
|
|
|
|
|
|
|
function EditorTermGraph() {
|
|
|
|
const { schema } = useRSForm();
|
2023-07-31 22:38:58 +03:00
|
|
|
const { darkMode } = useConceptTheme();
|
2023-07-30 00:47:07 +03:00
|
|
|
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'forceatlas2');
|
2023-07-31 22:38:58 +03:00
|
|
|
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 => {
|
2023-08-02 18:24:17 +03:00
|
|
|
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
|
|
|
|
2023-07-31 22:38:58 +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,
|
2023-07-31 22:38:58 +03:00
|
|
|
type: 'multi', // 'single' | 'multi' | 'multiModifier'
|
2023-07-30 00:47:07 +03:00
|
|
|
pathSelectionType: 'all',
|
2023-07-31 22:38:58 +03:00
|
|
|
focusOnSelect: false
|
2023-07-30 00:47:07 +03:00
|
|
|
});
|
|
|
|
|
2023-07-31 22:38:58 +03:00
|
|
|
return (<>
|
2023-07-30 00:47:07 +03:00
|
|
|
<div className='relative w-full'>
|
2023-07-31 22:38:58 +03:00
|
|
|
<div className='absolute top-0 left-0 z-20 px-3 py-2 w-[12rem] flex flex-col gap-2'>
|
2023-07-30 15:49:30 +03:00
|
|
|
<ConceptSelect
|
2023-07-30 00:47:07 +03:00
|
|
|
options={GraphLayoutSelector}
|
|
|
|
placeholder='Выберите тип'
|
2023-07-31 22:38:58 +03:00
|
|
|
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>
|
2023-07-31 22:38:58 +03:00
|
|
|
<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>
|
2023-07-31 22:38:58 +03:00
|
|
|
</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;
|