import React, { useState, useRef, useMemo } from 'react';
import { useFrame, useThree, useLoader, extend } from '@react-three/fiber';
import { MeshPortalMaterial, Text, OrbitControls, Environment } from '@react-three/drei';
import * as THREE from 'three';
import WeatherVisualization from './WeatherVisualization';
import * as geometry from "maath/geometry";
// Extend with rounded plane geometry from maath
extend(geometry);
const ForecastPortal = ({
position,
dayData,
index,
isActive,
isFullscreen,
onEnter,
onExit
}) => {
const portalRef = useRef();
const materialRef = useRef();
const [hovered, setHovered] = useState(false);
useFrame((state, delta) => {
if (materialRef.current) {
// Animate portal blend based on active state
const targetBlend = isFullscreen ? 1 : (isActive ? 0.5 : 0);
materialRef.current.blend = THREE.MathUtils.lerp(
materialRef.current.blend || 0,
targetBlend,
0.1
);
}
});
const formatDay = (dateString, index) => {
// Parse date as local time to avoid timezone shifts
const [year, month, day] = dateString.split('-');
const date = new Date(year, month - 1, day);
return date.toLocaleDateString('en-US', { weekday: 'short' }).toUpperCase();
};
// Create portal scene with forecast weather
const portalWeatherData = useMemo(() => ({
current: {
temp_f: dayData.day.maxtemp_f,
condition: dayData.day.condition,
is_day: 1, // Assume daytime for forecast
humidity: dayData.day.avghumidity,
wind_mph: dayData.day.maxwind_mph,
},
location: {
localtime: dayData.date + 'T12:00' // Noon time for forecast
}
}), [dayData]);
// Portal content with proper scene structure
const PortalScene = () => (
<>
>
);
return (
{/* Portal Frame */}
{/* Only show UI elements when not in fullscreen */}
{!isFullscreen && (
<>
{formatDay(dayData.date, index)}
{Math.round(dayData.day.maxtemp_f)}° / {Math.round(dayData.day.mintemp_f)}°
{dayData.day.condition.text}
>
)}
);
};
const ForecastPortals = ({ weatherData, isLoading, onPortalStateChange }) => {
const [activePortal, setActivePortal] = useState(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const { camera, gl, viewport } = useThree();
const cameraTargetRef = useRef({ x: 0, y: 1, z: 8 });
//Mobile-responsive scaling
const isMobile = viewport.width < 6;
const scale = isMobile ? 0.7 : 1;
const spacing = isMobile ? 2.2 : 3;
const mobileYPosition = isMobile ? 0.5 : -0.5; // Move portals up on mobile
// Smooth camera animation when transitioning to portal mode
useFrame((state, delta) => {
// Only animate camera during the initial transition to portal mode
if (activePortal !== null && !isFullscreen) {
const target = cameraTargetRef.current;
const distance = Math.abs(camera.position.x - target.x) +
Math.abs(camera.position.y - target.y) +
Math.abs(camera.position.z - target.z);
// Stop animating when close to target, let OrbitControls take over
if (distance > 0.1) {
camera.position.x = THREE.MathUtils.lerp(camera.position.x, target.x, 0.05);
camera.position.y = THREE.MathUtils.lerp(camera.position.y, target.y, 0.05);
camera.position.z = THREE.MathUtils.lerp(camera.position.z, target.z, 0.05);
}
}
});
if (isLoading || !weatherData?.forecast?.forecastday) {
return null;
}
const forecastDays = weatherData.forecast.forecastday.slice(0, 3);
const handleEnterPortal = (index) => {
if (isFullscreen) return; // Prevent entering when already fullscreen
setActivePortal(index);
setIsFullscreen(true);
// Notify parent component about portal state
if (onPortalStateChange) {
const dayData = forecastDays[index];
onPortalStateChange(true, dayData);
}
// Set camera for fullscreen portal view
cameraTargetRef.current = {
x: 0, // Center the camera
y: 0,
z: 5
};
};
const handleExitPortal = () => {
setIsFullscreen(false);
setActivePortal(null);
// Notify parent component about portal state
if (onPortalStateChange) {
onPortalStateChange(false, null);
}
// Set camera target back to default position
cameraTargetRef.current = { x: 0, y: 1, z: 8 };
};
return (
<>
{forecastDays.map((day, index) => (
handleEnterPortal(index)}
onExit={handleExitPortal}
/>
))}
>
);
};
export default ForecastPortals;