diff --git a/app/page.tsx b/app/page.tsx index b9f88ae8..4ace6502 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -35,7 +35,6 @@ import AllAutomatonsOnActivePlanet from "@/components/Structures/Auto/AllAutomat import { EarthViewLayout } from "@/components/(scenes)/planetScene/layout"; import Onboarding from "./scenes/onboarding/page"; import VerticalToolbar from "@/components/Layout/Toolbar"; -import StructureMissionGuide from "@/components/Layout/Guide"; import SimpleeMissionGuide from "./tests/singleMissionGuide"; export default function Home() { diff --git a/app/scenes/mars/page.tsx b/app/scenes/mars/page.tsx index 64f99e0c..bfd8eca1 100644 --- a/app/scenes/mars/page.tsx +++ b/app/scenes/mars/page.tsx @@ -11,7 +11,6 @@ import { InventoryStructureItem } from "@/types/Items"; import { PlanetarySystem } from "@/components/(scenes)/planetScene/orbitals/system"; import AllAutomatonsOnActivePlanet from "@/components/Structures/Auto/AllAutomatons"; import { MiningComponentComponent } from "@/components/(scenes)/mining/mining-component"; -import StructureMissionGuide from "@/components/Layout/Guide"; const MarsView: React.FC = () => { const supabase = useSupabaseClient(); @@ -57,7 +56,6 @@ const MarsStructures: React.FC = () => { return ( - // //
diff --git a/app/scenes/mining/page.tsx b/app/scenes/mining/page.tsx index 07a7ea03..c3104a57 100644 --- a/app/scenes/mining/page.tsx +++ b/app/scenes/mining/page.tsx @@ -2,14 +2,12 @@ import React from "react"; import { EarthActionSceneLayout, EarthViewLayout } from "@/components/(scenes)/planetScene/layout"; -import StructureMissionGuide from "@/components/Layout/Guide"; import { MiningComponentComponent } from "@/components/(scenes)/mining/mining-component"; export default function Mining() { return ( - ); }; \ No newline at end of file diff --git a/app/starnet/feed/anomalies/page.tsx b/app/starnet/feed/anomalies/page.tsx deleted file mode 100644 index 7fa8b0be..00000000 --- a/app/starnet/feed/anomalies/page.tsx +++ /dev/null @@ -1,137 +0,0 @@ -'use client'; - -import React, { useEffect, useState } from "react"; -import { PostCardSingle } from "@/content/Posts/PostSingle"; -import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; -import StarnetLayout from "@/components/Layout/Starnet"; - -interface Classification { - id: number; - created_at: string; - content: string | null; - author: string | null; - anomaly: number | null; - media: any | null; - classificationtype: string | null; - classificationConfiguration: any | null; - category: string; - tags: string[]; -}; - -export default function Starnet() { - const supabase = useSupabaseClient(); - const session = useSession(); - - const [classifications, setClassifications] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - const fetchClassifications = async () => { - if (!session?.user) { - setError("User session not found."); - setLoading(false); - return; - } - - setLoading(true); - setError(null); - try { - const { data, error } = await supabase - .from('classifications') - .select('*') - .eq('author', session.user.id) - .order('created_at', { ascending: false }) as { data: Classification[]; error: any }; - - if (error) throw error; - - const processedData = data.map((classification) => { - const media = classification.media; - let images: string[] = []; - - // Ensure 'images' is always an array - if (Array.isArray(media)) { - if (media.length === 2 && typeof media[1] === "string") { - images.push(media[1]); - } - } else if (media && typeof media.uploadUrl === 'string') { - images.push(media.uploadUrl); - } - - const votes = classification.classificationConfiguration?.votes || 0; - - return { ...classification, images, votes }; - }); - - setClassifications(processedData); - } catch (error) { - console.error("Error fetching classifications:", error); - setError("Failed to load classifications."); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchClassifications(); - }, [session]); - - const handleVote = async (classificationId: number, currentConfig: any) => { - try { - const currentVotes = currentConfig?.votes || 0; - - const updatedConfig = { - ...currentConfig, - votes: currentVotes + 1, - }; - - const { error } = await supabase - .from("classifications") - .update({ classificationConfiguration: updatedConfig }) - .eq("id", classificationId); - - if (error) { - console.error("Error updating classificationConfiguration:", error); - } else { - setClassifications((prevClassifications) => - prevClassifications.map((classification) => - classification.id === classificationId - ? { ...classification, votes: updatedConfig.votes } - : classification - ) - ); - } - } catch (error) { - console.error("Error voting:", error); - } - }; - - return ( - -
- {loading ? ( -

Loading classifications...

- ) : error ? ( -

{error}

- ) : ( - classifications.map((classification) => ( - handleVote(classification.id, classification.classificationConfiguration)} - votes={0} - /> - )) - )} -
-
- ); -}; \ No newline at end of file diff --git a/app/starnet/feed/page.tsx b/app/starnet/feed/page.tsx deleted file mode 100644 index adf29960..00000000 --- a/app/starnet/feed/page.tsx +++ /dev/null @@ -1,132 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import { PostCardSingle } from "@/content/Posts/PostSingle"; -import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; -import StarnetLayout from "@/components/Layout/Starnet"; - -interface Classification { - id: number; - created_at: string; - content: string | null; - author: string | null; - anomaly: number | null; - media: any | null; - classificationtype: string | null; - classificationConfiguration: any | null; -}; - -export default function Starnet() { - const supabase = useSupabaseClient(); - const session = useSession(); - - const [classifications, setClassifications] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - const fetchClassifications = async () => { - if (!session?.user) { - setError("User session not found."); - setLoading(false); - return; - }; - - setLoading(true); - setError(null); - try { - const { data, error } = await supabase - .from('classifications') - .select('*') - .eq('author', session.user.id) - .order('created_at', { ascending: false }) as { data: Classification[]; error: any }; - - if (error) throw error; - - const processedData = data.map((classification) => { - const media = classification.media; - let images: string[] = []; - - if (Array.isArray(media) && media.length === 2 && typeof media[1] === "string") { - images.push(media[1]); - } else if (media && media.uploadUrl) { - images.push(media.uploadUrl); - } - - const votes = classification.classificationConfiguration?.votes || 0; - - return { ...classification, images, votes }; - }); - - setClassifications(processedData); - } catch (error) { - console.error("Error fetching classifications:", error); - setError("Failed to load classifications."); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchClassifications(); - }, [session]); - - const handleVote = async (classificationId: number, currentConfig: any) => { - try { - const currentVotes = currentConfig?.votes || 0; - - const updatedConfig = { - ...currentConfig, - votes: currentVotes + 1, - }; - - const { error } = await supabase - .from("classifications") - .update({ classificationConfiguration: updatedConfig }) - .eq("id", classificationId); - - if (error) { - console.error("Error updating classificationConfiguration:", error); - } else { - setClassifications((prevClassifications) => - prevClassifications.map((classification) => - classification.id === classificationId - ? { ...classification, votes: updatedConfig.votes } - : classification - ) - ); - } - } catch (error) { - console.error("Error voting:", error); - } - }; - - return ( - -
- {loading ? ( -

Loading classifications...

- ) : error ? ( -

{error}

- ) : ( - classifications.map((classification) => ( - handleVote(classification.id, classification.classificationConfiguration)} - /> - )) - )} -
-
- ); -}; \ No newline at end of file diff --git a/app/starnet/page.tsx b/app/starnet/page.tsx index 98e073e0..adf29960 100644 --- a/app/starnet/page.tsx +++ b/app/starnet/page.tsx @@ -1,20 +1,132 @@ "use client"; import React, { useEffect, useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; +import { PostCardSingle } from "@/content/Posts/PostSingle"; +import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; import StarnetLayout from "@/components/Layout/Starnet"; -import ProfileCardModal from "@/components/profile/form"; -import { Button } from "@/components/ui/button"; -import Link from "next/link"; -import { StructureMissionGuideMobile } from "@/components/Layout/Guide"; + +interface Classification { + id: number; + created_at: string; + content: string | null; + author: string | null; + anomaly: number | null; + media: any | null; + classificationtype: string | null; + classificationConfiguration: any | null; +}; export default function Starnet() { - return ( - - <> - - - - - ); + const supabase = useSupabaseClient(); + const session = useSession(); + + const [classifications, setClassifications] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchClassifications = async () => { + if (!session?.user) { + setError("User session not found."); + setLoading(false); + return; + }; + + setLoading(true); + setError(null); + try { + const { data, error } = await supabase + .from('classifications') + .select('*') + .eq('author', session.user.id) + .order('created_at', { ascending: false }) as { data: Classification[]; error: any }; + + if (error) throw error; + + const processedData = data.map((classification) => { + const media = classification.media; + let images: string[] = []; + + if (Array.isArray(media) && media.length === 2 && typeof media[1] === "string") { + images.push(media[1]); + } else if (media && media.uploadUrl) { + images.push(media.uploadUrl); + } + + const votes = classification.classificationConfiguration?.votes || 0; + + return { ...classification, images, votes }; + }); + + setClassifications(processedData); + } catch (error) { + console.error("Error fetching classifications:", error); + setError("Failed to load classifications."); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchClassifications(); + }, [session]); + + const handleVote = async (classificationId: number, currentConfig: any) => { + try { + const currentVotes = currentConfig?.votes || 0; + + const updatedConfig = { + ...currentConfig, + votes: currentVotes + 1, + }; + + const { error } = await supabase + .from("classifications") + .update({ classificationConfiguration: updatedConfig }) + .eq("id", classificationId); + + if (error) { + console.error("Error updating classificationConfiguration:", error); + } else { + setClassifications((prevClassifications) => + prevClassifications.map((classification) => + classification.id === classificationId + ? { ...classification, votes: updatedConfig.votes } + : classification + ) + ); + } + } catch (error) { + console.error("Error voting:", error); + } + }; + + return ( + +
+ {loading ? ( +

Loading classifications...

+ ) : error ? ( +

{error}

+ ) : ( + classifications.map((classification) => ( + handleVote(classification.id, classification.classificationConfiguration)} + /> + )) + )} +
+
+ ); }; \ No newline at end of file diff --git a/app/tests/page.tsx b/app/tests/page.tsx index 06f4328e..964c3dc5 100644 --- a/app/tests/page.tsx +++ b/app/tests/page.tsx @@ -1,25 +1,17 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import StarnetLayout from "@/components/Layout/Starnet"; -import { MiningComponentComponent } from "@/components/(scenes)/mining/mining-component"; -import Greenhouse from "@/page.test"; -import BigMap from "@/components/(scenes)/planetScene/bigMap"; -import DiscoveriesPage from "@/content/Classifications/minimalDiscoveries"; -import UserMissions from "./missionsUnlocked"; -import SimpleeMissionGuide from "./singleMissionGuide"; +import FreeformUploadData from "@/components/Projects/(classifications)/FreeForm"; // import { TopographicMap } from "@/components/topographic-map"; export default function TestPage() { return ( // <> - - {/* */} - {/* */} - {/* */} {/* */} + // {/* */} ); diff --git a/components/Data/Generator/Astronomers/DailyMinorPlanet/Asteroid.tsx b/components/Data/Generator/Astronomers/DailyMinorPlanet/Asteroid.tsx new file mode 100644 index 00000000..9c29e92e --- /dev/null +++ b/components/Data/Generator/Astronomers/DailyMinorPlanet/Asteroid.tsx @@ -0,0 +1,54 @@ +import { useRef, useMemo } from "react" +import { useFrame } from "@react-three/fiber" +import * as THREE from "three" +import { createNoise3D } from "simplex-noise" + +export function Asteroid({ metallic }: { metallic: number }) { + const meshRef = useRef(null) + const noise3D = useMemo(() => createNoise3D(), []) + + // Generate geometry with noise-based displacement + const geometry = useMemo(() => { + const geo = new THREE.IcosahedronGeometry(1, 4) + const positions = geo.attributes.position + const vector = new THREE.Vector3() + + for (let i = 0; i < positions.count; i++) { + vector.fromBufferAttribute(positions, i) + const noise = noise3D(vector.x * 2, vector.y * 2, vector.z * 2) + vector.normalize().multiplyScalar(1 + 0.3 * noise) + positions.setXYZ(i, vector.x, vector.y, vector.z) + } + + geo.computeVertexNormals() + return geo + }, [noise3D]) + + // Interpolate between rocky and metallic materials based on slider + const material = useMemo(() => { + return new THREE.MeshStandardMaterial({ + color: new THREE.Color().lerpColors( + new THREE.Color("#8B7355"), // Rocky brown + new THREE.Color("#A8A8A8"), // Metallic gray + metallic + ), + metalness: THREE.MathUtils.lerp(0.2, 0.8, metallic), + roughness: THREE.MathUtils.lerp(0.8, 0.3, metallic), + }) + }, [metallic]) + + // Slow rotation + useFrame((state, delta) => { + if (meshRef.current) { + meshRef.current.rotation.y += delta * 0.2 + } + }) + + return ( + <> + + + + + ); +}; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/DailyMinorPlanet/asteroid-viewer.tsx b/components/Data/Generator/Astronomers/DailyMinorPlanet/asteroid-viewer.tsx new file mode 100644 index 00000000..a221dcc9 --- /dev/null +++ b/components/Data/Generator/Astronomers/DailyMinorPlanet/asteroid-viewer.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { Canvas } from "@react-three/fiber"; +import { OrbitControls } from "@react-three/drei"; +import { Slider } from "@/components/ui/slider"; +import { useState } from "react"; +import { Asteroid } from "./Asteroid"; +import { Background } from "./background"; + +interface AsteroidGeneratorProps { + classificationConfig?: any; + classificationId: string; +}; + +export default function AsteroidViewer({ classificationConfig, classificationId }: AsteroidGeneratorProps) { + const [metallic, setMetallic] = useState(0.5) + + return ( +
+ + + + + +
+ + setMetallic(value)} + max={1} + step={0.01} + className="w-full" + /> +
+ Rocky + Metallic +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/DailyMinorPlanet/background.tsx b/components/Data/Generator/Astronomers/DailyMinorPlanet/background.tsx new file mode 100644 index 00000000..1009a076 --- /dev/null +++ b/components/Data/Generator/Astronomers/DailyMinorPlanet/background.tsx @@ -0,0 +1,63 @@ +import { useRef, useMemo } from "react" +import * as THREE from "three" +import { useFrame } from "@react-three/fiber" + +const vertexShader = ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +` + +const fragmentShader = ` + uniform float time; + varying vec2 vUv; + + float random(vec2 st) { + return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123); + } + + void main() { + vec2 uv = vUv; + + // Create a noisy background + float noise = random(uv + time * 0.1); + vec3 color = vec3(noise * 0.15); + + // Add stars + float star = step(0.998, random(uv)); + color += star; + + gl_FragColor = vec4(color, 1.0); + } +` + +export function Background() { + const shaderRef = useRef(null) + + const uniforms = useMemo( + () => ({ + time: { value: 0 }, + }), + [] + ) + + useFrame((state) => { + if (shaderRef.current) { + shaderRef.current.uniforms.time.value = state.clock.elapsedTime + } + }) + + return ( + + + + + ); +}; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/DailyMinorPlanet/effects.tsx b/components/Data/Generator/Astronomers/DailyMinorPlanet/effects.tsx new file mode 100644 index 00000000..98a43152 --- /dev/null +++ b/components/Data/Generator/Astronomers/DailyMinorPlanet/effects.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { Effect } from "postprocessing"; +import { forwardRef } from "react"; +import { extend, useThree } from "@react-three/fiber"; +import { EffectComposer, Noise } from "@react-three/postprocessing"; +import { BlendFunction } from "postprocessing"; +import { Uniform, WebGLRenderer } from "three"; + +// Custom grain effect to match telescope image +class GrainEffect extends Effect { + constructor({ blendFunction = BlendFunction.MULTIPLY } = {}) { + super( + "GrainEffect", + /* glsl */ ` + uniform float time; + + float random(vec2 p) { + return fract(sin(dot(p.xy, vec2(12.9898,78.233))) * 43758.5453123); + } + + void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { + vec2 grain = vec2(random(uv + time * 0.1)); + outputColor = vec4(inputColor.rgb * (0.85 + 0.15 * grain.x), inputColor.a); + } + `, + { + blendFunction, + uniforms: new Map([["time", new Uniform(0)]]), + } + ); + } + + update(renderer: WebGLRenderer, inputBuffer: any, deltaTime: number) { + this.uniforms.get("time")!.value += deltaTime; + } +} + +extend({ GrainEffect }); + +export const Effects = forwardRef(() => { + return ( + + + {/* */} + + ); +}); + +Effects.displayName = "Effects"; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/PlanetHunters/PlanetGenerator.tsx b/components/Data/Generator/Astronomers/PlanetHunters/PlanetGenerator.tsx new file mode 100644 index 00000000..f7a89441 --- /dev/null +++ b/components/Data/Generator/Astronomers/PlanetHunters/PlanetGenerator.tsx @@ -0,0 +1,136 @@ +import { useState, useEffect } from 'react'; +import { PlanetScene } from './planet-scene'; +import { PlanetControls } from './planet-controls'; +import { PlanetImportExport } from './planet-import-export'; +import { calculatePlanetStats } from '@/utils/planet-physics'; +import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'; +import type { PlanetStats } from '@/utils/planet-physics'; + +const TERRESTRIAL_THRESHOLD = 7.5; // Earth masses +const GASEOUS_THRESHOLD = 2.0; // Earth radii + +interface PlanetGeneratorProps { + classificationConfig?: any; + classificationId: string; + author: string; +}; + +export default function PlanetGenerator({ classificationConfig, author, classificationId }: PlanetGeneratorProps) { + const supabase = useSupabaseClient(); + const session = useSession(); + + const initialMass = classificationConfig?.exportedValue?.mass ?? 1; + const initialRadius = classificationConfig?.exportedValue?.radius ?? 1; + + const [mass, setMass] = useState(initialMass); + const [radius, setRadius] = useState(initialRadius); + const [typeOverride, setTypeOverride] = useState<'terrestrial' | 'gaseous' | null>(null); + + const stats = calculatePlanetStats(mass, radius, typeOverride); + + const handleMassChange = (newMass: number) => { + if (typeOverride === 'terrestrial' && newMass > TERRESTRIAL_THRESHOLD) { + setMass(TERRESTRIAL_THRESHOLD); + } else if (typeOverride === 'gaseous' && newMass <= TERRESTRIAL_THRESHOLD) { + setMass(TERRESTRIAL_THRESHOLD + 0.1); + } else { + setMass(newMass); + } + }; + + const handleRadiusChange = (newRadius: number) => { + if (typeOverride === 'terrestrial' && newRadius > GASEOUS_THRESHOLD) { + setRadius(GASEOUS_THRESHOLD); + } else if (typeOverride === 'gaseous' && newRadius <= GASEOUS_THRESHOLD) { + setRadius(GASEOUS_THRESHOLD + 0.1); + } else { + setRadius(newRadius); + } + }; + + const handleTypeOverride = (type: 'terrestrial' | 'gaseous' | null) => { + setTypeOverride(type); + if (type === 'terrestrial') { + if (mass > TERRESTRIAL_THRESHOLD) setMass(TERRESTRIAL_THRESHOLD); + if (radius > GASEOUS_THRESHOLD) setRadius(GASEOUS_THRESHOLD); + } else if (type === 'gaseous') { + if (mass <= TERRESTRIAL_THRESHOLD) setMass(TERRESTRIAL_THRESHOLD + 0.1); + if (radius <= GASEOUS_THRESHOLD) setRadius(GASEOUS_THRESHOLD + 0.1); + }; + }; + + const handleImport = (importedStats: Partial) => { + if (importedStats.mass !== undefined) { + setMass(importedStats.mass); + } + if (importedStats.radius !== undefined) { + setRadius(importedStats.radius); + } + setTypeOverride(null); + }; + + const handleSave = async () => { + if (!classificationId) { + console.error('Classification ID is missing.'); + return; + } + + const idToQuery = typeof classificationId === 'string' ? classificationId : String(classificationId); + + try { + const { data, error } = await supabase + .from('classifications') + .select('classificationConfiguration') + .eq('id', idToQuery) + .single(); + + if (error) throw error; + + const currentConfig = data?.classificationConfiguration || {}; + const newConfig = { + ...currentConfig, + exportedValue: { mass, radius }, + }; + + const { error: updateError } = await supabase + .from('classifications') + .update({ classificationConfiguration: newConfig }) + .eq('id', idToQuery); + + if (updateError) throw updateError; + + alert('Planet configuration saved successfully!'); + } catch (err) { + console.error('Error saving planet configuration:', err); + alert('Failed to save planet configuration.'); + } + }; + + return ( +
+
+

Procedural Planet Generator

+
+ +
+ + + {author === session?.user.id && ( + + )} +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/PlanetHunters/planet-controls.tsx b/components/Data/Generator/Astronomers/PlanetHunters/planet-controls.tsx new file mode 100644 index 00000000..f7ef27bb --- /dev/null +++ b/components/Data/Generator/Astronomers/PlanetHunters/planet-controls.tsx @@ -0,0 +1,81 @@ +import { Slider } from "@/components/ui/slider" +import { Label } from "@/components/ui/label" +import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" +import type { PlanetStats } from "@/utils/planet-physics" + +interface PlanetControlsProps { + stats: PlanetStats + onMassChange: (value: number) => void + onRadiusChange: (value: number) => void + onTypeOverride: (type: 'terrestrial' | 'gaseous' | null) => void +} + +function calculateBiomeTemperatures(stats: PlanetStats) { + const baseTemp = stats.type === 'terrestrial' ? 15 : -150 // Celsius + const massEffect = (stats.mass - 1) * 10 // Adjust temperature based on mass + + return { + ocean: Math.round(baseTemp + massEffect - 5), + beach: Math.round(baseTemp + massEffect), + ground: Math.round(baseTemp + massEffect + 5), + mountain: Math.round(baseTemp + massEffect - 15), + } +} + +export function PlanetControls({ stats, onMassChange, onRadiusChange, onTypeOverride }: PlanetControlsProps) { + const biomeTemperatures = calculateBiomeTemperatures(stats) + + return ( + + +
+ + onMassChange(value)} + /> +
{stats.mass.toFixed(1)} M⊕
+
+ +
+ + onRadiusChange(value)} + /> +
{stats.radius.toFixed(1)} R⊕
+
+ +
+
Density: {stats.density.toFixed(2)} g/cm³
+
Type: {stats.type}
+
+ +
+ +
Ocean: {biomeTemperatures.ocean}°C
+
Beach: {biomeTemperatures.beach}°C
+
Ground: {biomeTemperatures.ground}°C
+
Mountain: {biomeTemperatures.mountain}°C
+
+ +
+ + +
+
+
+ ) +} + diff --git a/components/Data/Generator/Astronomers/PlanetHunters/planet-import-export.tsx b/components/Data/Generator/Astronomers/PlanetHunters/planet-import-export.tsx new file mode 100644 index 00000000..7872a076 --- /dev/null +++ b/components/Data/Generator/Astronomers/PlanetHunters/planet-import-export.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Textarea } from '@/components/ui/textarea'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import type { PlanetStats } from '@/utils/planet-physics'; + +interface PlanetImportExportProps { + stats: PlanetStats; + onImport: (importedStats: Partial) => void; + onSave: (updatedConfig: any) => void; +} + +export function PlanetImportExport({ stats, onImport, onSave }: PlanetImportExportProps) { + const [importExportText, setImportExportText] = useState(''); + + const handleExport = () => { + const exportText = `radius: ${stats.radius.toFixed(2)}\nmass: ${stats.mass.toFixed(2)}`; + setImportExportText(exportText); + }; + + const handleImport = () => { + const lines = importExportText.split('\n'); + const importedStats: Partial = {}; + + lines.forEach(line => { + const [key, value] = line.split(':').map(part => part.trim()); + if (key === 'radius' || key === 'mass') { + importedStats[key] = parseFloat(value); + } + }); + + if (Object.keys(importedStats).length > 0) { + onImport(importedStats); + } + }; + + const handleSave = () => { + const updatedConfig = { + exportedValue: { + radius: stats.radius, + mass: stats.mass, + }, + }; + onSave(updatedConfig); + }; + + return ( + + + Import/Export Settings + + +