diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-edge-types.ts b/rsconcept/frontend/src/features/rsform/components/term-graph/graph/tg-edge-types.ts similarity index 100% rename from rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-edge-types.ts rename to rsconcept/frontend/src/features/rsform/components/term-graph/graph/tg-edge-types.ts diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node-types.ts b/rsconcept/frontend/src/features/rsform/components/term-graph/graph/tg-node-types.ts similarity index 100% rename from rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node-types.ts rename to rsconcept/frontend/src/features/rsform/components/term-graph/graph/tg-node-types.ts diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node.tsx b/rsconcept/frontend/src/features/rsform/components/term-graph/graph/tg-node.tsx similarity index 87% rename from rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node.tsx rename to rsconcept/frontend/src/features/rsform/components/term-graph/graph/tg-node.tsx index 7acf9067..500a82ad 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node.tsx +++ b/rsconcept/frontend/src/features/rsform/components/term-graph/graph/tg-node.tsx @@ -6,11 +6,11 @@ import clsx from 'clsx'; import { APP_COLORS } from '@/styling/colors'; import { globalIDs } from '@/utils/constants'; -import { colorBgGraphNode } from '../../../../colors'; -import { labelCstTypification } from '../../../../labels'; -import { type TGNodeInternal } from '../../../../models/graph-layout'; -import { type IConstituenta } from '../../../../models/rsform'; -import { useTermGraphStore } from '../../../../stores/term-graph'; +import { colorBgGraphNode } from '../../../colors'; +import { labelCstTypification } from '../../../labels'; +import { type TGNodeInternal } from '../../../models/graph-api'; +import { type IConstituenta } from '../../../models/rsform'; +import { useTermGraphStore } from '../../../stores/term-graph'; const DESCRIPTION_THRESHOLD = 15; const LABEL_THRESHOLD = 3; diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/schemas-guide.tsx b/rsconcept/frontend/src/features/rsform/components/term-graph/schemas-guide.tsx similarity index 97% rename from rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/schemas-guide.tsx rename to rsconcept/frontend/src/features/rsform/components/term-graph/schemas-guide.tsx index 6407dc5c..bacd2fb4 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/schemas-guide.tsx +++ b/rsconcept/frontend/src/features/rsform/components/term-graph/schemas-guide.tsx @@ -5,7 +5,7 @@ import { Tooltip } from '@/components/container'; import { IconHelp } from '@/components/icons'; import { globalIDs, prefixes } from '@/utils/constants'; -import { colorBgSchemas } from '../../../colors'; +import { colorBgSchemas } from '../../colors'; interface SchemasGuideProps { schema: IRSForm; diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/select-coloring.tsx b/rsconcept/frontend/src/features/rsform/components/term-graph/select-coloring.tsx similarity index 93% rename from rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/select-coloring.tsx rename to rsconcept/frontend/src/features/rsform/components/term-graph/select-coloring.tsx index a410134b..4d258dc3 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/select-coloring.tsx +++ b/rsconcept/frontend/src/features/rsform/components/term-graph/select-coloring.tsx @@ -5,8 +5,8 @@ import { type IRSForm } from '@/features/rsform/models/rsform'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/input/select'; import { cn } from '@/components/utils'; -import { mapLabelColoring } from '../../../labels'; -import { useTermGraphStore } from '../../../stores/term-graph'; +import { mapLabelColoring } from '../../labels'; +import { useTermGraphStore } from '../../stores/term-graph'; import { SchemasGuide } from './schemas-guide'; diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-focused-cst.tsx b/rsconcept/frontend/src/features/rsform/components/term-graph/toolbar-focused-cst.tsx similarity index 100% rename from rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-focused-cst.tsx rename to rsconcept/frontend/src/features/rsform/components/term-graph/toolbar-focused-cst.tsx diff --git a/rsconcept/frontend/src/features/rsform/models/graph-api.ts b/rsconcept/frontend/src/features/rsform/models/graph-api.ts new file mode 100644 index 00000000..047fa03a --- /dev/null +++ b/rsconcept/frontend/src/features/rsform/models/graph-api.ts @@ -0,0 +1,117 @@ +/** + * Module: Graph of Terms graphical representation. + */ +import { type Edge, type Node } from 'reactflow'; +import dagre from '@dagrejs/dagre'; + +import { PARAMETER } from '@/utils/constants'; + +import { CstType } from '../backend/types'; +import { type GraphFilterParams } from '../stores/term-graph'; + +import { type IConstituenta, type IRSForm } from './rsform'; + +export interface TGNodeState { + cst: IConstituenta; + focused: boolean; +} + +/** Represents graph node. */ +export interface TGNodeData extends Node { + id: string; + data: TGNodeState; +} + +/** Represents graph node internal data. */ +export interface TGNodeInternal { + id: string; + data: TGNodeState; + selected: boolean; + dragging: boolean; + xPos: number; + yPos: number; +} + +export function applyLayout(nodes: Node[], edges: Edge[], subLabels: boolean) { + const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); + dagreGraph.setGraph({ + rankdir: 'TB', + ranksep: subLabels ? 60 : 40, + nodesep: subLabels ? 100 : 20, + ranker: 'network-simplex' + }); + nodes.forEach(node => { + dagreGraph.setNode(node.id, { width: 2 * PARAMETER.graphNodeRadius, height: 2 * PARAMETER.graphNodeRadius }); + }); + + edges.forEach(edge => { + dagreGraph.setEdge(edge.source, edge.target); + }); + + dagre.layout(dagreGraph); + + nodes.forEach(node => { + const nodeWithPosition = dagreGraph.node(node.id); + node.position.x = nodeWithPosition.x - PARAMETER.graphNodeRadius; + node.position.y = nodeWithPosition.y - PARAMETER.graphNodeRadius; + }); +} + +export function produceFilteredGraph(schema: IRSForm, params: GraphFilterParams, focusCst: IConstituenta | null) { + const filtered = schema.graph.clone(); + const allowedTypes: CstType[] = (() => { + const result: CstType[] = []; + if (params.allowBase) result.push(CstType.BASE); + if (params.allowStruct) result.push(CstType.STRUCTURED); + if (params.allowTerm) result.push(CstType.TERM); + if (params.allowAxiom) result.push(CstType.AXIOM); + if (params.allowFunction) result.push(CstType.FUNCTION); + if (params.allowPredicate) result.push(CstType.PREDICATE); + if (params.allowConstant) result.push(CstType.CONSTANT); + if (params.allowTheorem) result.push(CstType.THEOREM); + return result; + })(); + + if (params.noHermits) { + filtered.removeIsolated(); + } + if (params.noTemplates) { + schema.items.forEach(cst => { + if (cst !== focusCst && cst.is_template) { + filtered.foldNode(cst.id); + } + }); + } + if (allowedTypes.length < Object.values(CstType).length) { + schema.items.forEach(cst => { + if (cst !== focusCst && !allowedTypes.includes(cst.cst_type)) { + filtered.foldNode(cst.id); + } + }); + } + if (!focusCst && params.foldDerived) { + schema.items.forEach(cst => { + if (cst.spawner) { + filtered.foldNode(cst.id); + } + }); + } + if (focusCst) { + const includes: number[] = [ + focusCst.id, + ...focusCst.spawn, + ...(params.focusShowInputs ? schema.graph.expandInputs([focusCst.id]) : []), + ...(params.focusShowOutputs ? schema.graph.expandOutputs([focusCst.id]) : []) + ]; + schema.items.forEach(cst => { + if (!includes.includes(cst.id)) { + filtered.foldNode(cst.id); + } + }); + } + if (params.noTransitive) { + filtered.transitiveReduction(); + } + + return filtered; +} diff --git a/rsconcept/frontend/src/features/rsform/models/graph-layout.ts b/rsconcept/frontend/src/features/rsform/models/graph-layout.ts deleted file mode 100644 index c51e266d..00000000 --- a/rsconcept/frontend/src/features/rsform/models/graph-layout.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Module: Graph of Terms graphical representation. - */ -import { type Edge, type Node } from 'reactflow'; -import dagre from '@dagrejs/dagre'; - -import { PARAMETER } from '@/utils/constants'; - -import { type IConstituenta } from './rsform'; - -export interface TGNodeState { - cst: IConstituenta; - focused: boolean; -} - -/** Represents graph node. */ -export interface TGNodeData extends Node { - id: string; - data: TGNodeState; -} - -/** Represents graph node internal data. */ -export interface TGNodeInternal { - id: string; - data: TGNodeState; - selected: boolean; - dragging: boolean; - xPos: number; - yPos: number; -} - -export function applyLayout(nodes: Node[], edges: Edge[], subLabels: boolean) { - const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); - dagreGraph.setGraph({ - rankdir: 'TB', - ranksep: subLabels ? 60 : 40, - nodesep: subLabels ? 100 : 20, - ranker: 'network-simplex' - }); - nodes.forEach(node => { - dagreGraph.setNode(node.id, { width: 2 * PARAMETER.graphNodeRadius, height: 2 * PARAMETER.graphNodeRadius }); - }); - - edges.forEach(edge => { - dagreGraph.setEdge(edge.source, edge.target); - }); - - dagre.layout(dagreGraph); - - nodes.forEach(node => { - const nodeWithPosition = dagreGraph.node(node.id); - node.position.x = nodeWithPosition.x - PARAMETER.graphNodeRadius; - node.position.y = nodeWithPosition.y - PARAMETER.graphNodeRadius; - }); -} diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx index 627750a8..a6218347 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx @@ -3,23 +3,22 @@ import { useEffect, useRef } from 'react'; import { type Edge, MarkerType, type Node, useEdgesState, useNodesState, useOnSelectionChange } from 'reactflow'; -import { applyLayout, type TGNodeData } from '@/features/rsform/models/graph-layout'; - import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow'; import { useMainHeight } from '@/stores/app-layout'; import { PARAMETER } from '@/utils/constants'; import { withPreventDefault } from '@/utils/utils'; import { useMutatingRSForm } from '../../../backend/use-mutating-rsform'; +import { TGEdgeTypes } from '../../../components/term-graph/graph/tg-edge-types'; +import { TGNodeTypes } from '../../../components/term-graph/graph/tg-node-types'; +import { SelectColoring } from '../../../components/term-graph/select-coloring'; +import { ToolbarFocusedCst } from '../../../components/term-graph/toolbar-focused-cst'; import { ToolbarGraphSelection } from '../../../components/toolbar-graph-selection'; +import { applyLayout, type TGNodeData } from '../../../models/graph-api'; import { isBasicConcept } from '../../../models/rsform-api'; import { useTermGraphStore } from '../../../stores/term-graph'; import { useRSEdit } from '../rsedit-context'; -import { TGEdgeTypes } from './graph/tg-edge-types'; -import { TGNodeTypes } from './graph/tg-node-types'; -import { SelectColoring } from './select-coloring'; -import { ToolbarFocusedCst } from './toolbar-focused-cst'; import { ToolbarTermGraph } from './toolbar-term-graph'; import { useFilteredGraph } from './use-filtered-graph'; import { ViewHidden } from './view-hidden'; diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/use-filtered-graph.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/use-filtered-graph.tsx index 2fe78037..3b73f7de 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/use-filtered-graph.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/use-filtered-graph.tsx @@ -1,6 +1,5 @@ -import { CstType } from '../../../backend/types'; -import { type IConstituenta, type IRSForm } from '../../../models/rsform'; -import { type GraphFilterParams, useTermGraphStore } from '../../../stores/term-graph'; +import { produceFilteredGraph } from '../../../models/graph-api'; +import { useTermGraphStore } from '../../../stores/term-graph'; import { useRSEdit } from '../rsedit-context'; export function useFilteredGraph() { @@ -12,63 +11,3 @@ export function useFilteredGraph() { return { filteredGraph, hidden }; } - -// ====== Internals ========= -export function produceFilteredGraph(schema: IRSForm, params: GraphFilterParams, focusCst: IConstituenta | null) { - const filtered = schema.graph.clone(); - const allowedTypes: CstType[] = (() => { - const result: CstType[] = []; - if (params.allowBase) result.push(CstType.BASE); - if (params.allowStruct) result.push(CstType.STRUCTURED); - if (params.allowTerm) result.push(CstType.TERM); - if (params.allowAxiom) result.push(CstType.AXIOM); - if (params.allowFunction) result.push(CstType.FUNCTION); - if (params.allowPredicate) result.push(CstType.PREDICATE); - if (params.allowConstant) result.push(CstType.CONSTANT); - if (params.allowTheorem) result.push(CstType.THEOREM); - return result; - })(); - - if (params.noHermits) { - filtered.removeIsolated(); - } - if (params.noTemplates) { - schema.items.forEach(cst => { - if (cst !== focusCst && cst.is_template) { - filtered.foldNode(cst.id); - } - }); - } - if (allowedTypes.length < Object.values(CstType).length) { - schema.items.forEach(cst => { - if (cst !== focusCst && !allowedTypes.includes(cst.cst_type)) { - filtered.foldNode(cst.id); - } - }); - } - if (!focusCst && params.foldDerived) { - schema.items.forEach(cst => { - if (cst.spawner) { - filtered.foldNode(cst.id); - } - }); - } - if (focusCst) { - const includes: number[] = [ - focusCst.id, - ...focusCst.spawn, - ...(params.focusShowInputs ? schema.graph.expandInputs([focusCst.id]) : []), - ...(params.focusShowOutputs ? schema.graph.expandOutputs([focusCst.id]) : []) - ]; - schema.items.forEach(cst => { - if (!includes.includes(cst.id)) { - filtered.foldNode(cst.id); - } - }); - } - if (params.noTransitive) { - filtered.transitiveReduction(); - } - - return filtered; -}