From 37770d71d74f88898425724bf919f839774d2172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=83=D0=BB?= Date: Wed, 27 Nov 2024 19:18:31 +0300 Subject: [PATCH] feat: add 2d view for mindset --- .../mindset/components/Marker/index.tsx | 33 ++- .../mindset/components/MediaPlayer/index.tsx | 36 ++- .../PlayerContols/ProgressBar/index.tsx | 69 +++--- .../components/PlayerContols/index.tsx | 30 ++- .../components/Scene/Board/Edges/index.tsx | 45 ++++ .../Scene/Board/Node/Content/index.tsx | 14 +- .../Scene/Board/Node/Image/index.tsx | 53 +++++ .../components/Scene/Board/Node/index.tsx | 57 ++--- .../Scene/Board/RoundedRectangle/index.tsx | 2 - .../mindset/components/Scene/Board/index.tsx | 212 ++++++++++++------ .../mindset/components/Scene/index.tsx | 78 +++---- src/components/mindset/data/index.tsx | 30 +-- src/components/mindset/index.tsx | 8 +- src/stores/usePlayerStore/index.ts | 6 + 14 files changed, 431 insertions(+), 242 deletions(-) create mode 100644 src/components/mindset/components/Scene/Board/Edges/index.tsx create mode 100644 src/components/mindset/components/Scene/Board/Node/Image/index.tsx diff --git a/src/components/mindset/components/Marker/index.tsx b/src/components/mindset/components/Marker/index.tsx index 0fd63daed..812359cbd 100644 --- a/src/components/mindset/components/Marker/index.tsx +++ b/src/components/mindset/components/Marker/index.tsx @@ -1,10 +1,14 @@ +import { memo } from 'react' import styled from 'styled-components' import { Flex } from '~/components/common/Flex' +import { Tooltip } from '~/components/common/ToolTip' import { useSchemaStore } from '~/stores/useSchemaStore' import { colors } from '~/utils/colors' type Props = { type: string + name: string + left: number } type BadgeProps = { @@ -13,7 +17,7 @@ type BadgeProps = { label: string } -export const Marker = ({ type }: Props) => { +export const Marker = memo(({ type, name, left }: Props) => { const [normalizedSchemasByType] = useSchemaStore((s) => [s.normalizedSchemasByType]) const primaryColor = normalizedSchemasByType[type]?.primary_color @@ -26,8 +30,16 @@ export const Marker = ({ type }: Props) => { color: primaryColor ?? colors.THING, } - return -} + return ( + + + + + + ) +}) + +Marker.displayName = 'Marker' const Badge = ({ iconStart, color, label }: BadgeProps) => ( @@ -52,3 +64,18 @@ const EpisodeWrapper = styled(Flex).attrs({ object-fit: contain; } ` + +const MarkerWrapper = styled.div` + position: absolute; + top: -4px; /* Adjust as needed to center above the progress bar */ + width: 8px; + height: 8px; + background-color: ${colors.white}; + border-radius: 50%; + transform: translateX(-50%); /* Center the marker horizontally */ + transform: translateX(-50%) translateY(-50%); + top: 50%; + display: flex; + align-items: center; + justify-content: center; +` diff --git a/src/components/mindset/components/MediaPlayer/index.tsx b/src/components/mindset/components/MediaPlayer/index.tsx index 7af72f389..d7e94e5e6 100644 --- a/src/components/mindset/components/MediaPlayer/index.tsx +++ b/src/components/mindset/components/MediaPlayer/index.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect, useMemo, useRef, useState } from 'react' +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import ReactPlayer from 'react-player' import styled from 'styled-components' @@ -45,7 +45,6 @@ type Props = { } const MediaPlayerComponent = ({ mediaUrl }: Props) => { - const playerRef = useRef(null) const wrapperRef = useRef(null) const [isFocused, setIsFocused] = useState(false) const [isFullScreen, setIsFullScreen] = useState(false) @@ -79,6 +78,8 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => { resetPlayer, isSeeking, setIsSeeking, + setPlayerRef, + playerRef, } = usePlayerStore((s) => s) useEffect(() => () => resetPlayer(), [resetPlayer]) @@ -93,21 +94,21 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => { }, [playingNode, setPlayingTime, setDuration, setIsReady, isReady]) useEffect(() => { - if (isSeeking && playerRef.current) { - playerRef.current.seekTo(playingTime, 'seconds') + if (isSeeking && playerRef) { + playerRef.seekTo(playingTime, 'seconds') setIsSeeking(false) } - }, [playingTime, isSeeking, setIsSeeking]) + }, [playingTime, isSeeking, setIsSeeking, playerRef]) useEffect(() => { - if (isReady && NodeStartTime && playerRef.current && !hasSeekedToStart) { + if (isReady && NodeStartTime && playerRef && !hasSeekedToStart) { const startTimeInSeconds = videoTimeToSeconds(NodeStartTime) - playerRef.current.seekTo(startTimeInSeconds, 'seconds') + playerRef.seekTo(startTimeInSeconds, 'seconds') setPlayingTime(startTimeInSeconds) setHasSeekedToStart(true) } - }, [isReady, NodeStartTime, setPlayingTime, hasSeekedToStart]) + }, [isReady, NodeStartTime, setPlayingTime, hasSeekedToStart, playerRef]) const togglePlay = () => { setIsPlaying(!isPlaying) @@ -142,6 +143,8 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => { setPlayingTime(currentTime) + return + const edge = findCurrentEdge(edges, currentTime) if (edge) { @@ -153,17 +156,17 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => { } const handleReady = () => { - if (playerRef.current) { + if (playerRef) { setStatus('ready') - const videoDuration = playerRef.current.getDuration() + const videoDuration = playerRef.getDuration() setDuration(videoDuration) if (NodeStartTime && !hasSeekedToStart) { const startTimeInSeconds = videoTimeToSeconds(NodeStartTime) - playerRef.current.seekTo(startTimeInSeconds, 'seconds') + playerRef.seekTo(startTimeInSeconds, 'seconds') setPlayingTime(startTimeInSeconds) setHasSeekedToStart(true) } @@ -222,6 +225,15 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => { togglePlay() } + const playerRefCallback = useCallback( + (player: ReactPlayer) => { + if (!playerRef && player) { + setPlayerRef(player) // Update the store with the player instance + } + }, + [setPlayerRef, playerRef], + ) + return mediaUrl ? ( setIsFocused(false)} onFocus={() => setIsFocused(true)} tabIndex={0}> @@ -229,7 +241,7 @@ const MediaPlayerComponent = ({ mediaUrl }: Props) => { setStatus('buffering')} diff --git a/src/components/mindset/components/PlayerContols/ProgressBar/index.tsx b/src/components/mindset/components/PlayerContols/ProgressBar/index.tsx index b0a3ffa74..3e1e8a045 100644 --- a/src/components/mindset/components/PlayerContols/ProgressBar/index.tsx +++ b/src/components/mindset/components/PlayerContols/ProgressBar/index.tsx @@ -1,64 +1,57 @@ -import { LinearProgress } from '@mui/material' +import { Slider } from '@mui/material' import styled from 'styled-components' import { Flex } from '~/components/common/Flex' -import { Tooltip } from '~/components/common/ToolTip' import { NodeExtended } from '~/types' import { colors } from '~/utils' import { Marker } from '../../Marker' type Props = { duration: number - progress: number markers: NodeExtended[] + playingTIme: number + handleProgressChange: (_: Event, value: number | number[]) => void } -export const ProgressBar = ({ duration, progress, markers }: Props) => ( +export const ProgressBar = ({ duration, markers, handleProgressChange, playingTIme }: Props) => ( - + {markers.map((node) => { const position = ((node?.start || 0) / duration) * 100 const type = node?.node_type || '' + const name = node?.properties?.name || '' - return ( - - - - - - ) + return })} ) -const Progress = styled(LinearProgress)` - && { - height: 2px; - background-color: ${colors.white}; - color: blue; - flex-grow: 1; - - .MuiLinearProgress-bar { - background: ${colors.GRAY6}; - } - } -` - const ProgressWrapper = styled(Flex)` position: relative; flex: 1 1 100%; ` -const MarkerWrapper = styled.div` - position: absolute; - top: -4px; /* Adjust as needed to center above the progress bar */ - width: 8px; - height: 8px; - background-color: ${colors.white}; - border-radius: 50%; - transform: translateX(-50%); /* Center the marker horizontally */ - transform: translateX(-50%) translateY(-50%); - top: 50%; - display: flex; - align-items: center; - justify-content: center; +const ProgressSlider = styled(Slider)` + && { + z-index: 20; + color: ${colors.white}; + height: 3px; + width: calc(100% - 12px); + box-sizing: border-box; + .MuiSlider-track { + border: none; + } + .MuiSlider-thumb { + width: 10px; + height: 10px; + background-color: ${colors.white}; + &:before { + box-shadow: '0 4px 8px rgba(0,0,0,0.4)'; + } + &:hover, + &.Mui-focusVisible, + &.Mui-active { + box-shadow: none; + } + } + } ` diff --git a/src/components/mindset/components/PlayerContols/index.tsx b/src/components/mindset/components/PlayerContols/index.tsx index 90d01dd2f..6f825dd08 100644 --- a/src/components/mindset/components/PlayerContols/index.tsx +++ b/src/components/mindset/components/PlayerContols/index.tsx @@ -1,4 +1,5 @@ import { IconButton } from '@mui/material' +import { useCallback } from 'react' import styled from 'styled-components' import ChevronLeftIcon from '~/components/Icons/ChevronLeftIcon' import ChevronRightIcon from '~/components/Icons/ChevronRightIcon.js' @@ -7,9 +8,7 @@ import PlayIcon from '~/components/Icons/PlayIcon' import { Flex } from '~/components/common/Flex' import { usePlayerStore } from '~/stores/usePlayerStore' import { NodeExtended } from '~/types' -import { videoTimeToSeconds } from '~/utils' import { colors } from '~/utils/colors' -import { ProgressBarCanvas } from './CanvasProgressbar' import { ProgressBar } from './ProgressBar' type Props = { @@ -17,15 +16,20 @@ type Props = { } export const PlayerControl = ({ markers }: Props) => { - const { isPlaying, setIsPlaying, playingTime, playingNode, duration } = usePlayerStore((s) => s) + const { isPlaying, setIsPlaying, playingTime, playingNode, duration, playerRef } = usePlayerStore((s) => s) - const [start, end] = playingNode?.properties?.timestamp - ? (playingNode.properties.timestamp as string).split('-').map((time) => videoTimeToSeconds(time)) - : [0, duration] + const showPlayer = playingNode - const startTime = ((playingTime - start) / (end - start)) * 100 + const handleProgressChange = useCallback( + (_: Event, value: number | number[]) => { + const newValue = Array.isArray(value) ? value[0] : value - const showPlayer = playingNode + if (playerRef) { + playerRef.seekTo(newValue, 'seconds') + } + }, + [playerRef], + ) return showPlayer ? ( @@ -43,8 +47,14 @@ export const PlayerControl = ({ markers }: Props) => { - - {false && } + {true && ( + + )} ) : null } diff --git a/src/components/mindset/components/Scene/Board/Edges/index.tsx b/src/components/mindset/components/Scene/Board/Edges/index.tsx new file mode 100644 index 000000000..ab5e3f434 --- /dev/null +++ b/src/components/mindset/components/Scene/Board/Edges/index.tsx @@ -0,0 +1,45 @@ +import { useMemo } from 'react' +import { Vector3 } from 'three' + +type Props = { + sourcePosition: { x: number; y: number; z: number } + targetPosition: { x: number; y: number; z: number } + color?: string + arrowSize?: number +} + +export const Edge = ({ sourcePosition, targetPosition, color = 'white', arrowSize = 1 }: Props) => { + const points = 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() + + // Calculate arrowhead points + const arrowLeft = new Vector3() + .copy(direction) + .multiplyScalar(-arrowSize) + .applyAxisAngle(new Vector3(0, 0, 1), Math.PI / 6) + + const arrowRight = new Vector3() + .copy(direction) + .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)] + }, [sourcePosition, targetPosition, arrowSize]) + + return ( + + + [p.x, p.y, p.z]))} + attach="attributes-position" + count={points.length} + itemSize={3} + /> + + + + ) +} diff --git a/src/components/mindset/components/Scene/Board/Node/Content/index.tsx b/src/components/mindset/components/Scene/Board/Node/Content/index.tsx index 319977691..a32e0d48b 100644 --- a/src/components/mindset/components/Scene/Board/Node/Content/index.tsx +++ b/src/components/mindset/components/Scene/Board/Node/Content/index.tsx @@ -1,17 +1,22 @@ import styled from 'styled-components' import { Flex } from '~/components/common/Flex' +import { TypeBadge } from '~/components/common/TypeBadge' import PlusIcon from '~/components/Icons/PlusIcon' import { colors } from '~/utils' type Props = { name: string + type: string url?: string } -export const Content = ({ name, url }: Props) => ( +export const Content = ({ name, url, type }: Props) => ( {url && }
{name}
+
+ +
@@ -30,6 +35,13 @@ const Wrapper = styled(Flex)` margin-top: 8px; } + .badge { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + } + .image { width: 32px; height: 32px; diff --git a/src/components/mindset/components/Scene/Board/Node/Image/index.tsx b/src/components/mindset/components/Scene/Board/Node/Image/index.tsx new file mode 100644 index 000000000..0a03b81d6 --- /dev/null +++ b/src/components/mindset/components/Scene/Board/Node/Image/index.tsx @@ -0,0 +1,53 @@ +import { Plane } from '@react-three/drei' +import { useTexture } from '~/components/Universe/Graph/Cubes/Text/hooks/useTexture' + +type Props = { + url: string + width: number + height: number +} + +export const Image = ({ url, width, height }: Props) => { + const { texture } = useTexture(url || '') + + const uniforms = { + u_texture: { value: texture }, + u_radius: { value: 0.5 }, // Radius of the circular mask + } + + return ( + <> + + + + + + + + + ) +} diff --git a/src/components/mindset/components/Scene/Board/Node/index.tsx b/src/components/mindset/components/Scene/Board/Node/index.tsx index e07cda450..0021754ba 100644 --- a/src/components/mindset/components/Scene/Board/Node/index.tsx +++ b/src/components/mindset/components/Scene/Board/Node/index.tsx @@ -1,9 +1,9 @@ import { Html } from '@react-three/drei' -import { useThree } from '@react-three/fiber' -import { OrthographicCamera } from 'three' +import { memo } from 'react' import { Flex } from '~/components/common/Flex' import { RoundedRectangle } from '../RoundedRectangle' import { Content } from './Content' +import { Image } from './Image' type Props = { width: number @@ -14,53 +14,38 @@ type Props = { name: string type: string color: string + zoom: number } -export const Node = ({ width, height, position, url, onButtonClick, name, type, color }: Props) => { - const { camera, size } = useThree() +export const Node = memo(({ width, height, position, url, onButtonClick, name, type, color, zoom }: Props) => ( + + {/* Background Rectangle */} + + {false && } - console.info(url, type) - - // Function to calculate the distance between the camera and the node - const getPixelSize = (worldSize: number, worldHeight: number) => { - const ortographicCamera = camera as OrthographicCamera - const visibleWidth = ortographicCamera.right - ortographicCamera.left - const visibleHeight = ortographicCamera.top - ortographicCamera.bottom - - return { - pixelWidth: (worldSize / visibleWidth) * size.width, - pixelHeight: (worldHeight / visibleHeight) * size.height, - } - } - - // Calculate pixel dimensions for the node - const { pixelWidth, pixelHeight } = getPixelSize(width, height) - - return ( - - {/* Background Rectangle */} - - - {/* Html */} + {/* Html */} + {true && ( onButtonClick()} style={{ - fontSize: '20px', + fontSize: '12px', color: 'white', fontWeight: 600, - width: `${pixelWidth}px`, - height: `${pixelHeight}px`, + width: `${width * zoom}px`, + height: `${height * zoom}px`, display: 'flex', justifyContent: 'center', alignItems: 'center', - borderRadius: '8px', // Optional for rounded corners - pointerEvents: 'auto', // Allow interaction + borderRadius: '8px', + pointerEvents: 'auto', // Allow interaction with the HTML element }} > - + - - ) -} + )} + +)) + +Node.displayName = 'Node' diff --git a/src/components/mindset/components/Scene/Board/RoundedRectangle/index.tsx b/src/components/mindset/components/Scene/Board/RoundedRectangle/index.tsx index f7fba1f57..e48a40f98 100644 --- a/src/components/mindset/components/Scene/Board/RoundedRectangle/index.tsx +++ b/src/components/mindset/components/Scene/Board/RoundedRectangle/index.tsx @@ -24,8 +24,6 @@ const createRoundedRect = (width: number, height: number, radius: number) => { } export const RoundedRectangle = ({ width, height, radius, color }: Props) => { - console.log(width, height) - const roundedRectShape = createRoundedRect(width, height, radius) return ( diff --git a/src/components/mindset/components/Scene/Board/index.tsx b/src/components/mindset/components/Scene/Board/index.tsx index 849c1577a..7dbe0e359 100644 --- a/src/components/mindset/components/Scene/Board/index.tsx +++ b/src/components/mindset/components/Scene/Board/index.tsx @@ -1,15 +1,18 @@ import { useThree } from '@react-three/fiber' -import { useEffect, useMemo } from 'react' -import { edges, edgesMention, maxTimestamp, minTimestamp, nodes, normalizeTimestamp } from '~/components/mindset/data' +import { Fragment, useEffect, useMemo, useState } from 'react' +import { useDataStore } from '~/stores/useDataStore' +import { Edge } from './Edges' import { Node } from './Node' -const totalDuration = 185 +const nodeWidth = 144 / 10 +const nodeHeight = 84 / 10 export const Board = () => { const state = useThree() + const { dataInitial } = useDataStore((s) => s) + const { camera, viewport } = state - const { width } = state.viewport - const { camera } = state + const [zoomState, setZoomState] = useState(camera.zoom) useEffect(() => { const orthoCamera = camera as THREE.OrthographicCamera @@ -19,11 +22,13 @@ export const Board = () => { if (event.ctrlKey) { // Zoom the camera when ctrlKey is pressed - orthoCamera.zoom += event.deltaY * -0.01 // Adjust zoom level - orthoCamera.zoom = Math.max(0.5, Math.min(orthoCamera.zoom, 5)) // Clamp zoom + orthoCamera.zoom += event.deltaY * -0.1 // Adjust zoom level + orthoCamera.zoom = Math.max(2, Math.min(orthoCamera.zoom, 20)) // Clamp zoom + + setZoomState(orthoCamera.zoom) } else { // Move the camera left/right when ctrlKey is NOT pressed - orthoCamera.position.x += event.deltaX * 0.01 // Horizontal movement + orthoCamera.position.x += event.deltaX * 0.1 // Horizontal movement } orthoCamera.updateProjectionMatrix() // Update projection matrix @@ -38,75 +43,148 @@ export const Board = () => { } }, [camera]) - const rangeMin = 0 - const rangeMax = 97.52 * 10 - - const positions = useMemo( - () => - edges - .filter((edge) => edge?.properties?.start && edge?.properties?.end) - .map((edge) => { - const st: number = (edge?.properties?.start || 0) as number - const ed: number = (edge?.properties?.end || 0) as number - - return { - source: edge.source, - target: edge.target, - xStart: normalizeTimestamp(st, minTimestamp, maxTimestamp, rangeMin, rangeMax), - xEnd: normalizeTimestamp(ed, minTimestamp, maxTimestamp, rangeMin, rangeMax), - } - }), - [rangeMin, rangeMax], - ) + const processedData = useMemo(() => { + if (!dataInitial) { + return { nodes: [], edges: [], relatedNodes: {} } + } + + const edgesMention = dataInitial.links + .filter((e) => e?.properties?.start) + .map((edge) => ({ + source: edge.source, + target: edge.target, + start: edge.properties?.start as number, + })) + + // Step 1: Calculate positions for primary nodes + const nodesWithPositions = dataInitial.nodes + .filter((node) => dataInitial.links.some((ed) => ed.source === node.ref_id || ed.target === node.ref_id)) + .map((node) => { + const edge = edgesMention.find((ed) => node.ref_id === ed.source || node.ref_id === ed.target) + const x = (edge?.start || 0) * (viewport.width / 10) + const y = 0 // Main node positions are aligned to y = 0 + const z = 0 + + return { ...node, x, y, z, start: edge?.start || 0 } + }) + .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 + + 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), + ) + + // 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 targetNode = + nodesWithPositions.find((node) => node.ref_id === edge.target) || + related.find((node) => node.ref_id === edge.target) + + 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, + } + }) + }) + + return { + nodes: nodesWithPositions, + edges: edgesWithPositions, + relatedNodes, + } + }, [dataInitial, viewport.width]) return ( <> - {nodes.map((node, i) => { - const hasTimeStamp = positions.some((p) => p.source === nodes[i].ref_id) - - const position = hasTimeStamp ? positions.find((p) => p.source === nodes[i].ref_id)?.xStart || 0 : i * 35 - - const y = hasTimeStamp ? 0 : 15 - - return true ? null : ( + {/* Render Nodes */} + {processedData.nodes.map((node) => ( + - ) - })} - {edgesMention.map((e) => { - const node = nodes.find((i) => i.ref_id === e.source) - - const x = (e.start / totalDuration) * width - - const y = -5 - return node ? ( - null} - position={[x, y, 0]} - type={node.node_type} - url="logo.png" - width={5} - /> - ) : null + {/* Render Related Nodes */} + {(processedData.relatedNodes[node.ref_id] || []).map((relatedNode) => ( + + ))} + + ))} + + {/* 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 */} + + + ) })} - - {/* Radius: Half of viewport width */} - - ) } diff --git a/src/components/mindset/components/Scene/index.tsx b/src/components/mindset/components/Scene/index.tsx index 6d5f5913d..d3ef6ccb9 100644 --- a/src/components/mindset/components/Scene/index.tsx +++ b/src/components/mindset/components/Scene/index.tsx @@ -1,64 +1,38 @@ -import { Canvas, useThree } from '@react-three/fiber' -import { useEffect, useState } from 'react' +import { OrthographicCamera } from '@react-three/drei' +import { Canvas, useFrame, useThree } from '@react-three/fiber' +import { memo } from 'react' +import { usePlayerStore } from '~/stores/usePlayerStore' import { Board } from './Board' -export const Scene = () => { - const [cameraX, setCameraX] = useState(0) // State for horizontal camera movement +const CameraController = () => { + const { camera, viewport } = useThree() // Access the Three.js camera + const playerRef = usePlayerStore((s) => s.playerRef) // Ref to store the current `playerRef` - const handleSliderChange = (event: React.ChangeEvent) => { - setCameraX(Number(event.target.value)) - } + useFrame(() => { + if (playerRef) { + const time = playerRef.getCurrentTime() + + camera.position.x = (time * viewport.width) / 10 + } + }) + + return null // No React-rendered output +} + +export const Scene = memo(() => { + console.log('rerender') return (
- {/* Slider for controlling camera X-axis */} -
- -
- - + {/* */} + {/* Camera controller to manage GSAP animations */} + + - - - - {/* Add Axis Helper */} -
) -} - -// Custom camera component -const DynamicOrthographicCamera = ({ x }: { x: number }) => { - const { size, viewport, camera } = useThree() - const frustumSize = 50 +}) - const aspect = size.width / size.height - - console.log(aspect, size.width) - - // Dynamically calculate camera bounds - const left = (-frustumSize * aspect) / 2 - const right = (frustumSize * aspect) / 2 - const top = frustumSize / 2 - const bottom = -frustumSize / 2 - - useEffect(() => { - // Update camera bounds dynamically - const orthoCamera = camera as THREE.OrthographicCamera - - orthoCamera.left = left - orthoCamera.right = right - orthoCamera.top = top - orthoCamera.bottom = bottom - orthoCamera.position.x = x - orthoCamera.updateProjectionMatrix() - }, [camera, left, right, top, bottom, x]) - - useEffect(() => { - console.log('Viewport Width in World Units:', viewport.width) - }, [viewport.width]) - - return null -} +Scene.displayName = 'Scene' diff --git a/src/components/mindset/data/index.tsx b/src/components/mindset/data/index.tsx index 6ae9d1491..2fae3709e 100644 --- a/src/components/mindset/data/index.tsx +++ b/src/components/mindset/data/index.tsx @@ -27,8 +27,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '2', // Bitcoin - start: 7.68, - end: 19.619, + properties: { start: 7.68, end: 19.619 }, }, // Bitcoin's hard money nature @@ -37,8 +36,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '4', // Hard Money - start: 28.019, - end: 38.04, + properties: { start: 28.019, end: 38.04 }, }, // Blockchain as a public ledger @@ -47,8 +45,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '3', // Blockchain - start: 50.82, - end: 56.52, + properties: { start: 50.82, end: 56.52 }, }, // Scarcity of Bitcoin @@ -57,8 +54,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '5', // Scarcity - start: 19.439, - end: 25.619, + properties: { start: 19.439, end: 25.619 }, }, // Government control contrasted with Bitcoin @@ -67,8 +63,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '6', // Government Control - start: 34.619, - end: 43.02, + properties: { start: 34.619, end: 43.02 }, }, // Energy consumption in Bitcoin mining @@ -77,8 +72,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '9', // Energy Consumption - start: 31.8, - end: 38.04, + properties: { start: 31.8, end: 38.04 }, }, // Immutability ensured by the blockchain @@ -87,8 +81,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '10', // Immutability - start: 56.52, - end: 60.48, + properties: { start: 56.52, end: 60.48 }, }, // Decentralization of Bitcoin's public network @@ -97,8 +90,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '12', // Decentralization - start: 90.72, - end: 97.52, + properties: { start: 90.72, end: 97.52 }, }, // Bitcoin investment risks @@ -107,8 +99,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '13', // Investment Risks - start: 126.44, - end: 133.48, + properties: { start: 126.44, end: 133.48 }, }, // Bitcoin adoption rates and potential @@ -117,8 +108,7 @@ export const edges: Link[] = [ edge_type: 'Mentioned', target: '1', // Clip source: '14', // Adoption - start: 122.44, - end: 130.48, + properties: { start: 122.44, end: 130.48 }, }, ] diff --git a/src/components/mindset/index.tsx b/src/components/mindset/index.tsx index e4fed860a..c9c97cab9 100644 --- a/src/components/mindset/index.tsx +++ b/src/components/mindset/index.tsx @@ -17,7 +17,7 @@ import { SideBar } from './components/Sidebar' export const MindSet = () => { const { addNewNode, isFetching, runningProjectId, dataInitial } = useDataStore((s) => s) - const [showTwoD, setShowTwoD] = useState(false) + const [showTwoD, setShowTwoD] = useState(true) const { selectedEpisodeId, setSelectedEpisode } = useMindsetStore((s) => s) const socket: Socket | undefined = useSocket() @@ -106,6 +106,12 @@ export const MindSet = () => { useEffect(() => { if (socket) { + socket.connect() + + socket.on('connect_error', (error: unknown) => { + console.error('Socket connection error:', error) + }) + socket.on('new_node_created', handleNewNodeCreated) socket.on('node_updated', handleNodeUpdated) } diff --git a/src/stores/usePlayerStore/index.ts b/src/stores/usePlayerStore/index.ts index c9c93ab0f..aced28cba 100644 --- a/src/stores/usePlayerStore/index.ts +++ b/src/stores/usePlayerStore/index.ts @@ -1,3 +1,4 @@ +import ReactPlayer from 'react-player' import { create } from 'zustand' import { devtools } from 'zustand/middleware' import { NodeExtended } from '~/types' @@ -11,6 +12,7 @@ type PlayerStore = { duration: number volume: number playingNode: NodeExtended | null + playerRef: ReactPlayer | null setPlayingTime: (time: number) => void resetPlayer: () => void setVolume: (volume: number) => void @@ -21,6 +23,7 @@ type PlayerStore = { setPlayingNode: (playingNode: NodeExtended | null) => void setPlayingNodeLink: (link: string) => void setIsSeeking: (isSeeking: boolean) => void + setPlayerRef: (playerRef: ReactPlayer) => void } const defaultData: Omit< @@ -35,6 +38,7 @@ const defaultData: Omit< | 'setMiniPlayerIsVisible' | 'setPlayingNodeLink' | 'setIsSeeking' + | 'setPlayerRef' > = { isPlaying: false, miniPlayerIsVisible: false, @@ -44,6 +48,7 @@ const defaultData: Omit< playingNode: null, duration: 0, volume: 0.5, + playerRef: null, } export const usePlayerStore = create()( @@ -51,6 +56,7 @@ export const usePlayerStore = create()( ...defaultData, setIsSeeking: (isSeeking) => set({ isSeeking }), setIsPlaying: (isPlaying) => set({ isPlaying }), + setPlayerRef: (playerRef) => set({ playerRef }), setMiniPlayerIsVisible: (miniPlayerIsVisible) => { if (!miniPlayerIsVisible) { set({ miniPlayerIsVisible, isPlaying: false })