diff --git a/src/components/App/SideBar/Relevance/Episode/index.tsx b/src/components/App/SideBar/Relevance/Episode/index.tsx index 5f30844a6..e11201624 100644 --- a/src/components/App/SideBar/Relevance/Episode/index.tsx +++ b/src/components/App/SideBar/Relevance/Episode/index.tsx @@ -6,6 +6,7 @@ import { Flex } from '~/components/common/Flex' import { highlightSearchTerm } from '~/components/common/Highlight/Highlight' import { Text } from '~/components/common/Text' import { useAppStore } from '~/stores/useAppStore' +import { useGraphStore } from '~/stores/useGraphStore' import { NodeExtended } from '~/types' import { colors } from '~/utils/colors' import { Default } from './Default' @@ -70,6 +71,7 @@ export const Episode = ({ node, }: Props) => { const searchTerm = useAppStore((s) => s.currentSearch) + const { setHoveredNode } = useGraphStore((s) => s) const text = highlightSearchTerm(String(newText), searchTerm) as string const name = highlightSearchTerm(String(newName), searchTerm) as string const subtitleSource = type === 'show' ? '' : showTitle @@ -78,7 +80,16 @@ export const Episode = ({ const defaultViewTypes = ['Tweet', 'person', 'guest', 'topic', 'document'] return ( - + { + setHoveredNode(null) + }} + onMouseOver={() => { + setHoveredNode(node) + }} + > {!defaultViewTypes.includes(type) && ( { [texture, material], ) - return material + return { material, texture } } diff --git a/src/components/Universe/Graph/Cubes/Cube/index.tsx b/src/components/Universe/Graph/Cubes/Cube/index.tsx index 09cbd48ad..e7306671e 100644 --- a/src/components/Universe/Graph/Cubes/Cube/index.tsx +++ b/src/components/Universe/Graph/Cubes/Cube/index.tsx @@ -19,7 +19,7 @@ export const Cube = memo(({ node, hide, animated }: Props) => { const selectedNode = useSelectedNode() const { showSelectionGraph } = useGraphStore((s) => s) const isSelected = !!selectedNode && node.ref_id === selectedNode.ref_id - const material = useMaterial(node.image_url || 'noimage.jpeg', false) + const { material } = useMaterial(node.image_url || 'noimage.jpeg', false) useFrame((_, delta) => { if (animated && ref.current) { diff --git a/src/components/Universe/Graph/Cubes/NodePoints/Point/index.tsx b/src/components/Universe/Graph/Cubes/NodePoints/Point/index.tsx index 49e592427..c0984dcdd 100644 --- a/src/components/Universe/Graph/Cubes/NodePoints/Point/index.tsx +++ b/src/components/Universe/Graph/Cubes/NodePoints/Point/index.tsx @@ -6,9 +6,7 @@ type Props = { } export const Point = ({ color, scale }: Props) => ( - <> - - - - + + + ) diff --git a/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/constants.ts b/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/constants.ts new file mode 100644 index 000000000..59583c162 --- /dev/null +++ b/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/constants.ts @@ -0,0 +1,20 @@ +import { MeshStandardMaterial, TextureLoader } from 'three' +import { smoothness } from '../../../Cube/constants' + +export const loader = new TextureLoader() + +export const noImageTexture = loader.load('noimage.jpeg') + +export const noImageMaterial = new MeshStandardMaterial({ + ...smoothness, + map: noImageTexture, +}) + +export const transparentValue = 0.4 + +export const noImageTransparentMaterial = new MeshStandardMaterial({ + ...smoothness, + map: noImageTexture, + transparent: true, + opacity: transparentValue, +}) diff --git a/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts b/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts new file mode 100644 index 000000000..896732e3d --- /dev/null +++ b/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react' +import { Texture } from 'three' +import { loader } from './constants' + +type materialRecord = { + texture: THREE.Texture + material: THREE.MeshStandardMaterial +} + +const cachedMaterials: Record = {} + +export const useTexture = (url: string) => { + const [texture, setTexture] = useState(null) + + useEffect(() => { + if (!url) { + setTexture(null) + + return + } + + const cashPath = url + + if (cachedMaterials[cashPath]) { + setTexture(cachedMaterials[cashPath].texture) + + return + } + + loader.load( + url, + (loadedTexture) => { + setTexture(loadedTexture) + }, + undefined, + () => { + setTexture(null) + }, + ) + }, [url]) + + useEffect( + () => + function cleanup() { + if (texture) { + texture.dispose() + } + }, + [texture], + ) + + return { texture } +} diff --git a/src/components/Universe/Graph/Cubes/Text/index.tsx b/src/components/Universe/Graph/Cubes/Text/index.tsx index 8b1e6c005..21a6d7775 100644 --- a/src/components/Universe/Graph/Cubes/Text/index.tsx +++ b/src/components/Universe/Graph/Cubes/Text/index.tsx @@ -1,4 +1,4 @@ -import { Billboard, Svg, Text } from '@react-three/drei' +import { Billboard, Plane, Svg, Text } from '@react-three/drei' import { useFrame } from '@react-three/fiber' import { memo, useMemo, useRef } from 'react' import { Mesh, MeshBasicMaterial, Vector3 } from 'three' @@ -11,6 +11,7 @@ import { colors } from '~/utils/colors' import { removeEmojis } from '~/utils/removeEmojisFromText' import { truncateText } from '~/utils/truncateText' import { fontProps } from './constants' +import { useTexture } from './hooks/useTexture' const COLORS_MAP = [ '#fff', @@ -69,17 +70,20 @@ function splitStringIntoThreeParts(text: string): string { export const TextNode = memo(({ node, hide, isHovered }: Props) => { const svgRef = useRef(null) const ringRef = useRef(null) + const circleRef = useRef(null) const selectedNode = useSelectedNode() const nodePositionRef = useRef(new Vector3()) + const { texture } = useTexture(node.properties?.image_url || '') + const selectedNodeRelativeIds = useSelectedNodeRelativeIds() const isRelative = selectedNodeRelativeIds.includes(node?.ref_id || '') const isSelected = !!selectedNode && selectedNode?.ref_id === node.ref_id const showSelectionGraph = useGraphStore((s) => s.showSelectionGraph) const { normalizedSchemasByType } = useSchemaStore((s) => s) - useFrame(({ camera }) => { + useFrame(({ camera, clock }) => { const checkDistance = () => { const nodePosition = nodePositionRef.current.setFromMatrixPosition(ringRef.current!.matrixWorld) @@ -90,6 +94,20 @@ export const TextNode = memo(({ node, hide, isHovered }: Props) => { // Set visibility based on distance } + if (isHovered) { + if (ringRef.current) { + ringRef.current.visible = true + } + + const scale = 1 + 0.2 * Math.sin(clock.getElapsedTime() * 2) // Adjust frequency and amplitude + + if (circleRef.current) { + circleRef.current.scale.set(scale, scale, scale) + } + + return + } + checkDistance() }) @@ -134,27 +152,68 @@ export const TextNode = memo(({ node, hide, isHovered }: Props) => { const iconName = Icon ? primaryIcon : 'NodesIcon' const sanitizedNodeName = removeEmojis(String(node.name)) + const uniforms = { + u_texture: { value: texture }, + u_radius: { value: 0.5 }, // Radius of the circular mask + } + return ( - { - svg.traverse((child) => { - if (child instanceof Mesh) { - // Apply dynamic color to meshes - // eslint-disable-next-line no-param-reassign - child.material = new MeshBasicMaterial({ color }) - } - }) - }} - position={[-15, 15, 0]} - scale={2} - src={`svg-icons/${iconName}.svg`} - strokeMaterial={{ color: 'yellow' }} - userData={node} - /> + {isHovered ? ( + + + + + ) : null} + {node.properties?.image_url && node.node_type === 'Person' && texture ? ( + + + + ) : ( + { + svg.traverse((child) => { + if (child instanceof Mesh) { + // Apply dynamic color to meshes + // eslint-disable-next-line no-param-reassign + child.material = new MeshBasicMaterial({ color }) + } + }) + }} + position={[-15, 15, 0]} + scale={2} + src={`svg-icons/${iconName}.svg`} + strokeMaterial={{ color: 'yellow' }} + userData={node} + /> + )} {node.name && ( { diff --git a/src/components/Universe/Graph/index.tsx b/src/components/Universe/Graph/index.tsx index 8f1025002..55e057f36 100644 --- a/src/components/Universe/Graph/index.tsx +++ b/src/components/Universe/Graph/index.tsx @@ -139,7 +139,7 @@ export const Graph = () => { material.color = new Color(lineColor) material.transparent = true - material.opacity = 0.5 + material.opacity = 0.3 } }) }