2023-08-16 11:45:05 +03:00
|
|
|
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
2023-08-27 00:19:19 +03:00
|
|
|
|
import { GraphCanvas, GraphCanvasRef, GraphEdge,
|
|
|
|
|
GraphNode, LayoutTypes, Sphere, useSelection
|
2023-08-15 21:22:21 +03:00
|
|
|
|
} 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-08-15 21:22:21 +03:00
|
|
|
|
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
|
|
|
|
import Divider from '../../components/Common/Divider';
|
2023-08-16 00:39:16 +03:00
|
|
|
|
import MiniButton from '../../components/Common/MiniButton';
|
2023-08-23 18:11:42 +03:00
|
|
|
|
import HelpTermGraph from '../../components/Help/HelpTermGraph';
|
2023-08-16 10:11:22 +03:00
|
|
|
|
import InfoConstituenta from '../../components/Help/InfoConstituenta';
|
2023-08-16 11:45:05 +03:00
|
|
|
|
import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
|
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-08-27 15:39:49 +03:00
|
|
|
|
import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color';
|
2023-08-15 21:22:21 +03:00
|
|
|
|
import { prefixes, resources } from '../../utils/constants';
|
2023-08-03 16:42:49 +03:00
|
|
|
|
import { Graph } from '../../utils/Graph';
|
2023-08-16 18:32:37 +03:00
|
|
|
|
import { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
|
2023-08-16 00:39:16 +03:00
|
|
|
|
import { getCstClassColor, getCstStatusColor,
|
2023-08-15 21:22:21 +03:00
|
|
|
|
GraphColoringSelector, GraphLayoutSelector,
|
2023-08-16 00:39:16 +03:00
|
|
|
|
mapColoringLabels, mapLayoutLabels
|
2023-08-15 21:22:21 +03:00
|
|
|
|
} from '../../utils/staticUI';
|
2023-08-16 00:39:16 +03:00
|
|
|
|
import DlgGraphOptions from './DlgGraphOptions';
|
2023-08-15 21:22:21 +03:00
|
|
|
|
import ConstituentaTooltip from './elements/ConstituentaTooltip';
|
2023-07-29 21:23:18 +03:00
|
|
|
|
|
2023-08-15 21:22:21 +03:00
|
|
|
|
export type ColoringScheme = 'none' | 'status' | 'type';
|
|
|
|
|
const TREE_SIZE_MILESTONE = 50;
|
|
|
|
|
|
2023-08-27 15:39:49 +03:00
|
|
|
|
function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, colors: IColorTheme): string {
|
2023-08-15 21:22:21 +03:00
|
|
|
|
if (coloringScheme === 'type') {
|
2023-08-27 00:19:19 +03:00
|
|
|
|
return getCstClassColor(cst.cstClass, colors);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
}
|
|
|
|
|
if (coloringScheme === 'status') {
|
2023-08-27 00:19:19 +03:00
|
|
|
|
return getCstStatusColor(cst.status, colors);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
}
|
2023-08-27 15:39:49 +03:00
|
|
|
|
return '';
|
2023-08-16 00:39:16 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface GraphEditorParams {
|
|
|
|
|
noHermits: boolean
|
|
|
|
|
noTransitive: boolean
|
|
|
|
|
noTemplates: boolean
|
2023-08-16 00:50:27 +03:00
|
|
|
|
noTerms: boolean
|
2023-08-16 00:39:16 +03:00
|
|
|
|
|
|
|
|
|
allowBase: boolean
|
|
|
|
|
allowStruct: boolean
|
|
|
|
|
allowTerm: boolean
|
|
|
|
|
allowAxiom: boolean
|
|
|
|
|
allowFunction: boolean
|
|
|
|
|
allowPredicate: boolean
|
|
|
|
|
allowConstant: boolean
|
|
|
|
|
allowTheorem: boolean
|
2023-08-15 21:22:21 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface EditorTermGraphProps {
|
2023-08-22 22:38:27 +03:00
|
|
|
|
onOpenEdit: (cstID: number) => void
|
2023-08-16 18:32:37 +03:00
|
|
|
|
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
2023-08-22 22:38:27 +03:00
|
|
|
|
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
2023-08-15 21:22:21 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 11:45:05 +03:00
|
|
|
|
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
|
|
|
|
const { schema, isEditable } = useRSForm();
|
2023-08-27 00:19:19 +03:00
|
|
|
|
const { darkMode, colors, noNavigation } = useConceptTheme();
|
2023-08-15 21:22:21 +03:00
|
|
|
|
|
2023-08-08 18:44:30 +03:00
|
|
|
|
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
|
2023-08-15 21:22:21 +03:00
|
|
|
|
const [ coloringScheme, setColoringScheme ] = useLocalStorage<ColoringScheme>('graph_coloring', 'none');
|
2023-07-31 22:38:58 +03:00
|
|
|
|
const [ orbit, setOrbit ] = useState(false);
|
2023-08-16 00:39:16 +03:00
|
|
|
|
|
2023-08-03 16:42:49 +03:00
|
|
|
|
const [ noHermits, setNoHermits ] = useLocalStorage('graph_no_hermits', true);
|
|
|
|
|
const [ noTransitive, setNoTransitive ] = useLocalStorage('graph_no_transitive', false);
|
2023-08-16 00:39:16 +03:00
|
|
|
|
const [ noTemplates, setNoTemplates ] = useLocalStorage('graph_no_templates', false);
|
2023-08-16 00:50:27 +03:00
|
|
|
|
const [ noTerms, setNoTerms ] = useLocalStorage('graph_no_terms', false);
|
2023-08-16 00:39:16 +03:00
|
|
|
|
const [ allowBase, setAllowBase ] = useLocalStorage('graph_allow_base', true);
|
|
|
|
|
const [ allowStruct, setAllowStruct ] = useLocalStorage('graph_allow_struct', true);
|
|
|
|
|
const [ allowTerm, setAllowTerm ] = useLocalStorage('graph_allow_term', true);
|
|
|
|
|
const [ allowAxiom, setAllowAxiom ] = useLocalStorage('graph_allow_axiom', true);
|
|
|
|
|
const [ allowFunction, setAllowFunction ] = useLocalStorage('function', true);
|
|
|
|
|
const [ allowPredicate, setAllowPredicate ] = useLocalStorage('graph_allow_predicate', true);
|
|
|
|
|
const [ allowConstant, setAllowConstant ] = useLocalStorage('graph_allow_constant', true);
|
|
|
|
|
const [ allowTheorem, setAllowTheorem ] = useLocalStorage('graph_allow_theorem', true);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
|
|
|
|
|
const [ filtered, setFiltered ] = useState<Graph>(new Graph());
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const [ dismissed, setDismissed ] = useState<number[]>([]);
|
|
|
|
|
const [ selectedDismissed, setSelectedDismissed ] = useState<number[]>([]);
|
2023-07-30 00:47:07 +03:00
|
|
|
|
const graphRef = useRef<GraphCanvasRef | null>(null);
|
2023-08-16 00:39:16 +03:00
|
|
|
|
const [showOptions, setShowOptions] = useState(false);
|
2023-08-16 11:45:05 +03:00
|
|
|
|
const [toggleUpdate, setToggleUpdate] = useState(false);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const [hoverID, setHoverID] = useState<number | undefined>(undefined);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
const hoverCst = useMemo(
|
|
|
|
|
() => {
|
2023-08-22 22:38:27 +03:00
|
|
|
|
return schema?.items.find(cst => cst.id === hoverID);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
}, [schema?.items, hoverID]);
|
2023-07-29 23:00:03 +03:00
|
|
|
|
|
2023-08-15 21:22:21 +03:00
|
|
|
|
const is3D = useMemo(() => layout.includes('3d'), [layout]);
|
2023-08-16 00:39:16 +03:00
|
|
|
|
const allowedTypes: CstType[] = useMemo(
|
|
|
|
|
() => {
|
|
|
|
|
const result: CstType[] = [];
|
|
|
|
|
if (allowBase) result.push(CstType.BASE);
|
|
|
|
|
if (allowStruct) result.push(CstType.STRUCTURED);
|
|
|
|
|
if (allowTerm) result.push(CstType.TERM);
|
|
|
|
|
if (allowAxiom) result.push(CstType.AXIOM);
|
|
|
|
|
if (allowFunction) result.push(CstType.FUNCTION);
|
|
|
|
|
if (allowPredicate) result.push(CstType.PREDICATE);
|
|
|
|
|
if (allowConstant) result.push(CstType.CONSTANT);
|
|
|
|
|
if (allowTheorem) result.push(CstType.THEOREM);
|
|
|
|
|
return result;
|
|
|
|
|
}, [allowBase, allowStruct, allowTerm, allowAxiom, allowFunction, allowPredicate, allowConstant, allowTheorem]);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
|
2023-08-16 11:45:05 +03:00
|
|
|
|
useLayoutEffect(
|
2023-08-15 21:22:21 +03:00
|
|
|
|
() => {
|
2023-08-03 16:42:49 +03:00
|
|
|
|
if (!schema) {
|
|
|
|
|
setFiltered(new Graph());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const graph = schema.graph.clone();
|
|
|
|
|
if (noHermits) {
|
|
|
|
|
graph.removeIsolated();
|
|
|
|
|
}
|
|
|
|
|
if (noTransitive) {
|
|
|
|
|
graph.transitiveReduction();
|
|
|
|
|
}
|
2023-08-16 00:39:16 +03:00
|
|
|
|
if (noTemplates) {
|
|
|
|
|
schema.items.forEach(cst => {
|
|
|
|
|
if (cst.isTemplate) {
|
|
|
|
|
graph.foldNode(cst.id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (allowedTypes.length < Object.values(CstType).length) {
|
|
|
|
|
schema.items.forEach(cst => {
|
|
|
|
|
if (!allowedTypes.includes(cst.cstType)) {
|
|
|
|
|
graph.foldNode(cst.id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const newDismissed: number[] = [];
|
2023-08-15 21:22:21 +03:00
|
|
|
|
schema.items.forEach(cst => {
|
|
|
|
|
if (!graph.nodes.has(cst.id)) {
|
|
|
|
|
newDismissed.push(cst.id);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-08-03 16:42:49 +03:00
|
|
|
|
setFiltered(graph);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
setDismissed(newDismissed);
|
|
|
|
|
setSelectedDismissed([]);
|
|
|
|
|
setHoverID(undefined);
|
2023-08-16 11:45:05 +03:00
|
|
|
|
}, [schema, noHermits, noTransitive, noTemplates, allowedTypes, toggleUpdate]);
|
2023-08-03 16:42:49 +03:00
|
|
|
|
|
2023-08-22 22:38:27 +03:00
|
|
|
|
function toggleDismissed(cstID: number) {
|
2023-08-15 21:22:21 +03:00
|
|
|
|
setSelectedDismissed(prev => {
|
2023-08-22 20:29:07 +03:00
|
|
|
|
const index = prev.findIndex(id => cstID === id);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
if (index !== -1) {
|
|
|
|
|
prev.splice(index, 1);
|
|
|
|
|
} else {
|
|
|
|
|
prev.push(cstID);
|
|
|
|
|
}
|
|
|
|
|
return [... prev];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nodes: GraphNode[] = useMemo(
|
|
|
|
|
() => {
|
2023-08-03 16:42:49 +03:00
|
|
|
|
const result: GraphNode[] = [];
|
|
|
|
|
if (!schema) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
filtered.nodes.forEach(node => {
|
|
|
|
|
const cst = schema.items.find(cst => cst.id === node.id);
|
|
|
|
|
if (cst) {
|
|
|
|
|
result.push({
|
2023-08-22 22:38:27 +03:00
|
|
|
|
id: String(node.id),
|
2023-08-27 15:39:49 +03:00
|
|
|
|
fill: getCstNodeColor(cst, coloringScheme, colors),
|
2023-08-16 00:50:27 +03:00
|
|
|
|
label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias
|
2023-08-03 16:42:49 +03:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return result;
|
2023-08-27 15:39:49 +03:00
|
|
|
|
}, [schema, coloringScheme, filtered.nodes, noTerms, colors]);
|
2023-07-29 21:23:18 +03:00
|
|
|
|
|
2023-08-15 21:22:21 +03:00
|
|
|
|
const edges: GraphEdge[] = useMemo(
|
|
|
|
|
() => {
|
2023-07-29 23:00:03 +03:00
|
|
|
|
const result: GraphEdge[] = [];
|
|
|
|
|
let edgeID = 1;
|
2023-08-03 16:42:49 +03:00
|
|
|
|
filtered.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),
|
2023-08-22 22:38:27 +03:00
|
|
|
|
source: String(source.id),
|
|
|
|
|
target: String(target)
|
2023-07-29 23:00:03 +03:00
|
|
|
|
});
|
|
|
|
|
edgeID += 1;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return result;
|
2023-08-03 16:42:49 +03:00
|
|
|
|
}, [filtered.nodes]);
|
2023-08-15 21:22:21 +03:00
|
|
|
|
|
2023-07-30 00:47:07 +03:00
|
|
|
|
const {
|
|
|
|
|
selections, actives,
|
|
|
|
|
onNodeClick,
|
2023-08-16 11:45:05 +03:00
|
|
|
|
clearSelections,
|
2023-07-30 00:47:07 +03:00
|
|
|
|
onCanvasClick,
|
|
|
|
|
onNodePointerOver,
|
|
|
|
|
onNodePointerOut
|
|
|
|
|
} = useSelection({
|
|
|
|
|
ref: graphRef,
|
|
|
|
|
nodes,
|
|
|
|
|
edges,
|
2023-07-31 22:38:58 +03:00
|
|
|
|
type: 'multi', // 'single' | 'multi' | 'multiModifier'
|
2023-08-16 00:50:27 +03:00
|
|
|
|
pathSelectionType: 'out',
|
|
|
|
|
pathHoverType: 'all',
|
2023-07-31 22:38:58 +03:00
|
|
|
|
focusOnSelect: false
|
2023-07-30 00:47:07 +03:00
|
|
|
|
});
|
|
|
|
|
|
2023-08-22 22:38:27 +03:00
|
|
|
|
const allSelected: number[] = useMemo(
|
2023-08-16 11:45:05 +03:00
|
|
|
|
() => {
|
2023-08-22 22:38:27 +03:00
|
|
|
|
return [ ... selectedDismissed, ... selections.map(id => Number(id))];
|
2023-08-16 11:45:05 +03:00
|
|
|
|
}, [selectedDismissed, selections]);
|
|
|
|
|
const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]);
|
2023-08-16 10:46:22 +03:00
|
|
|
|
|
2023-08-16 00:39:16 +03:00
|
|
|
|
const handleRecreate = useCallback(
|
2023-08-15 21:22:21 +03:00
|
|
|
|
() => {
|
|
|
|
|
graphRef.current?.resetControls();
|
|
|
|
|
graphRef.current?.centerGraph();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleHoverIn = useCallback(
|
|
|
|
|
(node: GraphNode) => {
|
2023-08-22 22:38:27 +03:00
|
|
|
|
setHoverID(Number(node.id));
|
2023-08-15 21:22:21 +03:00
|
|
|
|
if (onNodePointerOver) onNodePointerOver(node);
|
|
|
|
|
}, [onNodePointerOver]);
|
|
|
|
|
|
|
|
|
|
const handleHoverOut = useCallback(
|
|
|
|
|
(node: GraphNode) => {
|
|
|
|
|
setHoverID(undefined);
|
|
|
|
|
if (onNodePointerOut) onNodePointerOut(node);
|
|
|
|
|
}, [onNodePointerOut]);
|
|
|
|
|
|
|
|
|
|
const handleNodeClick = useCallback(
|
|
|
|
|
(node: GraphNode) => {
|
|
|
|
|
if (selections.includes(node.id)) {
|
2023-08-22 22:38:27 +03:00
|
|
|
|
onOpenEdit(Number(node.id));
|
2023-08-15 21:22:21 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (onNodeClick) onNodeClick(node);
|
|
|
|
|
}, [onNodeClick, selections, onOpenEdit]);
|
|
|
|
|
|
2023-08-16 10:46:22 +03:00
|
|
|
|
const handleCanvasClick = useCallback(
|
|
|
|
|
(event: MouseEvent) => {
|
|
|
|
|
setSelectedDismissed([]);
|
|
|
|
|
if (onCanvasClick) onCanvasClick(event);
|
|
|
|
|
}, [onCanvasClick]);
|
|
|
|
|
|
2023-08-16 11:45:05 +03:00
|
|
|
|
// Implement hotkeys for editing
|
|
|
|
|
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
|
|
|
|
if (!isEditable) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (event.key === 'Delete' && allSelected.length > 0) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
handleDeleteCst();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleCreateCst() {
|
|
|
|
|
if (!schema) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-16 18:32:37 +03:00
|
|
|
|
const data: ICstCreateData = {
|
|
|
|
|
insert_after: null,
|
2023-08-22 20:29:07 +03:00
|
|
|
|
cst_type: allSelected.length === 0 ? CstType.BASE: CstType.TERM,
|
2023-08-16 18:32:37 +03:00
|
|
|
|
alias: '',
|
|
|
|
|
term_raw: '',
|
2023-08-22 20:29:07 +03:00
|
|
|
|
definition_formal: allSelected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' '),
|
2023-08-16 18:32:37 +03:00
|
|
|
|
definition_raw: '',
|
|
|
|
|
convention: '',
|
|
|
|
|
};
|
|
|
|
|
onCreateCst(data);
|
2023-08-16 11:45:05 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDeleteCst() {
|
|
|
|
|
if (!schema) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-22 20:29:07 +03:00
|
|
|
|
onDeleteCst(allSelected, () => {
|
2023-08-16 11:45:05 +03:00
|
|
|
|
clearSelections();
|
|
|
|
|
setDismissed([]);
|
|
|
|
|
setSelectedDismissed([]);
|
|
|
|
|
setToggleUpdate(prev => !prev);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 00:39:16 +03:00
|
|
|
|
function getOptions() {
|
|
|
|
|
return {
|
|
|
|
|
noHermits: noHermits,
|
|
|
|
|
noTemplates: noTemplates,
|
|
|
|
|
noTransitive: noTransitive,
|
2023-08-16 00:50:27 +03:00
|
|
|
|
noTerms: noTerms,
|
2023-08-16 00:39:16 +03:00
|
|
|
|
|
|
|
|
|
allowBase: allowBase,
|
|
|
|
|
allowStruct: allowStruct,
|
|
|
|
|
allowTerm: allowTerm,
|
|
|
|
|
allowAxiom: allowAxiom,
|
|
|
|
|
allowFunction: allowFunction,
|
|
|
|
|
allowPredicate: allowPredicate,
|
|
|
|
|
allowConstant: allowConstant,
|
|
|
|
|
allowTheorem: allowTheorem
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleChangeOptions = useCallback(
|
|
|
|
|
(params: GraphEditorParams) => {
|
|
|
|
|
setNoHermits(params.noHermits);
|
|
|
|
|
setNoTransitive(params.noTransitive);
|
|
|
|
|
setNoTemplates(params.noTemplates);
|
2023-08-16 00:50:27 +03:00
|
|
|
|
setNoTerms(params.noTerms);
|
2023-08-16 00:39:16 +03:00
|
|
|
|
|
|
|
|
|
setAllowBase(params.allowBase);
|
|
|
|
|
setAllowStruct(params.allowStruct);
|
|
|
|
|
setAllowTerm(params.allowTerm);
|
|
|
|
|
setAllowAxiom(params.allowAxiom);
|
|
|
|
|
setAllowFunction(params.allowFunction);
|
|
|
|
|
setAllowPredicate(params.allowPredicate);
|
|
|
|
|
setAllowConstant(params.allowConstant);
|
|
|
|
|
setAllowTheorem(params.allowTheorem);
|
|
|
|
|
}, [setNoHermits, setNoTransitive, setNoTemplates,
|
|
|
|
|
setAllowBase, setAllowStruct, setAllowTerm, setAllowAxiom, setAllowFunction,
|
2023-08-16 00:50:27 +03:00
|
|
|
|
setAllowPredicate, setAllowConstant, setAllowTheorem, setNoTerms]);
|
2023-08-16 00:39:16 +03:00
|
|
|
|
|
2023-08-15 21:22:21 +03:00
|
|
|
|
const canvasWidth = useMemo(
|
|
|
|
|
() => {
|
|
|
|
|
return 'calc(100vw - 14.6rem)';
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const canvasHeight = useMemo(
|
|
|
|
|
() => {
|
|
|
|
|
return !noNavigation ?
|
2023-08-27 00:19:19 +03:00
|
|
|
|
'calc(100vh - 10.1rem)'
|
|
|
|
|
: 'calc(100vh - 2.1rem)';
|
2023-08-15 21:22:21 +03:00
|
|
|
|
}, [noNavigation]);
|
2023-08-04 13:26:51 +03:00
|
|
|
|
|
2023-08-15 21:22:21 +03:00
|
|
|
|
const dismissedStyle = useCallback(
|
2023-08-22 22:38:27 +03:00
|
|
|
|
(cstID: number) => {
|
2023-08-15 21:22:21 +03:00
|
|
|
|
return selectedDismissed.includes(cstID) ? {outlineWidth: '2px', outlineStyle: 'solid'}: {};
|
|
|
|
|
}, [selectedDismissed]);
|
|
|
|
|
|
|
|
|
|
return (
|
2023-08-16 13:48:46 +03:00
|
|
|
|
<div className='flex justify-between w-full outline-none' tabIndex={0} onKeyDown={handleKeyDown}>
|
2023-08-16 00:39:16 +03:00
|
|
|
|
{showOptions &&
|
|
|
|
|
<DlgGraphOptions
|
|
|
|
|
hideWindow={() => setShowOptions(false)}
|
|
|
|
|
initial={getOptions()}
|
|
|
|
|
onConfirm={handleChangeOptions}
|
|
|
|
|
/>}
|
2023-08-27 15:39:49 +03:00
|
|
|
|
<div className='flex flex-col border-r border-b min-w-[13.5rem] px-2 pb-2 text-sm select-none clr-border' style={{height: canvasHeight}}>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
{hoverCst &&
|
|
|
|
|
<div className='relative'>
|
2023-08-16 10:11:22 +03:00
|
|
|
|
<InfoConstituenta
|
2023-08-15 21:43:15 +03:00
|
|
|
|
data={hoverCst}
|
|
|
|
|
className='absolute top-0 left-0 z-50 w-[25rem] min-h-[11rem] overflow-y-auto border h-fit clr-app px-3'
|
|
|
|
|
/>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
</div>}
|
2023-08-16 10:46:22 +03:00
|
|
|
|
|
2023-08-16 18:32:37 +03:00
|
|
|
|
<div className='flex items-center justify-between py-1'>
|
2023-08-16 11:45:05 +03:00
|
|
|
|
<div className='mr-3 whitespace-nowrap'>
|
|
|
|
|
Выбраны
|
|
|
|
|
<span className='ml-1'>
|
|
|
|
|
<b>{allSelected.length}</b> из {schema?.stats?.count_all ?? 0}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<MiniButton
|
|
|
|
|
tooltip='Удалить выбранные'
|
|
|
|
|
icon={<DumpBinIcon color={!nothingSelected ? 'text-red' : ''} size={5}/>}
|
|
|
|
|
disabled={!isEditable || nothingSelected}
|
|
|
|
|
onClick={handleDeleteCst}
|
|
|
|
|
/>
|
|
|
|
|
<MiniButton
|
|
|
|
|
tooltip='Новая конституента'
|
|
|
|
|
icon={<SmallPlusIcon color='text-green' size={5}/>}
|
|
|
|
|
disabled={!isEditable}
|
|
|
|
|
onClick={handleCreateCst}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2023-08-16 10:46:22 +03:00
|
|
|
|
</div>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
<div className='flex items-center w-full gap-1'>
|
2023-08-03 16:42:49 +03:00
|
|
|
|
<Button
|
2023-08-16 00:39:16 +03:00
|
|
|
|
icon={<FilterCogIcon size={7} />}
|
2023-08-03 16:42:49 +03:00
|
|
|
|
dense
|
2023-08-16 00:39:16 +03:00
|
|
|
|
tooltip='Настройки фильтрации узлов и связей'
|
2023-08-03 16:42:49 +03:00
|
|
|
|
widthClass='h-full'
|
2023-08-16 00:39:16 +03:00
|
|
|
|
onClick={() => setShowOptions(true)}
|
2023-08-03 16:42:49 +03:00
|
|
|
|
/>
|
|
|
|
|
<ConceptSelect
|
2023-08-27 00:19:19 +03:00
|
|
|
|
className='min-w-[9.8rem]'
|
2023-08-15 21:22:21 +03:00
|
|
|
|
options={GraphColoringSelector}
|
|
|
|
|
searchable={false}
|
|
|
|
|
placeholder='Выберите цвет'
|
|
|
|
|
values={coloringScheme ? [{ value: coloringScheme, label: mapColoringLabels.get(coloringScheme) }] : []}
|
|
|
|
|
onChange={data => { setColoringScheme(data.length > 0 ? data[0].value : GraphColoringSelector[0].value); }}
|
2023-08-03 16:42:49 +03:00
|
|
|
|
/>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
|
2023-08-03 16:42:49 +03:00
|
|
|
|
</div>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
<ConceptSelect
|
2023-08-22 22:38:27 +03:00
|
|
|
|
className='w-full mt-1'
|
2023-08-15 21:22:21 +03:00
|
|
|
|
options={GraphLayoutSelector}
|
|
|
|
|
searchable={false}
|
2023-08-22 22:38:27 +03:00
|
|
|
|
placeholder='Способ расположения'
|
2023-08-15 21:22:21 +03:00
|
|
|
|
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
|
|
|
|
|
onChange={data => { setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value); }}
|
|
|
|
|
/>
|
2023-07-31 22:38:58 +03:00
|
|
|
|
<Checkbox
|
2023-08-16 00:50:27 +03:00
|
|
|
|
label='Скрыть текст'
|
|
|
|
|
value={noTerms}
|
|
|
|
|
onChange={ event => setNoTerms(event.target.checked) }
|
2023-08-03 16:42:49 +03:00
|
|
|
|
/>
|
|
|
|
|
<Checkbox
|
|
|
|
|
label='Транзитивная редукция'
|
|
|
|
|
value={noTransitive}
|
|
|
|
|
onChange={ event => setNoTransitive(event.target.checked) }
|
2023-07-30 00:47:07 +03:00
|
|
|
|
/>
|
2023-08-16 00:50:27 +03:00
|
|
|
|
<Checkbox
|
|
|
|
|
disabled={!is3D}
|
|
|
|
|
label='Анимация вращения'
|
|
|
|
|
value={orbit}
|
|
|
|
|
onChange={ event => setOrbit(event.target.checked) }
|
|
|
|
|
/>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
|
|
|
|
|
<Divider margins='mt-3 mb-2' />
|
|
|
|
|
|
|
|
|
|
<div className='flex flex-col overflow-y-auto'>
|
|
|
|
|
<p className='text-center'><b>Скрытые конституенты</b></p>
|
|
|
|
|
<div className='flex flex-wrap justify-center gap-2 py-2'>
|
|
|
|
|
{dismissed.map(cstID => {
|
|
|
|
|
const cst = schema!.items.find(cst => cst.id === cstID)!;
|
2023-08-16 00:39:16 +03:00
|
|
|
|
const adjustedColoring = coloringScheme === 'none' ? 'status': coloringScheme;
|
2023-08-15 21:22:21 +03:00
|
|
|
|
return (<>
|
|
|
|
|
<div
|
|
|
|
|
key={`${cst.alias}`}
|
|
|
|
|
id={`${prefixes.cst_list}${cst.alias}`}
|
2023-08-16 00:50:27 +03:00
|
|
|
|
className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer'
|
2023-08-27 00:19:19 +03:00
|
|
|
|
style={{
|
2023-08-27 15:39:49 +03:00
|
|
|
|
backgroundColor: getCstNodeColor(cst, adjustedColoring, colors),
|
2023-08-27 00:19:19 +03:00
|
|
|
|
...dismissedStyle(cstID)
|
|
|
|
|
}}
|
2023-08-15 21:22:21 +03:00
|
|
|
|
onClick={() => toggleDismissed(cstID)}
|
|
|
|
|
onDoubleClick={() => onOpenEdit(cstID)}
|
|
|
|
|
>
|
|
|
|
|
{cst.alias}
|
|
|
|
|
</div>
|
|
|
|
|
<ConstituentaTooltip
|
|
|
|
|
data={cst}
|
|
|
|
|
anchor={`#${prefixes.cst_list}${cst.alias}`}
|
|
|
|
|
/>
|
|
|
|
|
</>);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2023-07-30 00:47:07 +03:00
|
|
|
|
</div>
|
2023-08-16 11:45:05 +03:00
|
|
|
|
<div className='w-full h-full overflow-auto'>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
<div
|
2023-08-27 00:19:19 +03:00
|
|
|
|
className='relative'
|
2023-08-15 21:55:43 +03:00
|
|
|
|
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
|
2023-08-15 21:22:21 +03:00
|
|
|
|
>
|
2023-08-16 18:32:37 +03:00
|
|
|
|
<div className='relative top-0 right-0 z-10 flex mt-1 ml-2 flex-start'>
|
2023-08-16 00:39:16 +03:00
|
|
|
|
<div className='px-1 py-1' id='items-graph-help' >
|
2023-08-16 13:48:46 +03:00
|
|
|
|
<HelpIcon color='text-primary' size={5} />
|
2023-08-16 00:39:16 +03:00
|
|
|
|
</div>
|
|
|
|
|
<MiniButton
|
2023-08-16 13:48:46 +03:00
|
|
|
|
icon={<ArrowsRotateIcon size={5} />}
|
2023-08-16 00:39:16 +03:00
|
|
|
|
tooltip='Пересоздать граф'
|
|
|
|
|
onClick={handleRecreate}
|
|
|
|
|
/>
|
2023-08-15 21:22:21 +03:00
|
|
|
|
</div>
|
|
|
|
|
<ConceptTooltip anchorSelect='#items-graph-help'>
|
2023-08-23 18:11:42 +03:00
|
|
|
|
<HelpTermGraph />
|
2023-08-15 21:22:21 +03:00
|
|
|
|
</ConceptTooltip>
|
2023-07-31 22:38:58 +03:00
|
|
|
|
<GraphCanvas
|
|
|
|
|
draggable
|
|
|
|
|
ref={graphRef}
|
|
|
|
|
nodes={nodes}
|
|
|
|
|
edges={edges}
|
|
|
|
|
layoutType={layout}
|
|
|
|
|
selections={selections}
|
|
|
|
|
actives={actives}
|
2023-08-15 21:22:21 +03:00
|
|
|
|
onNodeClick={handleNodeClick}
|
2023-08-16 10:46:22 +03:00
|
|
|
|
onCanvasClick={handleCanvasClick}
|
2023-08-15 21:22:21 +03:00
|
|
|
|
onNodePointerOver={handleHoverIn}
|
|
|
|
|
onNodePointerOut={handleHoverOut}
|
|
|
|
|
cameraMode={ orbit ? 'orbit' : is3D ? 'rotate' : 'pan'}
|
|
|
|
|
layoutOverrides={
|
2023-08-16 00:39:16 +03:00
|
|
|
|
layout.includes('tree') ? { nodeLevelRatio: filtered.nodes.size < TREE_SIZE_MILESTONE ? 3 : 1 }
|
2023-08-15 21:22:21 +03:00
|
|
|
|
: undefined
|
|
|
|
|
}
|
2023-07-31 22:38:58 +03:00
|
|
|
|
labelFontUrl={resources.graph_font}
|
2023-08-27 15:39:49 +03:00
|
|
|
|
theme={darkMode ? graphDarkT : graphLightT}
|
2023-08-03 16:42:49 +03:00
|
|
|
|
renderNode={({ node, ...rest }) => (
|
|
|
|
|
<Sphere {...rest} node={node} />
|
|
|
|
|
)}
|
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-08-15 21:22:21 +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;
|