This commit is contained in:
parent
527785a544
commit
2b97e109b4
|
@ -1,15 +1,35 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
|
|
||||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
import { IArgumentInfo } from '@/models/rslang';
|
import { IArgumentInfo } from '@/models/rslang';
|
||||||
|
import { TMGraph } from '@/models/TMGraph';
|
||||||
|
import { errors } from '@/utils/labels';
|
||||||
|
|
||||||
|
import MGraphFlow from './MGraphFlow';
|
||||||
|
|
||||||
interface DlgShowTypificationProps extends Pick<ModalProps, 'hideWindow'> {
|
interface DlgShowTypificationProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
result: string;
|
alias: string;
|
||||||
|
resultTypification: string;
|
||||||
args: IArgumentInfo[];
|
args: IArgumentInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function DlgShowTypification({ hideWindow, result, args }: DlgShowTypificationProps) {
|
function DlgShowTypification({ hideWindow, alias, resultTypification, args }: DlgShowTypificationProps) {
|
||||||
console.log(result, args);
|
const graph = useMemo(() => {
|
||||||
|
const result = new TMGraph();
|
||||||
|
result.addConstituenta(alias, resultTypification, args);
|
||||||
|
return result;
|
||||||
|
}, [alias, resultTypification, args]);
|
||||||
|
|
||||||
|
if (graph.nodes.length === 0) {
|
||||||
|
toast.error(errors.typeStructureFailed);
|
||||||
|
hideWindow();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
header='Структура типизации'
|
header='Структура типизации'
|
||||||
|
@ -17,7 +37,9 @@ function DlgShowTypification({ hideWindow, result, args }: DlgShowTypificationPr
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]'
|
className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]'
|
||||||
>
|
>
|
||||||
<div>В разработке...</div>
|
<ReactFlowProvider>
|
||||||
|
<MGraphFlow data={graph} />
|
||||||
|
</ReactFlowProvider>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
import { Edge, ReactFlow, useEdgesState, useNodesState, useReactFlow } from 'reactflow';
|
||||||
|
|
||||||
|
import { TMGraph } from '@/models/TMGraph';
|
||||||
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
|
||||||
|
import { TMGraphEdgeTypes } from './graph/MGraphEdgeTypes';
|
||||||
|
import { applyLayout } from './graph/MGraphLayout';
|
||||||
|
import { TMGraphNodeTypes } from './graph/MGraphNodeTypes';
|
||||||
|
|
||||||
|
interface MGraphFlowProps {
|
||||||
|
data: TMGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MGraphFlow({ data }: MGraphFlowProps) {
|
||||||
|
const [nodes, setNodes] = useNodesState([]);
|
||||||
|
const [edges, setEdges] = useEdgesState([]);
|
||||||
|
const flow = useReactFlow();
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const newNodes = data.nodes.map(node => ({
|
||||||
|
id: String(node.id),
|
||||||
|
data: node,
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
type: 'step'
|
||||||
|
}));
|
||||||
|
|
||||||
|
const newEdges: Edge[] = [];
|
||||||
|
data.nodes.forEach(node => {
|
||||||
|
const visited = new Set<number>();
|
||||||
|
const edges = new Map<number, number>();
|
||||||
|
node.parents.forEach((parent, index) => {
|
||||||
|
if (visited.has(parent)) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||||
|
newEdges.at(edges.get(parent)!)!.data!.indices.push(index + 1);
|
||||||
|
} else {
|
||||||
|
newEdges.push({
|
||||||
|
id: String(newEdges.length),
|
||||||
|
data: node.parents.length > 1 ? { indices: [index + 1] } : undefined,
|
||||||
|
source: String(parent),
|
||||||
|
target: String(node.id),
|
||||||
|
type: node.parents.length > 1 ? 'cartesian' : 'boolean'
|
||||||
|
});
|
||||||
|
edges.set(parent, newEdges.length - 1);
|
||||||
|
visited.add(parent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
applyLayout(newNodes);
|
||||||
|
|
||||||
|
setNodes(newNodes);
|
||||||
|
setEdges(newEdges);
|
||||||
|
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||||
|
}, [data, setNodes, setEdges, flow]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
edgesFocusable={false}
|
||||||
|
nodesFocusable={false}
|
||||||
|
nodeTypes={TMGraphNodeTypes}
|
||||||
|
edgeTypes={TMGraphEdgeTypes}
|
||||||
|
fitView
|
||||||
|
maxZoom={2}
|
||||||
|
minZoom={0.5}
|
||||||
|
nodesConnectable={false}
|
||||||
|
snapToGrid={true}
|
||||||
|
snapGrid={[PARAMETER.ossGridSize, PARAMETER.ossGridSize]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MGraphFlow;
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { StraightEdge } from 'reactflow';
|
||||||
|
|
||||||
|
import { MGraphEdgeInternal } from '@/models/miscellaneous';
|
||||||
|
|
||||||
|
function BooleanEdge(props: MGraphEdgeInternal) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StraightEdge {...props} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BooleanEdge;
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { SimpleBezierEdge } from 'reactflow';
|
||||||
|
|
||||||
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
|
import { MGraphEdgeInternal } from '@/models/miscellaneous';
|
||||||
|
|
||||||
|
function CartesianEdge({ data, ...restProps }: MGraphEdgeInternal) {
|
||||||
|
const { colors } = useConceptOptions();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SimpleBezierEdge
|
||||||
|
{...restProps}
|
||||||
|
label={data?.indices.join(', ')}
|
||||||
|
labelBgStyle={{ fill: colors.bgDefault }}
|
||||||
|
labelStyle={{ fill: colors.fgDefault }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CartesianEdge;
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { EdgeTypes } from 'reactflow';
|
||||||
|
|
||||||
|
import BooleanEdge from './BooleanEdge';
|
||||||
|
import CartesianEdge from './CartesianEdge';
|
||||||
|
|
||||||
|
export const TMGraphEdgeTypes: EdgeTypes = {
|
||||||
|
boolean: BooleanEdge,
|
||||||
|
cartesian: CartesianEdge
|
||||||
|
};
|
|
@ -0,0 +1,171 @@
|
||||||
|
import { Node } from 'reactflow';
|
||||||
|
|
||||||
|
import { Graph } from '@/models/Graph';
|
||||||
|
import { TMGraphNode } from '@/models/TMGraph';
|
||||||
|
|
||||||
|
export function applyLayout(nodes: Node<TMGraphNode>[]) {
|
||||||
|
new LayoutManager(nodes).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNIT_HEIGHT = 100;
|
||||||
|
const UNIT_WIDTH = 100;
|
||||||
|
const MIN_NODE_DISTANCE = 80;
|
||||||
|
const LAYOUT_ITERATIONS = 8;
|
||||||
|
|
||||||
|
class LayoutManager {
|
||||||
|
nodes: Node<TMGraphNode>[];
|
||||||
|
|
||||||
|
graph = new Graph();
|
||||||
|
ranks = new Map<number, number>();
|
||||||
|
posX = new Map<number, number>();
|
||||||
|
posY = new Map<number, number>();
|
||||||
|
|
||||||
|
maxRank = 0;
|
||||||
|
virtualCount = 0;
|
||||||
|
layers: number[][] = [];
|
||||||
|
|
||||||
|
constructor(nodes: Node<TMGraphNode>[]) {
|
||||||
|
this.nodes = nodes;
|
||||||
|
this.prepareGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prepares graph for layout calculations.
|
||||||
|
*
|
||||||
|
* Assumes that nodes are already topologically sorted.
|
||||||
|
* 1. Adds nodes to graph.
|
||||||
|
* 2. Adds elementary edges to graph.
|
||||||
|
* 3. Splits non-elementary edges via virtual nodes.
|
||||||
|
*/
|
||||||
|
private prepareGraph(): void {
|
||||||
|
this.nodes.forEach(node => {
|
||||||
|
if (this.maxRank < node.data.rank) {
|
||||||
|
this.maxRank = node.data.rank;
|
||||||
|
}
|
||||||
|
const nodeID = node.data.id;
|
||||||
|
this.ranks.set(nodeID, node.data.rank);
|
||||||
|
this.graph.addNode(nodeID);
|
||||||
|
if (node.data.parents.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visited = new Set<number>();
|
||||||
|
node.data.parents.forEach(parent => {
|
||||||
|
if (!visited.has(parent)) {
|
||||||
|
visited.add(parent);
|
||||||
|
let target = nodeID;
|
||||||
|
let currentRank = node.data.rank;
|
||||||
|
const parentRank = this.ranks.get(parent)!;
|
||||||
|
while (currentRank - 1 > parentRank) {
|
||||||
|
currentRank = currentRank - 1;
|
||||||
|
|
||||||
|
this.virtualCount = this.virtualCount + 1;
|
||||||
|
this.ranks.set(-this.virtualCount, currentRank);
|
||||||
|
this.graph.addEdge(-this.virtualCount, target);
|
||||||
|
target = -this.virtualCount;
|
||||||
|
}
|
||||||
|
this.graph.addEdge(parent, target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(): void {
|
||||||
|
this.calculateLayers();
|
||||||
|
this.calculatePositions();
|
||||||
|
this.savePositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateLayers(): void {
|
||||||
|
this.initLayers();
|
||||||
|
// TODO: implement ordering algorithm iterations
|
||||||
|
}
|
||||||
|
|
||||||
|
private initLayers(): void {
|
||||||
|
this.layers = Array.from({ length: this.maxRank + 1 }, () => []);
|
||||||
|
|
||||||
|
const visited = new Set<number>();
|
||||||
|
const dfs = (nodeID: number) => {
|
||||||
|
if (visited.has(nodeID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited.add(nodeID);
|
||||||
|
this.layers[this.ranks.get(nodeID)!].push(nodeID);
|
||||||
|
this.graph.at(nodeID)!.outputs.forEach(dfs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const simpleNodes = this.nodes
|
||||||
|
.filter(node => node.data.rank === 0)
|
||||||
|
.sort((a, b) => a.data.text.localeCompare(b.data.text))
|
||||||
|
.map(node => node.data.id);
|
||||||
|
|
||||||
|
simpleNodes.forEach(dfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculatePositions(): void {
|
||||||
|
this.initPositions();
|
||||||
|
|
||||||
|
for (let i = 0; i < LAYOUT_ITERATIONS; i++) {
|
||||||
|
this.fixLayersPositions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fixLayersPositions(): void {
|
||||||
|
for (let rank = 1; rank <= this.maxRank; rank++) {
|
||||||
|
this.layers[rank].reverse().forEach(nodeID => {
|
||||||
|
const inputs = this.graph.at(nodeID)!.inputs;
|
||||||
|
const currentPos = this.posX.get(nodeID)!;
|
||||||
|
if (inputs.length === 1) {
|
||||||
|
const parent = inputs[0];
|
||||||
|
const parentPos = this.posX.get(parent)!;
|
||||||
|
if (currentPos === parentPos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentPos > parentPos) {
|
||||||
|
this.tryMoveNodeX(parent, currentPos);
|
||||||
|
} else {
|
||||||
|
this.tryMoveNodeX(nodeID, parentPos);
|
||||||
|
}
|
||||||
|
} else if (inputs.length % 2 === 1) {
|
||||||
|
const median = inputs[Math.floor(inputs.length / 2)];
|
||||||
|
const medianPos = this.posX.get(median)!;
|
||||||
|
if (currentPos === medianPos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.tryMoveNodeX(nodeID, medianPos);
|
||||||
|
} else {
|
||||||
|
const median1 = inputs[Math.floor(inputs.length / 2)];
|
||||||
|
const median2 = inputs[Math.floor(inputs.length / 2) - 1];
|
||||||
|
const medianPos = (this.posX.get(median1)! + this.posX.get(median2)!) / 2;
|
||||||
|
this.tryMoveNodeX(nodeID, medianPos);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryMoveNodeX(nodeID: number, targetX: number) {
|
||||||
|
const rank = this.ranks.get(nodeID)!;
|
||||||
|
if (this.layers[rank].some(id => id !== nodeID && Math.abs(targetX - this.posX.get(id)!) < MIN_NODE_DISTANCE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.posX.set(nodeID, targetX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initPositions(): void {
|
||||||
|
this.layers.forEach((layer, rank) => {
|
||||||
|
layer.forEach((nodeID, index) => {
|
||||||
|
this.posX.set(nodeID, index * UNIT_WIDTH);
|
||||||
|
this.posY.set(nodeID, -rank * UNIT_HEIGHT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private savePositions(): void {
|
||||||
|
this.nodes.forEach(node => {
|
||||||
|
const nodeID = node.data.id;
|
||||||
|
node.position = {
|
||||||
|
x: this.posX.get(nodeID)!,
|
||||||
|
y: this.posY.get(nodeID)!
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Handle, Position } from 'reactflow';
|
||||||
|
|
||||||
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
|
import { MGraphNodeInternal } from '@/models/miscellaneous';
|
||||||
|
import { colorBgTMGraphNode } from '@/styling/color';
|
||||||
|
import { globals } from '@/utils/constants';
|
||||||
|
|
||||||
|
function MGraphNode(node: MGraphNodeInternal) {
|
||||||
|
const { colors } = useConceptOptions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Handle type='source' position={Position.Top} style={{ opacity: 0 }} />
|
||||||
|
<div
|
||||||
|
className='w-full h-full cursor-default flex items-center justify-center rounded-full'
|
||||||
|
data-tooltip-id={globals.tooltip}
|
||||||
|
data-tooltip-content={node.data.text}
|
||||||
|
style={{ backgroundColor: colorBgTMGraphNode(node.data, colors) }}
|
||||||
|
>
|
||||||
|
{node.data.rank === 0 ? node.data.text : ''}
|
||||||
|
</div>
|
||||||
|
<Handle type='target' position={Position.Bottom} style={{ opacity: 0 }} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MGraphNode;
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { NodeTypes } from 'reactflow';
|
||||||
|
|
||||||
|
import MGraphNode from './MGraphNode';
|
||||||
|
|
||||||
|
export const TMGraphNodeTypes: NodeTypes = {
|
||||||
|
step: MGraphNode
|
||||||
|
};
|
|
@ -2,6 +2,8 @@
|
||||||
* Module: Multi-graph for typifications.
|
* Module: Multi-graph for typifications.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
|
||||||
import { IArgumentInfo } from './rslang';
|
import { IArgumentInfo } from './rslang';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +77,7 @@ export class TMGraph {
|
||||||
const text = parentNode.parents.length === 1 ? `ℬ${parentNode.text}` : `ℬ(${parentNode.text})`;
|
const text = parentNode.parents.length === 1 ? `ℬ${parentNode.text}` : `ℬ(${parentNode.text})`;
|
||||||
const node: TMGraphNode = {
|
const node: TMGraphNode = {
|
||||||
id: this.nodes.length,
|
id: this.nodes.length,
|
||||||
rank: parentNode.rank,
|
rank: parentNode.rank + 1,
|
||||||
text: text,
|
text: text,
|
||||||
parents: [parent],
|
parents: [parent],
|
||||||
annotations: []
|
annotations: []
|
||||||
|
@ -132,7 +134,7 @@ export class TMGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
private processResult(result: string): TMGraphNode | undefined {
|
private processResult(result: string): TMGraphNode | undefined {
|
||||||
if (!result) {
|
if (!result || result === PARAMETER.logicLabel) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this.parseToNode(result);
|
return this.parseToNode(result);
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
* Module: Miscellaneous frontend model types. Future targets for refactoring aimed at extracting modules.
|
* Module: Miscellaneous frontend model types. Future targets for refactoring aimed at extracting modules.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Node } from 'reactflow';
|
import { EdgeProps, Node } from 'reactflow';
|
||||||
|
|
||||||
import { LibraryItemType, LocationHead } from './library';
|
import { LibraryItemType, LocationHead } from './library';
|
||||||
import { IOperation } from './oss';
|
import { IOperation } from './oss';
|
||||||
|
import { TMGraphNode } from './TMGraph';
|
||||||
import { UserID } from './user';
|
import { UserID } from './user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,6 +46,24 @@ export interface OssNodeInternal {
|
||||||
yPos: number;
|
yPos: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents graph TMGraph node internal data.
|
||||||
|
*/
|
||||||
|
export interface MGraphNodeInternal {
|
||||||
|
id: string;
|
||||||
|
data: TMGraphNode;
|
||||||
|
dragging: boolean;
|
||||||
|
xPos: number;
|
||||||
|
yPos: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents graph TMGraph edge internal data.
|
||||||
|
*/
|
||||||
|
export interface MGraphEdgeInternal extends EdgeProps {
|
||||||
|
data?: { indices: number[] };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents graph node coloring scheme.
|
* Represents graph node coloring scheme.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,8 +17,8 @@ import { useRSForm } from '@/context/RSFormContext';
|
||||||
import DlgShowTypification from '@/dialogs/DlgShowTypification';
|
import DlgShowTypification from '@/dialogs/DlgShowTypification';
|
||||||
import { ConstituentaID, CstType, IConstituenta, ICstUpdateData } from '@/models/rsform';
|
import { ConstituentaID, CstType, IConstituenta, ICstUpdateData } from '@/models/rsform';
|
||||||
import { isBaseSet, isBasicConcept, isFunctional } from '@/models/rsformAPI';
|
import { isBaseSet, isBasicConcept, isFunctional } from '@/models/rsformAPI';
|
||||||
import { ParsingStatus } from '@/models/rslang';
|
import { IExpressionParse, ParsingStatus } from '@/models/rslang';
|
||||||
import { information, labelCstTypification } from '@/utils/labels';
|
import { errors, information, labelCstTypification } from '@/utils/labels';
|
||||||
|
|
||||||
import EditorRSExpression from '../EditorRSExpression';
|
import EditorRSExpression from '../EditorRSExpression';
|
||||||
import ControlsOverlay from './ControlsOverlay';
|
import ControlsOverlay from './ControlsOverlay';
|
||||||
|
@ -59,6 +59,7 @@ function FormConstituenta({
|
||||||
const [convention, setConvention] = useState('');
|
const [convention, setConvention] = useState('');
|
||||||
const [typification, setTypification] = useState('N/A');
|
const [typification, setTypification] = useState('N/A');
|
||||||
const [showTypification, setShowTypification] = useState(false);
|
const [showTypification, setShowTypification] = useState(false);
|
||||||
|
const [localParse, setLocalParse] = useState<IExpressionParse | undefined>(undefined);
|
||||||
|
|
||||||
const [forceComment, setForceComment] = useState(false);
|
const [forceComment, setForceComment] = useState(false);
|
||||||
|
|
||||||
|
@ -102,6 +103,7 @@ function FormConstituenta({
|
||||||
setExpression(state.definition_formal || '');
|
setExpression(state.definition_formal || '');
|
||||||
setTypification(state ? labelCstTypification(state) : 'N/A');
|
setTypification(state ? labelCstTypification(state) : 'N/A');
|
||||||
setForceComment(false);
|
setForceComment(false);
|
||||||
|
setLocalParse(undefined);
|
||||||
}
|
}
|
||||||
}, [state, schema, toggleReset]);
|
}, [state, schema, toggleReset]);
|
||||||
|
|
||||||
|
@ -132,7 +134,8 @@ function FormConstituenta({
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTypificationClick(event: CProps.EventMouse) {
|
function handleTypificationClick(event: CProps.EventMouse) {
|
||||||
if ((!event.ctrlKey && !event.metaKey) || !state || state.parse.status !== ParsingStatus.VERIFIED) {
|
if (!state || (localParse && !localParse.parseResult) || state.parse.status !== ParsingStatus.VERIFIED) {
|
||||||
|
toast.error(errors.typeStructureFailed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -145,8 +148,9 @@ function FormConstituenta({
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showTypification && state ? (
|
{showTypification && state ? (
|
||||||
<DlgShowTypification
|
<DlgShowTypification
|
||||||
result={state.parse.typification}
|
alias={state.alias}
|
||||||
args={state.parse.args}
|
resultTypification={localParse ? localParse.typification : state.parse.typification}
|
||||||
|
args={localParse ? localParse.args : state.parse.args}
|
||||||
hideWindow={() => setShowTypification(false)}
|
hideWindow={() => setShowTypification(false)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -186,8 +190,9 @@ function FormConstituenta({
|
||||||
noOutline
|
noOutline
|
||||||
readOnly
|
readOnly
|
||||||
label='Типизация'
|
label='Типизация'
|
||||||
|
title='Отобразить структуру типизации'
|
||||||
value={typification}
|
value={typification}
|
||||||
colors='clr-app clr-text-default cursor-default'
|
colors='clr-app clr-text-default cursor-pointer'
|
||||||
onClick={event => handleTypificationClick(event)}
|
onClick={event => handleTypificationClick(event)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -216,6 +221,7 @@ function FormConstituenta({
|
||||||
toggleReset={toggleReset}
|
toggleReset={toggleReset}
|
||||||
onChange={newValue => setExpression(newValue)}
|
onChange={newValue => setExpression(newValue)}
|
||||||
setTypification={setTypification}
|
setTypification={setTypification}
|
||||||
|
setLocalParse={setLocalParse}
|
||||||
onOpenEdit={onOpenEdit}
|
onOpenEdit={onOpenEdit}
|
||||||
/>
|
/>
|
||||||
</AnimateFade>
|
</AnimateFade>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
|
@ -40,6 +40,7 @@ interface EditorRSExpressionProps {
|
||||||
toggleReset?: boolean;
|
toggleReset?: boolean;
|
||||||
|
|
||||||
setTypification: (typification: string) => void;
|
setTypification: (typification: string) => void;
|
||||||
|
setLocalParse: React.Dispatch<React.SetStateAction<IExpressionParse | undefined>>;
|
||||||
onChange: (newValue: string) => void;
|
onChange: (newValue: string) => void;
|
||||||
onOpenEdit?: (cstID: ConstituentaID) => void;
|
onOpenEdit?: (cstID: ConstituentaID) => void;
|
||||||
}
|
}
|
||||||
|
@ -50,6 +51,7 @@ function EditorRSExpression({
|
||||||
value,
|
value,
|
||||||
toggleReset,
|
toggleReset,
|
||||||
setTypification,
|
setTypification,
|
||||||
|
setLocalParse,
|
||||||
onChange,
|
onChange,
|
||||||
onOpenEdit,
|
onOpenEdit,
|
||||||
...restProps
|
...restProps
|
||||||
|
@ -78,6 +80,7 @@ function EditorRSExpression({
|
||||||
|
|
||||||
function handleCheckExpression(callback?: (parse: IExpressionParse) => void) {
|
function handleCheckExpression(callback?: (parse: IExpressionParse) => void) {
|
||||||
parser.checkConstituenta(value, activeCst, parse => {
|
parser.checkConstituenta(value, activeCst, parse => {
|
||||||
|
setLocalParse(parse);
|
||||||
if (parse.errors.length > 0) {
|
if (parse.errors.length > 0) {
|
||||||
onShowError(parse.errors[0], parse.prefixLen);
|
onShowError(parse.errors[0], parse.prefixLen);
|
||||||
} else {
|
} else {
|
||||||
|
@ -137,7 +140,6 @@ function EditorRSExpression({
|
||||||
toast.error(errors.astFailed);
|
toast.error(errors.astFailed);
|
||||||
} else {
|
} else {
|
||||||
setSyntaxTree(parse.ast);
|
setSyntaxTree(parse.ast);
|
||||||
// TODO: return prefix from parser API instead of prefixLength
|
|
||||||
setExpression(getDefinitionPrefix(activeCst) + value);
|
setExpression(getDefinitionPrefix(activeCst) + value);
|
||||||
setShowAST(true);
|
setShowAST(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { GramData, Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '@/models
|
||||||
import { GraphColoring } from '@/models/miscellaneous';
|
import { GraphColoring } from '@/models/miscellaneous';
|
||||||
import { CstClass, ExpressionStatus, IConstituenta } from '@/models/rsform';
|
import { CstClass, ExpressionStatus, IConstituenta } from '@/models/rsform';
|
||||||
import { ISyntaxTreeNode, TokenID } from '@/models/rslang';
|
import { ISyntaxTreeNode, TokenID } from '@/models/rslang';
|
||||||
|
import { TMGraphNode } from '@/models/TMGraph';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -563,3 +564,16 @@ export function colorBgGraphNode(cst: IConstituenta, coloringScheme: GraphColori
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines m-graph color for {@link TMGraphNode}.
|
||||||
|
*/
|
||||||
|
export function colorBgTMGraphNode(node: TMGraphNode, colors: IColorTheme): string {
|
||||||
|
if (node.rank === 0) {
|
||||||
|
return colors.bgControls;
|
||||||
|
}
|
||||||
|
if (node.parents.length === 1) {
|
||||||
|
return colors.bgTeal;
|
||||||
|
}
|
||||||
|
return colors.bgOrange;
|
||||||
|
}
|
||||||
|
|
|
@ -66,9 +66,15 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-flow__edge {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.react-flow__attribution {
|
.react-flow__attribution {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-family: var(--font-ui);
|
font-family: var(--font-ui);
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 3px;
|
||||||
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--cl-fg-60);
|
color: var(--cl-fg-60);
|
||||||
|
@ -77,19 +83,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__node-input,
|
[class*='react-flow__node-'] {
|
||||||
.react-flow__node-synthesis {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
border: 1px solid;
|
|
||||||
padding: 2px;
|
|
||||||
width: 150px;
|
|
||||||
height: 40px;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
border-radius: 5px;
|
border: 1px solid;
|
||||||
background-color: var(--cl-bg-120);
|
|
||||||
|
|
||||||
|
background-color: var(--cl-bg-120);
|
||||||
color: var(--cl-fg-100);
|
color: var(--cl-fg-100);
|
||||||
border-color: var(--cl-bg-40);
|
border-color: var(--cl-bg-40);
|
||||||
background-color: var(--cl-bg-120);
|
background-color: var(--cl-bg-120);
|
||||||
|
@ -116,3 +115,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-flow__node-input,
|
||||||
|
.react-flow__node-synthesis {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 2px;
|
||||||
|
width: 150px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node-step {
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import {
|
||||||
} from '@/models/rslang';
|
} from '@/models/rslang';
|
||||||
import { UserLevel } from '@/models/user';
|
import { UserLevel } from '@/models/user';
|
||||||
|
|
||||||
|
import { PARAMETER } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove html tags from target string.
|
* Remove html tags from target string.
|
||||||
*/
|
*/
|
||||||
|
@ -531,7 +533,7 @@ export function labelTypification({
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
}
|
}
|
||||||
if (resultType === '' || resultType === 'LOGIC') {
|
if (resultType === '' || resultType === PARAMETER.logicLabel) {
|
||||||
resultType = 'Logical';
|
resultType = 'Logical';
|
||||||
}
|
}
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
|
@ -1000,6 +1002,7 @@ export const information = {
|
||||||
*/
|
*/
|
||||||
export const errors = {
|
export const errors = {
|
||||||
astFailed: 'Невозможно построить дерево разбора',
|
astFailed: 'Невозможно построить дерево разбора',
|
||||||
|
typeStructureFailed: 'Структура отсутствует',
|
||||||
passwordsMismatch: 'Пароли не совпадают',
|
passwordsMismatch: 'Пароли не совпадают',
|
||||||
imageFailed: 'Ошибка при создании изображения',
|
imageFailed: 'Ошибка при создании изображения',
|
||||||
reuseOriginal: 'Повторное использование удаляемой конституенты при отождествлении',
|
reuseOriginal: 'Повторное использование удаляемой конституенты при отождествлении',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user