diff --git a/src/components/mindset/components/Scene/Board/Edges/index.tsx b/src/components/mindset/components/Scene/Board/Edges/index.tsx index ab5e3f434..aedea3f1b 100644 --- a/src/components/mindset/components/Scene/Board/Edges/index.tsx +++ b/src/components/mindset/components/Scene/Board/Edges/index.tsx @@ -1,3 +1,4 @@ +import { Text } from '@react-three/drei' import { useMemo } from 'react' import { Vector3 } from 'three' @@ -6,15 +7,22 @@ type Props = { targetPosition: { x: number; y: number; z: number } color?: string arrowSize?: number + label?: string // Добавляем текстовую метку } -export const Edge = ({ sourcePosition, targetPosition, color = 'white', arrowSize = 1 }: Props) => { - const points = useMemo(() => { +export const Edge = ({ sourcePosition, targetPosition, color = 'white', arrowSize = 1, label = 'label' }: Props) => { + const { points, textPosition, textRotation } = useMemo(() => { const start = new Vector3(sourcePosition.x, sourcePosition.y, sourcePosition.z) const end = new Vector3(targetPosition.x, targetPosition.y, targetPosition.z) + + // Вычисляем направление и середину линии const direction = new Vector3().subVectors(end, start).normalize() + const midpoint = new Vector3().addVectors(start, end).multiplyScalar(0.5) + + // Угол поворота текста относительно оси Z + const angle = Math.atan2(direction.y, direction.x) - // Calculate arrowhead points + // Вычисляем точки для линии и стрелок const arrowLeft = new Vector3() .copy(direction) .multiplyScalar(-arrowSize) @@ -25,21 +33,36 @@ export const Edge = ({ sourcePosition, targetPosition, color = 'white', arrowSiz .multiplyScalar(-arrowSize) .applyAxisAngle(new Vector3(0, 0, 1), -Math.PI / 6) - // Return line points + arrowhead points - return [start, end, end.clone(), end.clone().add(arrowLeft), end.clone(), end.clone().add(arrowRight)] + const pointsFinal = [start, end, end.clone(), end.clone().add(arrowLeft), end.clone(), end.clone().add(arrowRight)] + + return { points: pointsFinal, textPosition: midpoint, textRotation: angle } }, [sourcePosition, targetPosition, arrowSize]) return ( - - - [p.x, p.y, p.z]))} - attach="attributes-position" - count={points.length} - itemSize={3} - /> - - - + <> + + + [p.x, p.y, p.z]))} + attach="attributes-position" + count={points.length} + itemSize={3} + /> + + + + {label && ( + + {label} + + )} + ) } diff --git a/src/components/mindset/components/Scene/Board/index.tsx b/src/components/mindset/components/Scene/Board/index.tsx index 7dbe0e359..7794968c7 100644 --- a/src/components/mindset/components/Scene/Board/index.tsx +++ b/src/components/mindset/components/Scene/Board/index.tsx @@ -1,12 +1,27 @@ import { useThree } from '@react-three/fiber' import { Fragment, useEffect, useMemo, useState } from 'react' +import { Vector3 } from 'three' import { useDataStore } from '~/stores/useDataStore' +import { Link } from '~/types' import { Edge } from './Edges' import { Node } from './Node' const nodeWidth = 144 / 10 const nodeHeight = 84 / 10 +type LinkExtended = Link & { + sourcePositions: { + x?: number + y?: number + z?: number + } + targetPositions: { + x?: number + y?: number + z?: number + } +} + export const Board = () => { const state = useThree() const { dataInitial } = useDataStore((s) => s) @@ -69,68 +84,63 @@ export const Board = () => { }) .filter((node) => node.node_type !== 'Clip' && node.node_type !== 'Episode' && node.node_type !== 'Show') - // 'fc9fc515-d9f8-4e28-ac4a-89597db29a2f' - // '2f919e76-90cd-41e2-92eb-15d8c89e1fd2' - // Step 2: Calculate positions for related nodes - const relatedNodes = nodesWithPositions.reduce((acc, marker) => { - const related = dataInitial.nodes - .filter((node) => - dataInitial.links.some( - (edge) => - node.node_type !== 'Episode' && - node.node_type !== 'Clip' && - ((edge.source === marker.ref_id && edge.target === node.ref_id) || - (edge.target === marker.ref_id && edge.source === node.ref_id)), - ), - ) - .map((relatedNode, index) => { - // Calculate positions relative to the main node - const { x } = marker - const y = (Math.floor(index / 2) + 1) * nodeHeight * 2 * (index % 2 === 0 ? 1 : -1) // Offset based on index - const z = 0 - - return { ...relatedNode, x, y, z } - }) - - acc[marker.ref_id] = related + // Step 2 & Step 3: Calculate positions for related nodes and edges + const relatedNodesWithEdges = nodesWithPositions.reduce((acc, marker) => { + const linksRelatedToNodeWithoutTimestamp = dataInitial.links.filter( + (link) => !link?.properties?.start && [link.target, link.source].includes(marker.ref_id), + ) - return acc - }, {} as Record) - - // Step 3: Calculate edge positions based on node and related node positions - // eslint-disable-next-line camelcase - const edgesWithPositions = Object.entries(relatedNodes).flatMap(([ref_id, related]) => { - // Gather all node ids in this group (ref_id + related nodes) - // eslint-disable-next-line camelcase - const allNodesInGroup = [ref_id, ...related.map((node) => node.ref_id)] - - // Filter edges that connect nodes within this group - const edgesInGroup = dataInitial.links.filter( - (edge) => allNodesInGroup.includes(edge.source) && allNodesInGroup.includes(edge.target), + const relatedNodes = dataInitial.nodes.filter( + (node) => + node.node_type !== 'Episode' && + node.node_type !== 'Clip' && + node.ref_id !== marker.ref_id && + linksRelatedToNodeWithoutTimestamp.some((link) => [link.source, link.target].includes(node.ref_id)), ) - // Map these edges to include source and target positions - return edgesInGroup.map((edge) => { - const sourceNode = - nodesWithPositions.find((node) => node.ref_id === edge.source) || - related.find((node) => node.ref_id === edge.source) + const relatedNodesWithPositions = relatedNodes.map((relatedNode, index) => { + const { x } = marker + const y = (Math.floor(index / 2) + 1) * nodeHeight * 2 * (index % 2 === 0 ? 1 : -1) // Offset based on index + const z = 0 + + return { ...relatedNode, x, y, z } + }) + + const relatedLinksWithPositions = linksRelatedToNodeWithoutTimestamp.map((link) => { + if (link.source === marker.ref_id) { + const targetNode = relatedNodesWithPositions.find((node) => node.ref_id === link.target) + + return { + ...link, + sourcePositions: { x: marker.x, y: marker.y, z: marker.z }, + targetPositions: { x: targetNode?.x, y: targetNode?.y, z: targetNode?.z }, + } + } - const targetNode = - nodesWithPositions.find((node) => node.ref_id === edge.target) || - related.find((node) => node.ref_id === edge.target) + const sourceNode = relatedNodesWithPositions.find((node) => node.ref_id === link.source) return { - ...edge, - sourcePosition: sourceNode ? { x: sourceNode.x, y: sourceNode.y, z: sourceNode.z } : null, - targetPosition: targetNode ? { x: targetNode.x, y: targetNode.y, z: targetNode.z } : null, + ...link, + sourcePositions: { x: sourceNode?.x, y: sourceNode?.y, z: sourceNode?.z }, + targetPositions: { x: marker.x, y: marker.y, z: marker.z }, } }) - }) + + acc[marker.ref_id] = { + nodes: relatedNodesWithPositions, + edges: relatedLinksWithPositions, + } + + return acc + }, {} as Record) + + // Combine all edges from relatedNodesWithEdges + const allEdgesWithPositions = Object.values(relatedNodesWithEdges).flatMap((group) => group.edges) return { nodes: nodesWithPositions, - edges: edgesWithPositions, - relatedNodes, + edges: allEdgesWithPositions, + relatedNodes: Object.fromEntries(Object.entries(relatedNodesWithEdges).map(([key, group]) => [key, group.nodes])), } }, [dataInitial, viewport.width]) @@ -170,21 +180,17 @@ export const Board = () => { ))} {/* Render Edges */} - {processedData.edges.map((edge, index) => { - if (!edge.sourcePosition || !edge.targetPosition) { - return null - } - - const { sourcePosition, targetPosition } = edge - - return ( - // eslint-disable-next-line react/no-array-index-key - - {/* Line */} - - - ) - })} + {processedData.edges.map((edge, index) => + edge?.sourcePositions && edge?.targetPositions ? ( + + ) : null, + )} ) }