From c5d63293dc0b17fd150cb361abc1771e6223c1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=83=D0=BB?= Date: Fri, 1 Nov 2024 10:35:52 +0300 Subject: [PATCH 1/2] feat: in progress --- .../App/SideBar/Relevance/Episode/index.tsx | 9 +- .../Cubes/Cube/hooks/useMaterial/index.ts | 2 +- .../Universe/Graph/Cubes/Cube/index.tsx | 2 +- .../Graph/Cubes/NodePoints/Point/index.tsx | 25 ++++- .../Universe/Graph/Cubes/NodePoints/index.tsx | 14 ++- .../Cubes/Text/hooks/useTexture/constants.ts | 20 ++++ .../Cubes/Text/hooks/useTexture/index.ts | 57 ++++++++++ .../Universe/Graph/Cubes/Text/index.tsx | 100 ++++++++++++++---- 8 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 src/components/Universe/Graph/Cubes/Text/hooks/useTexture/constants.ts create mode 100644 src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts diff --git a/src/components/App/SideBar/Relevance/Episode/index.tsx b/src/components/App/SideBar/Relevance/Episode/index.tsx index 5f30844a6..a4c989760 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,12 @@ export const Episode = ({ const defaultViewTypes = ['Tweet', 'person', 'guest', 'topic', 'document'] return ( - + setHoveredNode(node)} + onPointerOut={() => setHoveredNode(null)} + > {!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..eecbb7384 100644 --- a/src/components/Universe/Graph/Cubes/NodePoints/Point/index.tsx +++ b/src/components/Universe/Graph/Cubes/NodePoints/Point/index.tsx @@ -1,14 +1,29 @@ import { Billboard, Instance } from '@react-three/drei' +import { useFrame } from '@react-three/fiber' +import { useRef } from 'react' +import * as THREE from 'three' type Props = { color: string scale: number + shouldPulsate?: boolean } -export const Point = ({ color, scale }: Props) => ( - <> +export const Point = ({ color, scale, shouldPulsate }: Props) => { + const instanceRef = useRef(null) + + useFrame(({ clock }) => { + if (instanceRef.current && shouldPulsate) { + // Pulsate between 0.9 and 1.1 of the base scale + const pulsateScale = scale * (0.9 + 0.2 * Math.sin(clock.elapsedTime * 2)) + + instanceRef.current.scale.set(pulsateScale, pulsateScale, pulsateScale) + } + }) + + return ( - + - -) + ) +} diff --git a/src/components/Universe/Graph/Cubes/NodePoints/index.tsx b/src/components/Universe/Graph/Cubes/NodePoints/index.tsx index ddb0e35a0..b8ad24a17 100644 --- a/src/components/Universe/Graph/Cubes/NodePoints/index.tsx +++ b/src/components/Universe/Graph/Cubes/NodePoints/index.tsx @@ -2,7 +2,7 @@ import { Instances } from '@react-three/drei' import { memo, useMemo } from 'react' import { BufferGeometry, TorusGeometry } from 'three' import { useDataStore, useNodeTypes } from '~/stores/useDataStore' -import { useSelectedNode } from '~/stores/useGraphStore' +import { useHoveredNode, useSelectedNode } from '~/stores/useGraphStore' import { useSchemaStore } from '~/stores/useSchemaStore' import { NodeExtended } from '~/types' import { colors } from '~/utils' @@ -41,11 +41,14 @@ const COLORS_MAP = [ // eslint-disable-next-line no-underscore-dangle const _NodePoints = () => { const selectedNode = useSelectedNode() + const hoveredNode = useHoveredNode() const data = useDataStore((s) => s.dataInitial) const { normalizedSchemasByType } = useSchemaStore((s) => s) const nodeTypes = useNodeTypes() const ringGeometry = useMemo(() => new TorusGeometry(30, 2, 16, 100), []) + console.log(hoveredNode) + return ( <> { const primaryColor = normalizedSchemasByType[node.node_type]?.primary_color const color = primaryColor ?? (COLORS_MAP[nodeTypes.indexOf(node.node_type)] || colors.white) - return + return ( + + ) })} 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..59bc12eaa --- /dev/null +++ b/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts @@ -0,0 +1,57 @@ +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) => { + // on load + + setTexture(loadedTexture) + }, + undefined, + () => { + console.log(123) + // on error, set blank meterial + 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..d35e0a68b 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,69 @@ 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 === 'Persons' && 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 && ( Date: Fri, 1 Nov 2024 18:21:57 +0300 Subject: [PATCH 2/2] feat: add person image to graph --- .../App/SideBar/Relevance/Episode/index.tsx | 8 ++++-- .../Graph/Cubes/NodePoints/Point/index.tsx | 27 ++++--------------- .../Universe/Graph/Cubes/NodePoints/index.tsx | 14 ++-------- .../Cubes/Text/hooks/useTexture/index.ts | 4 --- .../Universe/Graph/Cubes/Text/index.tsx | 5 ++-- src/components/Universe/Graph/Cubes/index.tsx | 2 +- src/components/Universe/Graph/index.tsx | 2 +- 7 files changed, 17 insertions(+), 45 deletions(-) diff --git a/src/components/App/SideBar/Relevance/Episode/index.tsx b/src/components/App/SideBar/Relevance/Episode/index.tsx index a4c989760..e11201624 100644 --- a/src/components/App/SideBar/Relevance/Episode/index.tsx +++ b/src/components/App/SideBar/Relevance/Episode/index.tsx @@ -83,8 +83,12 @@ export const Episode = ({ setHoveredNode(node)} - onPointerOut={() => setHoveredNode(null)} + onMouseLeave={() => { + setHoveredNode(null) + }} + onMouseOver={() => { + setHoveredNode(node) + }} > {!defaultViewTypes.includes(type) && ( { - const instanceRef = useRef(null) - - useFrame(({ clock }) => { - if (instanceRef.current && shouldPulsate) { - // Pulsate between 0.9 and 1.1 of the base scale - const pulsateScale = scale * (0.9 + 0.2 * Math.sin(clock.elapsedTime * 2)) - - instanceRef.current.scale.set(pulsateScale, pulsateScale, pulsateScale) - } - }) - - return ( - - - - ) -} +export const Point = ({ color, scale }: Props) => ( + + + +) diff --git a/src/components/Universe/Graph/Cubes/NodePoints/index.tsx b/src/components/Universe/Graph/Cubes/NodePoints/index.tsx index b8ad24a17..ddb0e35a0 100644 --- a/src/components/Universe/Graph/Cubes/NodePoints/index.tsx +++ b/src/components/Universe/Graph/Cubes/NodePoints/index.tsx @@ -2,7 +2,7 @@ import { Instances } from '@react-three/drei' import { memo, useMemo } from 'react' import { BufferGeometry, TorusGeometry } from 'three' import { useDataStore, useNodeTypes } from '~/stores/useDataStore' -import { useHoveredNode, useSelectedNode } from '~/stores/useGraphStore' +import { useSelectedNode } from '~/stores/useGraphStore' import { useSchemaStore } from '~/stores/useSchemaStore' import { NodeExtended } from '~/types' import { colors } from '~/utils' @@ -41,14 +41,11 @@ const COLORS_MAP = [ // eslint-disable-next-line no-underscore-dangle const _NodePoints = () => { const selectedNode = useSelectedNode() - const hoveredNode = useHoveredNode() const data = useDataStore((s) => s.dataInitial) const { normalizedSchemasByType } = useSchemaStore((s) => s) const nodeTypes = useNodeTypes() const ringGeometry = useMemo(() => new TorusGeometry(30, 2, 16, 100), []) - console.log(hoveredNode) - return ( <> { const primaryColor = normalizedSchemasByType[node.node_type]?.primary_color const color = primaryColor ?? (COLORS_MAP[nodeTypes.indexOf(node.node_type)] || colors.white) - return ( - - ) + return })} diff --git a/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts b/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts index 59bc12eaa..896732e3d 100644 --- a/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts +++ b/src/components/Universe/Graph/Cubes/Text/hooks/useTexture/index.ts @@ -30,14 +30,10 @@ export const useTexture = (url: string) => { loader.load( url, (loadedTexture) => { - // on load - setTexture(loadedTexture) }, undefined, () => { - console.log(123) - // on error, set blank meterial setTexture(null) }, ) diff --git a/src/components/Universe/Graph/Cubes/Text/index.tsx b/src/components/Universe/Graph/Cubes/Text/index.tsx index d35e0a68b..21a6d7775 100644 --- a/src/components/Universe/Graph/Cubes/Text/index.tsx +++ b/src/components/Universe/Graph/Cubes/Text/index.tsx @@ -163,10 +163,10 @@ export const TextNode = memo(({ node, hide, isHovered }: Props) => { {isHovered ? ( - + ) : null} - {node.properties?.image_url && node.node_type === 'Persons' && texture ? ( + {node.properties?.image_url && node.node_type === 'Person' && texture ? ( { } } `} - transparent uniforms={uniforms} vertexShader={` varying vec2 vUv; diff --git a/src/components/Universe/Graph/Cubes/index.tsx b/src/components/Universe/Graph/Cubes/index.tsx index 73dfb9295..b9474f7e6 100644 --- a/src/components/Universe/Graph/Cubes/index.tsx +++ b/src/components/Universe/Graph/Cubes/index.tsx @@ -120,7 +120,7 @@ export const Cubes = memo(() => { diff --git a/src/components/Universe/Graph/index.tsx b/src/components/Universe/Graph/index.tsx index a9ca224d6..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 = 1 + material.opacity = 0.3 } }) }