From 2b97e109b4668676f52894042909e8e4ed9c60f8 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:09:40 +0300 Subject: [PATCH] F: Implementing M-Graph pt3 --- .../DlgShowTypification.tsx | 30 ++- .../DlgShowTypification/MGraphFlow.tsx | 77 ++++++++ .../DlgShowTypification/graph/BooleanEdge.tsx | 13 ++ .../graph/CartesianEdge.tsx | 20 ++ .../graph/MGraphEdgeTypes.ts | 9 + .../DlgShowTypification/graph/MGraphLayout.ts | 171 ++++++++++++++++++ .../DlgShowTypification/graph/MGraphNode.tsx | 27 +++ .../graph/MGraphNodeTypes.ts | 7 + rsconcept/frontend/src/models/TMGraph.ts | 6 +- .../frontend/src/models/miscellaneous.ts | 21 ++- .../EditorConstituenta/FormConstituenta.tsx | 18 +- .../EditorRSExpression/EditorRSExpression.tsx | 6 +- rsconcept/frontend/src/styling/color.ts | 14 ++ rsconcept/frontend/src/styling/overrides.css | 37 +++- rsconcept/frontend/src/utils/labels.ts | 5 +- 15 files changed, 435 insertions(+), 26 deletions(-) create mode 100644 rsconcept/frontend/src/dialogs/DlgShowTypification/MGraphFlow.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgShowTypification/graph/BooleanEdge.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgShowTypification/graph/CartesianEdge.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphEdgeTypes.ts create mode 100644 rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphLayout.ts create mode 100644 rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNode.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNodeTypes.ts diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/DlgShowTypification.tsx b/rsconcept/frontend/src/dialogs/DlgShowTypification/DlgShowTypification.tsx index b124fd9a..b2ab466f 100644 --- a/rsconcept/frontend/src/dialogs/DlgShowTypification/DlgShowTypification.tsx +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/DlgShowTypification.tsx @@ -1,15 +1,35 @@ 'use client'; +import { useMemo } from 'react'; +import { toast } from 'react-toastify'; +import { ReactFlowProvider } from 'reactflow'; + import Modal, { ModalProps } from '@/components/ui/Modal'; import { IArgumentInfo } from '@/models/rslang'; +import { TMGraph } from '@/models/TMGraph'; +import { errors } from '@/utils/labels'; + +import MGraphFlow from './MGraphFlow'; interface DlgShowTypificationProps extends Pick { - result: string; + alias: string; + resultTypification: string; args: IArgumentInfo[]; } -function DlgShowTypification({ hideWindow, result, args }: DlgShowTypificationProps) { - console.log(result, args); +function DlgShowTypification({ hideWindow, alias, resultTypification, args }: DlgShowTypificationProps) { + 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 ( -
В разработке...
+ + +
); } diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/MGraphFlow.tsx b/rsconcept/frontend/src/dialogs/DlgShowTypification/MGraphFlow.tsx new file mode 100644 index 00000000..c2351c8f --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/MGraphFlow.tsx @@ -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(); + const edges = new Map(); + 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 ( + + ); +} + +export default MGraphFlow; diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/BooleanEdge.tsx b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/BooleanEdge.tsx new file mode 100644 index 00000000..ef05b56b --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/BooleanEdge.tsx @@ -0,0 +1,13 @@ +import { StraightEdge } from 'reactflow'; + +import { MGraphEdgeInternal } from '@/models/miscellaneous'; + +function BooleanEdge(props: MGraphEdgeInternal) { + return ( + <> + + + ); +} + +export default BooleanEdge; diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/CartesianEdge.tsx b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/CartesianEdge.tsx new file mode 100644 index 00000000..c93395c2 --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/CartesianEdge.tsx @@ -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 ( + <> + + + ); +} + +export default CartesianEdge; diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphEdgeTypes.ts b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphEdgeTypes.ts new file mode 100644 index 00000000..84676a31 --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphEdgeTypes.ts @@ -0,0 +1,9 @@ +import { EdgeTypes } from 'reactflow'; + +import BooleanEdge from './BooleanEdge'; +import CartesianEdge from './CartesianEdge'; + +export const TMGraphEdgeTypes: EdgeTypes = { + boolean: BooleanEdge, + cartesian: CartesianEdge +}; diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphLayout.ts b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphLayout.ts new file mode 100644 index 00000000..ef5bcf72 --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphLayout.ts @@ -0,0 +1,171 @@ +import { Node } from 'reactflow'; + +import { Graph } from '@/models/Graph'; +import { TMGraphNode } from '@/models/TMGraph'; + +export function applyLayout(nodes: Node[]) { + 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[]; + + graph = new Graph(); + ranks = new Map(); + posX = new Map(); + posY = new Map(); + + maxRank = 0; + virtualCount = 0; + layers: number[][] = []; + + constructor(nodes: Node[]) { + 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(); + 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(); + 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)! + }; + }); + } +} diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNode.tsx b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNode.tsx new file mode 100644 index 00000000..e32e3044 --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNode.tsx @@ -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 ( + <> + +
+ {node.data.rank === 0 ? node.data.text : ''} +
+ + + ); +} + +export default MGraphNode; diff --git a/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNodeTypes.ts b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNodeTypes.ts new file mode 100644 index 00000000..ed988c35 --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgShowTypification/graph/MGraphNodeTypes.ts @@ -0,0 +1,7 @@ +import { NodeTypes } from 'reactflow'; + +import MGraphNode from './MGraphNode'; + +export const TMGraphNodeTypes: NodeTypes = { + step: MGraphNode +}; diff --git a/rsconcept/frontend/src/models/TMGraph.ts b/rsconcept/frontend/src/models/TMGraph.ts index b14a1265..d728cea9 100644 --- a/rsconcept/frontend/src/models/TMGraph.ts +++ b/rsconcept/frontend/src/models/TMGraph.ts @@ -2,6 +2,8 @@ * Module: Multi-graph for typifications. */ +import { PARAMETER } from '@/utils/constants'; + import { IArgumentInfo } from './rslang'; /** @@ -75,7 +77,7 @@ export class TMGraph { const text = parentNode.parents.length === 1 ? `ℬ${parentNode.text}` : `ℬ(${parentNode.text})`; const node: TMGraphNode = { id: this.nodes.length, - rank: parentNode.rank, + rank: parentNode.rank + 1, text: text, parents: [parent], annotations: [] @@ -132,7 +134,7 @@ export class TMGraph { } private processResult(result: string): TMGraphNode | undefined { - if (!result) { + if (!result || result === PARAMETER.logicLabel) { return undefined; } return this.parseToNode(result); diff --git a/rsconcept/frontend/src/models/miscellaneous.ts b/rsconcept/frontend/src/models/miscellaneous.ts index 5bd2d2b9..f643ac49 100644 --- a/rsconcept/frontend/src/models/miscellaneous.ts +++ b/rsconcept/frontend/src/models/miscellaneous.ts @@ -2,10 +2,11 @@ * 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 { IOperation } from './oss'; +import { TMGraphNode } from './TMGraph'; import { UserID } from './user'; /** @@ -45,6 +46,24 @@ export interface OssNodeInternal { 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. */ diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx index bfa0783d..e40058aa 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx @@ -17,8 +17,8 @@ import { useRSForm } from '@/context/RSFormContext'; import DlgShowTypification from '@/dialogs/DlgShowTypification'; import { ConstituentaID, CstType, IConstituenta, ICstUpdateData } from '@/models/rsform'; import { isBaseSet, isBasicConcept, isFunctional } from '@/models/rsformAPI'; -import { ParsingStatus } from '@/models/rslang'; -import { information, labelCstTypification } from '@/utils/labels'; +import { IExpressionParse, ParsingStatus } from '@/models/rslang'; +import { errors, information, labelCstTypification } from '@/utils/labels'; import EditorRSExpression from '../EditorRSExpression'; import ControlsOverlay from './ControlsOverlay'; @@ -59,6 +59,7 @@ function FormConstituenta({ const [convention, setConvention] = useState(''); const [typification, setTypification] = useState('N/A'); const [showTypification, setShowTypification] = useState(false); + const [localParse, setLocalParse] = useState(undefined); const [forceComment, setForceComment] = useState(false); @@ -102,6 +103,7 @@ function FormConstituenta({ setExpression(state.definition_formal || ''); setTypification(state ? labelCstTypification(state) : 'N/A'); setForceComment(false); + setLocalParse(undefined); } }, [state, schema, toggleReset]); @@ -132,7 +134,8 @@ function FormConstituenta({ } 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; } event.stopPropagation(); @@ -145,8 +148,9 @@ function FormConstituenta({ {showTypification && state ? ( setShowTypification(false)} /> ) : null} @@ -186,8 +190,9 @@ function FormConstituenta({ noOutline readOnly label='Типизация' + title='Отобразить структуру типизации' value={typification} - colors='clr-app clr-text-default cursor-default' + colors='clr-app clr-text-default cursor-pointer' onClick={event => handleTypificationClick(event)} /> ) : null} @@ -216,6 +221,7 @@ function FormConstituenta({ toggleReset={toggleReset} onChange={newValue => setExpression(newValue)} setTypification={setTypification} + setLocalParse={setLocalParse} onOpenEdit={onOpenEdit} /> diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx index 76d01da2..8a29b223 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx @@ -2,7 +2,7 @@ import { ReactCodeMirrorRef } from '@uiw/react-codemirror'; 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 BadgeHelp from '@/components/info/BadgeHelp'; @@ -40,6 +40,7 @@ interface EditorRSExpressionProps { toggleReset?: boolean; setTypification: (typification: string) => void; + setLocalParse: React.Dispatch>; onChange: (newValue: string) => void; onOpenEdit?: (cstID: ConstituentaID) => void; } @@ -50,6 +51,7 @@ function EditorRSExpression({ value, toggleReset, setTypification, + setLocalParse, onChange, onOpenEdit, ...restProps @@ -78,6 +80,7 @@ function EditorRSExpression({ function handleCheckExpression(callback?: (parse: IExpressionParse) => void) { parser.checkConstituenta(value, activeCst, parse => { + setLocalParse(parse); if (parse.errors.length > 0) { onShowError(parse.errors[0], parse.prefixLen); } else { @@ -137,7 +140,6 @@ function EditorRSExpression({ toast.error(errors.astFailed); } else { setSyntaxTree(parse.ast); - // TODO: return prefix from parser API instead of prefixLength setExpression(getDefinitionPrefix(activeCst) + value); setShowAST(true); } diff --git a/rsconcept/frontend/src/styling/color.ts b/rsconcept/frontend/src/styling/color.ts index e7ad813c..04bd8c81 100644 --- a/rsconcept/frontend/src/styling/color.ts +++ b/rsconcept/frontend/src/styling/color.ts @@ -6,6 +6,7 @@ import { GramData, Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '@/models import { GraphColoring } from '@/models/miscellaneous'; import { CstClass, ExpressionStatus, IConstituenta } from '@/models/rsform'; import { ISyntaxTreeNode, TokenID } from '@/models/rslang'; +import { TMGraphNode } from '@/models/TMGraph'; import { PARAMETER } from '@/utils/constants'; /** @@ -563,3 +564,16 @@ export function colorBgGraphNode(cst: IConstituenta, coloringScheme: GraphColori } 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; +} diff --git a/rsconcept/frontend/src/styling/overrides.css b/rsconcept/frontend/src/styling/overrides.css index 0150cefc..cc6788df 100644 --- a/rsconcept/frontend/src/styling/overrides.css +++ b/rsconcept/frontend/src/styling/overrides.css @@ -66,9 +66,15 @@ cursor: default; } +.react-flow__edge { + cursor: default; +} + .react-flow__attribution { font-size: var(--font-size-sm); font-family: var(--font-ui); + margin-left: 3px; + margin-right: 3px; background-color: transparent; color: var(--cl-fg-60); @@ -77,19 +83,12 @@ } } -.react-flow__node-input, -.react-flow__node-synthesis { - cursor: pointer; - - border: 1px solid; - padding: 2px; - width: 150px; - height: 40px; +[class*='react-flow__node-'] { font-size: 14px; - border-radius: 5px; - background-color: var(--cl-bg-120); + border: 1px solid; + background-color: var(--cl-bg-120); color: var(--cl-fg-100); border-color: var(--cl-bg-40); 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; +} diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts index be1b8a2e..2e30f82a 100644 --- a/rsconcept/frontend/src/utils/labels.ts +++ b/rsconcept/frontend/src/utils/labels.ts @@ -22,6 +22,8 @@ import { } from '@/models/rslang'; import { UserLevel } from '@/models/user'; +import { PARAMETER } from './constants'; + /** * Remove html tags from target string. */ @@ -531,7 +533,7 @@ export function labelTypification({ if (!isValid) { return 'N/A'; } - if (resultType === '' || resultType === 'LOGIC') { + if (resultType === '' || resultType === PARAMETER.logicLabel) { resultType = 'Logical'; } if (args.length === 0) { @@ -1000,6 +1002,7 @@ export const information = { */ export const errors = { astFailed: 'Невозможно построить дерево разбора', + typeStructureFailed: 'Структура отсутствует', passwordsMismatch: 'Пароли не совпадают', imageFailed: 'Ошибка при создании изображения', reuseOriginal: 'Повторное использование удаляемой конституенты при отождествлении',