From e31149c8c5e348e4a4d689f80176ab1015d68080 Mon Sep 17 00:00:00 2001 From: Gizmotronn Date: Tue, 31 Mar 2026 19:57:09 +1100 Subject: [PATCH 01/17] =?UTF-8?q?=F0=9F=A5=8C=F0=9F=9A=90=20=E2=86=9D=20[u?= =?UTF-8?q?zm1um=20++]:=20How=20far=20can=20we=20go=3F=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tasks/task-data-fetching-optimization.md | 17 + .knowns/tasks/task-folder-restructuring.md | 17 + .../tasks/task-mobile-ux-simplification.md | 17 + .knowns/tasks/task-performance-audit.md | 17 + .../tasks/task-planet-painter-restoration.md | 32 + .knowns/tasks/task-refactor-god-components.md | 17 + src/app/game/GameClient.tsx | 17 +- src/app/planets/[id]/page.tsx | 623 --------------- src/app/planets/edit/[id]/page.tsx | 16 +- src/app/planets/paint/[id]/page.tsx | 214 ----- src/app/research/page.tsx | 2 +- .../[mission]/TelescopeClassifyPageClient.tsx | 14 +- .../ChildClassificationMarker.tsx | 154 ---- .../PlanetHunters/DepositMarker.tsx | 138 ---- .../PlanetHunters/PlanetGenerator.tsx | 474 ----------- .../PlanetHunters/Settings/BasicSettings.tsx | 159 ---- .../PlanetHunters/Settings/ColourSettings.tsx | 119 --- .../Settings/TerrainSettings.tsx | 308 -------- .../PlanetHunters/SettingsPanel.tsx | 169 ---- .../PlanetHunters/SimplePlanetGenerator.tsx | 56 -- .../PlanetHunters/planet-simple.tsx | 186 ----- .../Astronomers/PlanetHunters/planet.tsx | 143 ---- .../PlanetHunters/planetViewer-simple.tsx | 59 -- .../PlanetHunters/planetViewer.tsx | 66 -- src/components/discovery/planets/physics.ts | 294 ------- .../projects/(classifications)/NextScene.tsx | 734 +++--------------- .../results/DiskDetectiveResults.tsx | 144 ++++ .../results/DiskDetectiveSlideshow.tsx | 90 +++ .../results/MilestoneDisplay.tsx | 56 ++ .../results/MineralDepositCallout.tsx | 54 ++ .../research/CompactResearchPanel.tsx | 677 ---------------- .../deploy/satellite/DeploySatellite.tsx | 6 +- .../deploy/satellite/satelliteSpiderScan.tsx | 11 +- src/components/social/posts/PostWithGen.tsx | 43 +- .../game/components/station/StationNav.tsx | 38 +- .../game/components/station/StructureCard.tsx | 26 +- .../components/CompactResearchPanel.tsx | 266 +++++++ .../components}/CompactUpgradeCard.tsx | 0 .../research/config/upgrades-config.ts | 129 +++ .../utils/generators/PH/color-picker.tsx | 69 -- .../utils/generators/PH/fragment-shader.ts | 362 --------- .../utils/generators/PH/vertex-shader.ts | 208 ----- 42 files changed, 1041 insertions(+), 5200 deletions(-) create mode 100644 .knowns/tasks/task-data-fetching-optimization.md create mode 100644 .knowns/tasks/task-folder-restructuring.md create mode 100644 .knowns/tasks/task-mobile-ux-simplification.md create mode 100644 .knowns/tasks/task-performance-audit.md create mode 100644 .knowns/tasks/task-planet-painter-restoration.md create mode 100644 .knowns/tasks/task-refactor-god-components.md delete mode 100644 src/app/planets/[id]/page.tsx delete mode 100644 src/app/planets/paint/[id]/page.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/ChildClassificationMarker.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/DepositMarker.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/Settings/BasicSettings.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/Settings/ColourSettings.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/Settings/TerrainSettings.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/SettingsPanel.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/SimplePlanetGenerator.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/planet-simple.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/planet.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/planetViewer-simple.tsx delete mode 100644 src/components/discovery/data-sources/Astronomers/PlanetHunters/planetViewer.tsx delete mode 100644 src/components/discovery/planets/physics.ts create mode 100644 src/components/projects/(classifications)/results/DiskDetectiveResults.tsx create mode 100644 src/components/projects/(classifications)/results/DiskDetectiveSlideshow.tsx create mode 100644 src/components/projects/(classifications)/results/MilestoneDisplay.tsx create mode 100644 src/components/projects/(classifications)/results/MineralDepositCallout.tsx delete mode 100644 src/components/research/CompactResearchPanel.tsx create mode 100644 src/features/research/components/CompactResearchPanel.tsx rename src/{components/research => features/research/components}/CompactUpgradeCard.tsx (100%) create mode 100644 src/features/research/config/upgrades-config.ts delete mode 100644 src/shared/utils/generators/PH/color-picker.tsx delete mode 100644 src/shared/utils/generators/PH/fragment-shader.ts delete mode 100644 src/shared/utils/generators/PH/vertex-shader.ts diff --git a/.knowns/tasks/task-data-fetching-optimization.md b/.knowns/tasks/task-data-fetching-optimization.md new file mode 100644 index 000000000..efcc0572c --- /dev/null +++ b/.knowns/tasks/task-data-fetching-optimization.md @@ -0,0 +1,17 @@ +# Task: Data Fetching & Caching Optimization + +## Status +**Status:** Backlog +**Priority:** High +**Created:** 2026-04-02 +**Context:** Many components use manual `useEffect` + `fetch` patterns, leading to redundant code and sub-optimal performance on mobile. + +## Objectives +- [ ] Integrate a modern data-fetching library (e.g., SWR or TanStack Query). +- [ ] Consolidate fragmented API calls into "Aggregated Summary" endpoints. +- [ ] Implement robust error handling and loading states globally. + +## Strategy +- Identify a suitable library (SWR is already in use in some parts of the ecosystem, but let's check `package.json`). +- Refactor `GameClient.tsx` and major tabs to use hooks for data fetching. +- Work with backend/API routes to create `/api/gameplay/summary` endpoints. diff --git a/.knowns/tasks/task-folder-restructuring.md b/.knowns/tasks/task-folder-restructuring.md new file mode 100644 index 000000000..68c06c7dc --- /dev/null +++ b/.knowns/tasks/task-folder-restructuring.md @@ -0,0 +1,17 @@ +# Task: Feature-First Folder Structure Migration + +## Status +**Status:** In Progress +**Priority:** Medium +**Created:** 2026-04-02 +**Context:** The project structure is inconsistent, with logic split across `src/components`, `src/app`, and `src/lib`. + +## Objectives +- [ ] Consolidate feature-specific logic into `src/features/`. +- [ ] Ensure components, hooks, and types for a feature live together. +- [ ] Reduce top-level directory clutter. + +## Strategy +- Follow the established pattern in `src/features/game/`. +- Create `src/features/telescope/`, `src/features/satellite/`, etc. +- Update imports across the codebase using search-and-replace. diff --git a/.knowns/tasks/task-mobile-ux-simplification.md b/.knowns/tasks/task-mobile-ux-simplification.md new file mode 100644 index 000000000..4a66e8e08 --- /dev/null +++ b/.knowns/tasks/task-mobile-ux-simplification.md @@ -0,0 +1,17 @@ +# Task: Streamline "Build" Phase for Mobile UX + +## Status +**Status:** Backlog +**Priority:** Medium +**Created:** 2026-04-02 +**Context:** The current deployment flow is multi-step and requires significant navigation, which is friction-heavy for mobile users. + +## Objectives +- [ ] Implement "One-Tap Deployment" directly from the Hub's `StructureCard`. +- [ ] Create a "Unified Mission Feed" to bypass viewport-specific navigation for active tasks. +- [ ] Simplify touch targets and navigation paths for the core "Build -> Do" loop. + +## Strategy +- Add a "Quick Action" button to `StructureCard` when in an "undeployed" state. +- Develop a `GlobalMissionFeed` component that aggregates pending anomalies from all structures. +- Reduce the number of confirmation dialogs for mobile users. diff --git a/.knowns/tasks/task-performance-audit.md b/.knowns/tasks/task-performance-audit.md new file mode 100644 index 000000000..54cf530d7 --- /dev/null +++ b/.knowns/tasks/task-performance-audit.md @@ -0,0 +1,17 @@ +# Task: Asset & Animation Performance Audit + +## Status +**Status:** Backlog +**Priority:** Low +**Created:** 2026-04-02 +**Context:** Visual flair is important, but heavy DOM-based animations can lag on older mobile devices. + +## Objectives +- [ ] Optimize `LivingWorldBg.tsx` for high entity counts. +- [ ] Convert heavy SVG/DOM animations to `` where appropriate. +- [ ] Audit image sizes and formats (WebP migration). + +## Strategy +- Profile the `LivingWorldBg` with 100+ entities to identify bottlenecks. +- Benchmark a Canvas-based starfield vs the current CSS-based one. +- Implement lazy loading or lower-quality assets for low-power devices. diff --git a/.knowns/tasks/task-planet-painter-restoration.md b/.knowns/tasks/task-planet-painter-restoration.md new file mode 100644 index 000000000..cdb6b2084 --- /dev/null +++ b/.knowns/tasks/task-planet-painter-restoration.md @@ -0,0 +1,32 @@ +# Task: Restore Planet Painter Functionality + +## Status +**Status:** Deferred +**Priority:** Low +**Created:** 2026-04-02 +**Context:** Removed during a simplification sweep to improve performance and focus on core gameplay. + +## Background +The "Planet Painter" was a complex 3D component used at the end of the Planet Hunters mission for specific anomalies. It allowed users to visualize and configure planets based on classification data. + +## What Was Removed +- `src/app/planets/paint/[id]/page.tsx`: The main page for the planet painter. +- `src/app/planets/[id]/page.tsx`: Planet detail page that used the generator. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator.tsx`: The core generator component. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/planetViewer.tsx`: The 3D viewer using `@react-three/fiber`. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/planet.tsx`: The 3D planet mesh and shaders. +- `src/components/discovery/data-sources/Astronomers/PlanetHunters/SettingsPanel.tsx`: UI for adjusting planet parameters. +- `src/components/discovery/planets/physics.ts`: Physics and mineral logic for planets. +- Shaders in `src/shared/utils/generators/PH/`. + +## Why It Was Removed +- **High Complexity:** Involved thousands of lines of code and heavy 3D dependencies. +- **Low Usage:** It was an "end-game" mechanic that few users reached. +- **Performance:** Caused potential slowdowns on mobile devices due to WebGL and complex shaders. +- **User Experience:** The component was not well-designed and felt disconnected from the core loop. + +## Restoration Requirements +1. **Redesign:** The UI needs to be more intuitive and better integrated into the main game flow. +2. **Performance:** Optimize the 3D rendering for mobile devices. +3. **Connectivity:** Ensure it clearly links back to the citizen science data that "paints" the planet. +4. **Simplification:** Reduce the number of controls and focus on the visual reward of "discovering" a planet. diff --git a/.knowns/tasks/task-refactor-god-components.md b/.knowns/tasks/task-refactor-god-components.md new file mode 100644 index 000000000..d03d59adc --- /dev/null +++ b/.knowns/tasks/task-refactor-god-components.md @@ -0,0 +1,17 @@ +# Task: Refactor "God Components" (NextScene, ResearchPanel) + +## Status +**Status:** In Progress +**Priority:** High +**Created:** 2026-04-02 +**Context:** Components like `NextScene.tsx` and `CompactResearchPanel.tsx` have grown to 600-700+ lines, making them hard to maintain and prone to regressions. + +## Objectives +- [ ] Decompose `NextScene.tsx` into smaller, mission-specific "ResultCards". +- [ ] Refactor `CompactResearchPanel.tsx` to use a data-driven approach for upgrades. +- [ ] Reduce line counts and improve modularity. + +## Strategy +- Extract mission-specific logic into dedicated components in `src/components/projects/(classifications)/results/`. +- Create a centralized `upgrades-config.ts` for the research panel. +- Use a registry pattern to load result components based on classification type. diff --git a/src/app/game/GameClient.tsx b/src/app/game/GameClient.tsx index ff06bbb1e..f42ac54a1 100644 --- a/src/app/game/GameClient.tsx +++ b/src/app/game/GameClient.tsx @@ -203,6 +203,11 @@ export default function GameClient({ initialData, user }: GameClientProps) { router.replace(`/game?${params.toString()}`, { scroll: false }); }, [router, posthog, activeView]); + const handleQuickDeploy = useCallback((id: StructureId) => { + posthog?.capture("quick_deploy_used", { structure: id }); + markTutorialComplete(`${id}-deploy` as TutorialId); + }, [markTutorialComplete, posthog]); + const handleStructureClick = useCallback((id: StructureId) => { const tutorialKey = `${id}-deploy` as TutorialId; if (!hasTutorialCompleted(tutorialKey)) { @@ -432,6 +437,9 @@ export default function GameClient({ initialData, user }: GameClientProps) { agencyId={data.profile?.username || data.profile?.id} /> + {/* Header Spacer to prevent overlap */} +
+ {/* ─── Persistent HUD Strip ─── */} s + r.signals, 0)} @@ -439,7 +447,7 @@ export default function GameClient({ initialData, user }: GameClientProps) { classifications={data.classifications?.length ?? 0} /> -
+
{activeView === "base" ? ( handleStructureClick(id)} + onQuickDeploy={() => handleQuickDeploy(id)} />
); @@ -659,6 +668,12 @@ export default function GameClient({ initialData, user }: GameClientProps) { + + ); } diff --git a/src/app/planets/[id]/page.tsx b/src/app/planets/[id]/page.tsx deleted file mode 100644 index e323001b9..000000000 --- a/src/app/planets/[id]/page.tsx +++ /dev/null @@ -1,623 +0,0 @@ -'use client'; - -import React, { useEffect, useCallback, useState, useMemo } from "react"; -import { useSession } from "@/src/lib/auth/session-context"; -import { PostCardSingleWithGenerator } from "@/src/components/social/posts/PostWithGen"; -import Image from "next/image"; -import { Button } from "@/src/components/ui/button"; -import { ArrowLeft, Droplets, FileText, Info, Thermometer } from "lucide-react"; -import PlanetGenerator from "@/src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator"; -// import { PlanetGenerator } from "starsailors-planet-generator"; -// import WeatherEventStatus from "@/src/components/discovery/data-sources/Weather/EventsCounter"; -// import BiomassStats from "@/src/components/deployment/missions/structures/Biologists/BiomassOnPlanet"; -import BiomeAggregator from "@/src/components/discovery/data-sources/BiomeAggregator"; -import PlanetProgress from "@/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PlanetCompletion"; -// import WeatherGenerator from "@/src/components/discovery/data-sources/Weather/SimpleWeatherEvents"; -import MainHeader from "@/src/components/layout/Header/MainHeader"; -import UseDarkMode from "@/src/shared/hooks/useDarkMode"; - -export interface Classification { - id: number; - content: string | null; - author: string | null; - anomaly: Anomaly | null; - media: (string | { uploadUrl?: string })[] | null; - classificationtype: string | null; - classificationConfiguration?: any; - created_at: string; - title?: string; - votes?: number; - category?: string; - tags?: string[]; - images?: string[]; - relatedClassifications?: Classification[]; -}; - -type PlanetData = { - name: string; - galaxy: string; - diameter: string; - dayLength: string; - temperature: string; - climate: string; -}; - -type FocusView = "planet" | "overview" | "Climate" | "atmosphere" | "exploration" | "map" | "edit" - -export type Anomaly = { - id: number; - content: string | null; - anomalytype?: string | null; - mass?: number | null; - radius?: number | null; - density?: number | null; - gravity?: number | null; - temperature?: number | null; - orbital_period?: number | null; -avatar_url?: string | null; - created_at?: string; -}; - -export interface AggregatedCloud { - annotationOptions: Record; - classificationOptions: Record>; - additionalFields: Record>; - cloudColours?: Record; -}; - -export interface AggregatedP4 { - fanCount: number; - blotchCount: number; - classificationCounts: Record; -}; - -export interface AggregatedAI4M { - sandCount: number; - soilCount: number; - bedrockCount: number; - rockCount: number; - unlabelledCount: number; - classificationCounts: Record; -}; - -export interface AggregatedBalloon { - shapeCount: number; - vortexCount: number; - streakCount: number; - unlabelledCount: number; - classificationCounts: Record; -}; - -export interface AI4MClassification { - id: number; - classificationConfiguration: any; - annotationOptions: any[]; -}; - -import { useParams } from 'next/navigation'; -import Link from "next/link"; - -export default function TestPlanetWrapper() { - const params = useParams(); - const id = params?.id as string; - - const session = useSession(); - const { isDark, toggleDarkMode } = UseDarkMode(); - - const [classification, setClassification] = useState(null); - const [anomaly, setAnomaly] = useState(null); - const [currentView, setCurrentView] = useState("planet"); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [dominantBiome, setDominantBiome] = useState('Barren (Pending)'); - const [cloudSummary, setCloudSummary] = useState(null); - const [p4Summary, setP4Summary] = useState(null); - const [ai4MSummary, setAI4MSummary] = useState(null); - const [relatedClassifications, setRelatedClassifications] = useState([]); - const [period, setPeriod] = useState(""); - const [showCurrentUser, setShowCurrentUser] = useState(true); - const [showMetadata, setShowMetadata] = useState(false); - const [currentPlanet, setCurrentPlanet] = useState("orionis") - const [density, setDensity] = useState(null); - const [temperature, setTemperature] = useState(null); - const [biomassScore, setBiomassScore] = useState(0); - const [surveyorPeriod, setSurveyorPeriod] = useState("") - const [comments, setComments] = useState([]); - const [timeLeft, setTimeLeft] = useState(0); - - const getTimeUntilNextSunday = () => { - const now = new Date(); - const nextSunday = new Date(now); - nextSunday.setDate(now.getDate() + (7 - now.getDay()) % 7); - nextSunday.setHours(10, 1, 0, 0); - return nextSunday.getTime() - now.getTime(); - }; - - const calculateBiomass = (temperature?: number, radius?: number, surveyorPeriod?: number): number => { - const T = temperature ?? 300; - const R = radius ?? 1; - const P = surveyorPeriod ?? 1.5; // default to 1.5 if undefined or invalid - - return ( - 0.1 * - (T / (T + 300)) * - (1 / (1 + Math.exp(-(R - 1.2)))) * - (1 / (1 + Math.exp(-(1.5 - P)))) - ); -}; - - const formatTime = (timeInSeconds: number) => { - const hours = String(Math.floor(timeInSeconds / 3600)).padStart(2, "0"); - const minutes = String(Math.floor((timeInSeconds % 3600) / 60)).padStart(2, "0"); - const seconds = String(timeInSeconds % 60).padStart(2, "0"); - return `${hours}:${minutes}:${seconds}`; - }; - - useEffect(() => { - // Ensure all parameters are parsed correctly - const orbital = parseFloat(surveyorPeriod); - const rad = density !== null && !isNaN(density) ? density : 1; // Default to 1 if density is invalid - const temp = temperature !== null && !isNaN(temperature) ? temperature : 300; // Default to 300 if temperature is invalid - - // Ensure orbital is a valid number, fallback to a default value if invalid - const validOrbital = !isNaN(orbital) ? orbital : 1.5; - - // Calculate biomass score with valid parameters - setBiomassScore(calculateBiomass(temp, rad, validOrbital)); -}, [density, temperature, surveyorPeriod]); // Re-run when these values change - - const MemoizedPlanetGenerator = useMemo(() => { - return classification?.id && classification.author ? ( - // - - ) : null; - }, [classification?.id, classification?.author]); - - useEffect(() => { - if (!params?.id) return; - - const fetchClassifications = async () => { - const response = await fetch(`/api/gameplay/planets/${params.id}`, { cache: "no-store" }); - const payload = await response.json().catch(() => null); - if (!response.ok || !payload?.classification) { - console.error("Error fetching classification", payload?.error || response.statusText); - setError("Failed to fetch classification data"); - return; - } - - const data = payload.classification as Classification; - setClassification(data); - setAnomaly(data.anomaly); - - const periodFromField = data.classificationConfiguration?.additionalFields?.field_0; - if (periodFromField) setPeriod(periodFromField); - - setRelatedClassifications((payload.relatedClassifications ?? []) as Classification[]); - - setLoading(false); - }; - - fetchClassifications(); - }, [session, params?.id]); - - const fetchComments = async () => { - if (!classification) return; - - const response = await fetch( - `/api/gameplay/social/comments?classificationId=${classification.id}&order=desc`, - { cache: "no-store" } - ); - const payload = await response.json().catch(() => null); - const data = payload?.comments; - if (!response.ok || !Array.isArray(data)) { - console.error("Error fetching comments: ", payload?.error || response.statusText); - } else { - let aggregatedComments: any[] = []; - - // Create a map to store the aggregated comments by category and author - const aggregatedMap: Record = {}; - let biomassSum = 0; // Variable to track biomass sum - - data?.forEach((comment) => { - // Create a unique key based on category and author - const key = `${comment.category}-${comment.author}`; - - if (aggregatedMap[key]) { - // If a comment of this type exists, aggregate the values - if (comment.value) { - aggregatedMap[key].value += parseFloat(comment.value) || 0; - } - } else { - // If this is the first time we encounter this comment type and author, just add it - aggregatedMap[key] = { ...comment, value: parseFloat(comment.value) || 0 }; - } - - // Check for "Biomass" comments and aggregate their value - if (comment.category === "Biomass") { - biomassSum += parseFloat(comment.value) || 0; - } - }); - - // Convert the map back to an array - aggregatedComments = Object.values(aggregatedMap); - - // Now, set the aggregated comments to state - setComments(aggregatedComments); - - // Update the biomass score by adding the biomass sum - setBiomassScore(prevScore => prevScore + biomassSum); - - // Extract specific comments after aggregation for individual fields like OrbitalPeriod, PlanetType, etc. - const orbitalPeriodComment = aggregatedComments.find( - (comment) => comment.category === "OrbitalPeriod" && comment.author === classification.author - ); - - const radiusComment = aggregatedComments.find( - (comment) => comment.category === "PlanetType" && comment.author === classification.author - ); - - const temperatureComment = aggregatedComments.find( - (comment) => comment.category === "Temperature" && comment.author === classification.author - ); - - if (orbitalPeriodComment) setSurveyorPeriod(orbitalPeriodComment.value || ""); - if (radiusComment && radiusComment.value) { - const radiusValue = parseFloat(radiusComment.value); - setDensity(!isNaN(radiusValue) ? radiusValue : null); - } - if (temperatureComment && temperatureComment.value) { - const tempValue = parseFloat(temperatureComment.value); - setTemperature(!isNaN(tempValue) ? tempValue : null); - } - }; - }; - - const fetchDensity = async () => { - if (!classification) return; - - const response = await fetch( - `/api/gameplay/social/comments?classificationId=${classification.id}&category=Density&order=desc`, - { cache: "no-store" } - ); - const payload = await response.json().catch(() => null); - const data = payload?.comments; - if (!response.ok || !Array.isArray(data)) { - console.error("Error fetching comments: ", payload?.error || response.statusText); - } else if (data && data.length > 0 && data[0].value) { - const densityValue = parseFloat(data[0].value); - if (!isNaN(densityValue)) { - setDensity(densityValue); - } - }; - }; - - useEffect(() => { - fetchComments(); - fetchDensity(); - }, [classification]); - - useEffect(() => { - const timer = setInterval(() => { - setTimeLeft(prev => { - const time = Math.max(getTimeUntilNextSunday() / 1000, 300); - return Math.floor(time); - }); - }, 1000); - - return () => clearInterval(timer); - }, []); - - const handleViewChange = (view: FocusView) => { - setCurrentView(view) - }; - - const handleBackToMain = () => { - setCurrentView('planet'); - }; - - // Planet generator content - const PlanetComponent = useMemo(() => ( -
- {classification && !loading && MemoizedPlanetGenerator} -
- ), [classification, loading, MemoizedPlanetGenerator]); - - const ClimateComponent = () => ( -
- {/*
-

Classification Configuration

-
-
-                {JSON.stringify(classification?.classificationConfiguration, null, 2)}
-              
-
-
- - {relatedClassifications.length > 0 && ( -
-

Related Classifications

-
- {relatedClassifications.map((rc, index) => ( -
-
-                      {JSON.stringify(rc, null, 2)}
-                    
-
- ))} -
-
- )} */} -
- {(cloudSummary || p4Summary || ai4MSummary) && ( - setDominantBiome(biome)} biomassVersion={biomassScore} /> - )} - -
-
- ); - - const AtmosphereComponent = () => ( -
- {/* {classification?.id !== undefined && ( - - )} */} -
- ); - - const ExplorationComponent = () => ( -
- {classification && ( - - )} -
- ); - - const MapComponent = () => ( -
- -
- ); - - const EditComponent = () => ( -
- -
- ); - - const renderFocusComponent = () => { - switch (currentView) { - case "planet": - return PlanetComponent - case "overview": - return PlanetComponent - case "Climate": - return - case "atmosphere": - return - case "exploration": - return - case "map": - return - case "edit": - return - default: - return PlanetComponent - }; - }; - - return ( -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
-
- background -
-
- - {/* {dominantBiome && ( -
- -
- )} */} - -
- {currentView !== "planet" && ( -
- -
- )} - -
-
-
Biomass:
- {biomassScore} -
-
-
Density:
- {density} - g/cm^3 -
-
-
Main biome:
- {dominantBiome} -
- {/*
-
Period:
- {surveyorPeriod} Days {/* // or {period} -
*/} -
- -
- - - {/* */} - - -
- -
- {renderFocusComponent()} -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - - -
-
-
-
- ); -}; diff --git a/src/app/planets/edit/[id]/page.tsx b/src/app/planets/edit/[id]/page.tsx index 7cafad4b9..7fcebc345 100644 --- a/src/app/planets/edit/[id]/page.tsx +++ b/src/app/planets/edit/[id]/page.tsx @@ -5,7 +5,21 @@ import { useSession } from "@/src/lib/auth/session-context"; import Navbar from "@/src/components/layout/Navbar"; import { PostCardSingleWithGeneratorEditMode } from "@/src/components/social/posts/PostWithGen"; -import { Classification } from "../../[id]/page"; +interface Classification { + id: number; + content: string | null; + author: string | null; + anomaly?: { id: number; content: string | null } | null; + media?: (string | { uploadUrl?: string })[] | null; + classificationtype: string | null; + classificationConfiguration?: any; + created_at: string; + title?: string; + votes?: number; + category?: string; + tags?: string[]; + images?: string[]; +}; export default function EditPlanetAnomaly(props: { params: Promise<{ id: string }> }) { const params = use(props.params); diff --git a/src/app/planets/paint/[id]/page.tsx b/src/app/planets/paint/[id]/page.tsx deleted file mode 100644 index ae938e727..000000000 --- a/src/app/planets/paint/[id]/page.tsx +++ /dev/null @@ -1,214 +0,0 @@ -"use client" - -import { useState, useEffect } from "react" -import { useParams, useRouter } from "next/navigation" -import PlanetViewer from "@/src/components/discovery/data-sources/Astronomers/PlanetHunters/planetViewer" -import { type PlanetConfig, defaultPlanetConfig } from "@/src/components/discovery/planets/planet-config" -import SettingsPanel from "@/src/components/discovery/data-sources/Astronomers/PlanetHunters/SettingsPanel" -import { Button } from "@/src/components/ui/button" -import MainHeader from "@/src/components/layout/Header/MainHeader" -import UseDarkMode from "@/src/shared/hooks/useDarkMode" -import { TelescopeBackground } from "@/src/components/classification/telescope/telescope-background" - -export default function PlanetGeneratorPage() { - const router = useRouter() - const params = useParams() - const planetId = params?.id as string | undefined; - - const [planetConfig, setPlanetConfig] = useState(defaultPlanetConfig) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const { isDark, toggleDarkMode } = UseDarkMode() - - const handleConfigChange = (newConfig: Partial) => { - setPlanetConfig((prev) => ({ ...prev, ...newConfig })) - } - - useEffect(() => { - const fetchPlanetData = async () => { - if (!planetId) { - setError("No planet ID provided.") - setLoading(false) - return - } - - try { - const idAsNumber = Number.parseInt(planetId) - if (isNaN(idAsNumber)) { - throw new Error("Invalid planet ID") - } - - const response = await fetch(`/api/gameplay/classifications/${idAsNumber}`, { - cache: "no-store", - }) - const payload = await response.json().catch(() => null) - if (!response.ok || !payload?.classification) { - throw new Error(payload?.error ?? "Failed to load planet data") - } - - const data = payload.classification - - if (!data) { - throw new Error("Planet not found") - } - - let config = data.classificationConfiguration - - // Ensure it's parsed if Supabase returned it as a string - if (typeof config === "string") { - try { - config = JSON.parse(config) - } catch (err) { - console.warn("Failed to parse classificationConfiguration JSON string:", err) - config = {} - } - } - - if (config?.planetConfiguration) { - setPlanetConfig(config.planetConfiguration) - } - - setError(null) - } catch (err) { - console.error("Error fetching planet data:", err) - setError(err instanceof Error ? err.message : "Failed to load planet data") - } finally { - setLoading(false) - } - } - - fetchPlanetData() - }, [planetId]) - - // Hide sidebar for this page - useEffect(() => { - if (typeof window !== "undefined") { - window.dispatchEvent(new CustomEvent("sidebar:collapse", { detail: { collapsed: true } })); - window.localStorage.setItem("hideSidebar", "true"); - } - return () => { - if (typeof window !== "undefined") { - window.localStorage.removeItem("hideSidebar"); - window.dispatchEvent(new CustomEvent("sidebar:collapse", { detail: { collapsed: false } })); - } - }; - }, []); - - if (loading) { - return ( -
-
- {}} - /> -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
-
-

Loading planet data...

-
-
- ) - } - - if (error) { - return ( -
-
- {}} - /> -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
-

Error

-

{error}

- -
-
- ) - } - - return ( -
-
- {}} - /> -
- {}} - activityFeed={[]} - otherClassifications={[]} - /> -
- {/* Planet Viewer */} -
- -
- - {/* Settings Panel */} -
-
-
-

Planet Settings

- ID: {planetId} -
- -
-
- - -
-
-
-
-
-
- ) -} diff --git a/src/app/research/page.tsx b/src/app/research/page.tsx index aca641f6e..c6caa4efa 100644 --- a/src/app/research/page.tsx +++ b/src/app/research/page.tsx @@ -4,7 +4,7 @@ import React, { Suspense, useEffect, useRef, useState } from "react"; import { useSearchParams } from "next/navigation"; import ActivityHeaderSection from "@/src/components/social/activity/ActivityHeaderSection"; import { Button } from "@/src/components/ui/button"; -import CompactResearchPanel from "@/src/components/research/CompactResearchPanel"; +import CompactResearchPanel from "@/src/features/research/components/CompactResearchPanel"; import ReferralCodePanel from "@/src/components/profile/setup/Referrals"; import UseDarkMode from "@/src/shared/hooks/useDarkMode"; import MainHeader from "@/src/components/layout/Header/MainHeader"; diff --git a/src/app/structures/telescope/[project]/[id]/[mission]/TelescopeClassifyPageClient.tsx b/src/app/structures/telescope/[project]/[id]/[mission]/TelescopeClassifyPageClient.tsx index 91094081c..f68ac38d8 100644 --- a/src/app/structures/telescope/[project]/[id]/[mission]/TelescopeClassifyPageClient.tsx +++ b/src/app/structures/telescope/[project]/[id]/[mission]/TelescopeClassifyPageClient.tsx @@ -11,7 +11,6 @@ import { StarterTelescopeTess, TelescopeTessWithId } from "@/src/components/proj import MainHeader from "@/src/components/layout/Header/MainHeader" import PlanetTypeCommentForm from "@/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PlanetType" import VotePlanetClassifications from "@/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PHVote" -import PlanetGenerator from "@/src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator" import { DailyMinorPlanetWithId } from "@/src/components/projects/Telescopes/DailyMinorPlanet" import { TelescopeBackground } from "@/src/components/classification/telescope/telescope-background" import { DiskDetectorTutorial } from "@/src/components/projects/Telescopes/DiskDetector" @@ -73,7 +72,18 @@ export default function TelescopeClassifyPage() { break; case "paint": component = ( - +
+

Surface Visualisation Offline

+

+ The advanced planetary surface imaging suite has been decommissioned for station maintenance. Please focus on core classification tasks. +

+ +
); break; default: diff --git a/src/components/discovery/data-sources/Astronomers/PlanetHunters/ChildClassificationMarker.tsx b/src/components/discovery/data-sources/Astronomers/PlanetHunters/ChildClassificationMarker.tsx deleted file mode 100644 index eaf9b58d4..000000000 --- a/src/components/discovery/data-sources/Astronomers/PlanetHunters/ChildClassificationMarker.tsx +++ /dev/null @@ -1,154 +0,0 @@ -"use client"; - -import { useRef, useState } from "react"; -import * as THREE from "three"; -import { useFrame } from "@react-three/fiber"; -import { Html, Line } from "@react-three/drei"; -import { type ChildClassification } from "@/src/components/discovery/planets/physics"; - -interface ChildClassificationMarkerProps { - classification: ChildClassification; - planetRadius: number; - onClassificationClick?: (classification: ChildClassification) => void; -} - -// Color mapping for different classification types -const getTypeColor = (type: string): { color: string; emissive: string } => { - const typeColors: Record = { - "cloud": { color: "#E8F4F8", emissive: "#FFFFFF" }, - "zoodex-mars": { color: "#FFA500", emissive: "#FF8C00" }, - "planet-4": { color: "#CD853F", emissive: "#D2691E" }, - "compass": { color: "#4169E1", emissive: "#1E90FF" }, - "waypoint": { color: "#32CD32", emissive: "#228B22" }, - "anomaly": { color: "#FF1493", emissive: "#FF69B4" }, - "structure": { color: "#A0522D", emissive: "#8B4513" }, - "lifeform": { color: "#00FF7F", emissive: "#00FA9A" }, - }; - - // Try to match the type with known types - const lowerType = type.toLowerCase(); - for (const [key, colors] of Object.entries(typeColors)) { - if (lowerType.includes(key)) { - return colors; - } - } - - // Default color for unknown types - return { color: "#FFFFFF", emissive: "#CCCCCC" }; -}; - -export default function ChildClassificationMarker({ - classification, - planetRadius, - onClassificationClick -}: ChildClassificationMarkerProps) { - const meshRef = useRef(null); - const [hovered, setHovered] = useState(false); - - // Convert lat/long to 3D position on sphere - const position = new THREE.Vector3(); - const lat = THREE.MathUtils.degToRad(classification.position.latitude); - const lon = THREE.MathUtils.degToRad(classification.position.longitude); - - // Slightly above surface - const radius = planetRadius * 1.015; - position.x = radius * Math.cos(lat) * Math.cos(lon); - position.y = radius * Math.sin(lat); - position.z = radius * Math.cos(lat) * Math.sin(lon); - - // Label position - const labelPosition = position.clone().multiplyScalar(1.25); - - // Get colors for this classification type - const { color, emissive } = getTypeColor(classification.type); - - // Small marker size - const markerSize = 0.015 * planetRadius; - - // Gentle pulse - useFrame((state) => { - if (meshRef.current) { - const pulse = Math.sin(state.clock.getElapsedTime() * 1.5) * 0.1 + 1; - meshRef.current.scale.setScalar(pulse); - } - }); - - return ( - - {/* Marker on planet surface */} - { - e.stopPropagation(); - onClassificationClick?.(classification); - }} - onPointerOver={(e) => { - e.stopPropagation(); - setHovered(true); - document.body.style.cursor = 'pointer'; - }} - onPointerOut={() => { - setHovered(false); - document.body.style.cursor = 'default'; - }} - > - {/* Main marker - using cone/pyramid shape for variety */} - - - - - {/* Small glow */} - - - - - - {/* Label on hover */} - {hovered && ( - <> - - - -
-
- {classification.type.replace(/-/g, ' ')} -
- {classification.content && ( -
- {classification.content} -
- )} -
- ID: {classification.id} -
-
- - - )} -
- ); -} diff --git a/src/components/discovery/data-sources/Astronomers/PlanetHunters/DepositMarker.tsx b/src/components/discovery/data-sources/Astronomers/PlanetHunters/DepositMarker.tsx deleted file mode 100644 index e4799df42..000000000 --- a/src/components/discovery/data-sources/Astronomers/PlanetHunters/DepositMarker.tsx +++ /dev/null @@ -1,138 +0,0 @@ -"use client"; - -import { useRef, useState } from "react"; -import * as THREE from "three"; -import { useFrame } from "@react-three/fiber"; -import { Html, Line } from "@react-three/drei"; -import { type MineralDeposit, getDepositVisuals } from "@/src/components/discovery/planets/physics"; - -interface DepositMarkerProps { - deposit: MineralDeposit; - planetRadius: number; - temperature: number; - onDepositClick?: (deposit: MineralDeposit) => void; -} - -export default function DepositMarker({ - deposit, - planetRadius, - temperature, - onDepositClick -}: DepositMarkerProps) { - const meshRef = useRef(null); - const [hovered, setHovered] = useState(false); - - // Convert lat/long to 3D position on sphere - const position = new THREE.Vector3(); - const lat = THREE.MathUtils.degToRad(deposit.position.latitude); - const lon = THREE.MathUtils.degToRad(deposit.position.longitude); - - // Spherical to Cartesian conversion - // Slightly above surface to avoid z-fighting - const radius = planetRadius * 1.01; - position.x = radius * Math.cos(lat) * Math.cos(lon); - position.y = radius * Math.sin(lat); - position.z = radius * Math.cos(lat) * Math.sin(lon); - - // Get visual properties based on deposit type and state - const visuals = deposit.color - ? { color: deposit.color, emissive: deposit.color, intensity: 0.3 } - : getDepositVisuals(deposit.type, deposit.state, temperature); - - // Smaller deposit size for subtlety - const depositSize = Math.max(0.02, deposit.size * 0.5) * planetRadius; - - // Label position (closer to planet for less obtrusiveness) - const labelPosition = position.clone().multiplyScalar(1.3); - - // Pulse effect for deposits (more subtle) - useFrame((state) => { - if (meshRef.current) { - const pulse = Math.sin(state.clock.getElapsedTime() * 2) * 0.15 + 1; - meshRef.current.scale.setScalar(pulse); - } - }); - - return ( - - {/* Deposit marker on planet surface */} - { - e.stopPropagation(); - onDepositClick?.(deposit); - }} - onPointerOver={(e) => { - e.stopPropagation(); - setHovered(true); - document.body.style.cursor = 'pointer'; - }} - onPointerOut={() => { - setHovered(false); - document.body.style.cursor = 'default'; - }} - > - {/* Main deposit sphere */} - - - - - {/* Smaller glow effect */} - - - - - - {/* Connection line from deposit to label - only on hover */} - {hovered && ( - <> - - - {/* Floating label */} - -
- {/* Label container - smaller */} -
-
- {deposit.type.replace(/-/g, ' ')} -
-
- {deposit.state} • {deposit.purity ? `${deposit.purity.toFixed(0)}%` : 'Unknown'} -
- {deposit.amount && ( -
- {deposit.amount.toFixed(1)} units -
- )} -
-
- - - )} -
- ); -} diff --git a/src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator.tsx b/src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator.tsx deleted file mode 100644 index 0ffac3879..000000000 --- a/src/components/discovery/data-sources/Astronomers/PlanetHunters/PlanetGenerator.tsx +++ /dev/null @@ -1,474 +0,0 @@ - -"use client"; - -import { useState, useEffect } from "react"; -import PlanetViewer, { PlanetOnly } from "./planetViewer"; -import { - type PlanetConfig, - defaultPlanetConfig, - type MineralDeposit, - type ChildClassification, - getMineralState -} from "@/src/components/discovery/planets/physics"; -import SettingsPanel from "./SettingsPanel"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/src/components/ui/dialog"; -import { Button } from "@/src/components/ui/button"; -import { Textarea } from "@/src/components/ui/textarea"; -import { mergeClassificationConfiguration } from "@/src/lib/gameplay/classification-configuration"; - -interface PlanetGeneratorProps { - classificationId: string; - editMode?: boolean; - hideControls?: boolean; - hideBackground?: boolean; - hideSky?: boolean; - hideZoom?: boolean; - hideSettings?: boolean; - style?: React.CSSProperties; - cameraZoom?: number; -} - -// Minimal variant: just the planet, no dialogs, no settings, no extra markup -export function PlanetGeneratorMinimal({ - classificationId, - hideBackground, - hideSky, - hideZoom, - style, - cameraZoom, -}: { - classificationId: string; - hideBackground?: boolean; - hideSky?: boolean; - hideZoom?: boolean; - style?: React.CSSProperties; - cameraZoom?: number; -}) { - const [planetConfig, setPlanetConfig] = useState(defaultPlanetConfig); - const [planetStats, setPlanetStats] = useState({}); - - useEffect(() => { - const fetchExportedValues = async () => { - const idAsNumber = Number.parseInt(classificationId); - if (isNaN(idAsNumber)) return; - - const res = await fetch(`/api/gameplay/classifications?id=${idAsNumber}&limit=1`); - const payload = await res.json().catch(() => ({})); - const data = res.ok ? payload?.classifications?.[0] : null; - const error = !res.ok ? payload?.error : null; - - if (error || !data?.classificationConfiguration?.exportedValue) return; - - const { exportedValue } = data.classificationConfiguration; - setPlanetStats((prev) => ({ - ...prev, - ...(exportedValue.mass && { mass: exportedValue.mass }), - ...(exportedValue.radius && { radius: exportedValue.radius }), - })); - }; - fetchExportedValues(); - }, [classificationId]); - - // Merge handler for partial config - const handleConfigChange = (config: Partial) => { - setPlanetConfig((prev) => ({ ...prev, ...config })); - }; - return ( -
- -
- ); -} - -interface PlanetGeneratorProps { - classificationId: string; - editMode?: boolean; - hideControls?: boolean; - hideBackground?: boolean; - hideSky?: boolean; - hideZoom?: boolean; - hideSettings?: boolean; - style?: React.CSSProperties; - cameraZoom?: number; -} - -interface PlanetStats { - mass?: number; - radius?: number; - [key: string]: any; -}; - -function secureRandomUnit(): number { - const randomBuffer = new Uint32Array(1); - crypto.getRandomValues(randomBuffer); - return randomBuffer[0] / 4294967296; -} - -function secureRandomBetween(min: number, max: number): number { - return secureRandomUnit() * (max - min) + min; -} -export default function PlanetGenerator({ - classificationId, - editMode, - hideControls, - hideBackground, - hideSky, - hideZoom, - hideSettings, - style, - cameraZoom, -}: PlanetGeneratorProps) { - const [planetConfig, setPlanetConfig] = useState(defaultPlanetConfig); - const [importDialogOpen, setImportDialogOpen] = useState(false); - const [exportDialogOpen, setExportDialogOpen] = useState(false); - const [depositDialogOpen, setDepositDialogOpen] = useState(false); - const [childClassificationDialogOpen, setChildClassificationDialogOpen] = useState(false); - const [selectedDeposit, setSelectedDeposit] = useState(null); - const [selectedChildClassification, setSelectedChildClassification] = useState(null); - const [deposits, setDeposits] = useState([]); - const [childClassifications, setChildClassifications] = useState([]); - const [importText, setImportText] = useState(""); - const [importError, setImportError] = useState(""); - const [planetStats, setPlanetStats] = useState({}); - - const handleConfigChange = (newConfig: Partial) => { - setPlanetConfig((prev) => ({ ...prev, ...newConfig })); - }; - - const handleImport = () => { - try { - const config = JSON.parse(importText); - setPlanetConfig(config); - setImportDialogOpen(false); - setImportError(""); - } catch { - setImportError("Invalid JSON configuration"); - } - }; - - const handleExport = async () => { - const idAsNumber = Number.parseInt(classificationId); - if (isNaN(idAsNumber)) return; - await mergeClassificationConfiguration(idAsNumber, { - planetConfiguration: planetConfig, - }); - }; - - useEffect(() => { - const fetchExportedValues = async () => { - const idAsNumber = Number.parseInt(classificationId); - if (isNaN(idAsNumber)) return; - - const classRes = await fetch(`/api/gameplay/classifications?id=${idAsNumber}&limit=1`); - const classPayload = await classRes.json().catch(() => ({})); - const data = classRes.ok ? classPayload?.classifications?.[0] : null; - const error = !classRes.ok ? classPayload?.error : null; - - if (error || !data?.classificationConfiguration?.exportedValue) return; - - const { exportedValue } = data.classificationConfiguration; - const temperature = exportedValue.temperature || 288; - - setPlanetStats((prev) => ({ - ...prev, - ...(exportedValue.mass && { mass: exportedValue.mass }), - ...(exportedValue.radius && { radius: exportedValue.radius }), - })); - - // Fetch mineral deposits by classification ID (discovery field) - const depositsRes = await fetch(`/api/gameplay/mineral-deposits?discovery=${idAsNumber}`); - const depositsPayload = await depositsRes.json().catch(() => ({})); - const depositsData = depositsRes.ok ? depositsPayload?.deposits : []; - const depositsError = !depositsRes.ok ? depositsPayload?.error : null; - - - if (!depositsError && depositsData) { - const parsedDeposits: MineralDeposit[] = depositsData.map((deposit: any) => { - // Parse mineralconfiguration JSON string - let config; - try { - config = typeof deposit.mineral_configuration === 'string' - ? JSON.parse(deposit.mineral_configuration) - : deposit.mineral_configuration; - } catch (e) { - console.error("Error parsing mineral_configuration:", e); - config = {}; - } - - const type = config?.type || config?.mineralType || "water-ice"; - const state = getMineralState(type, temperature); - - // Parse location JSON string or plain text - let position: { latitude: number; longitude: number }; - if (deposit.location) { - try { - if (typeof deposit.location === 'string') { - if (deposit.location.trim().startsWith('{') || deposit.location.trim().startsWith('[')) { - position = JSON.parse(deposit.location); - } else { - // Plain string, generate random - position = { - latitude: secureRandomBetween(-90, 90), - longitude: secureRandomBetween(-180, 180), - }; - } - } else { - position = deposit.location; - } - } catch (e) { - position = { - latitude: secureRandomBetween(-90, 90), - longitude: secureRandomBetween(-180, 180), - }; - } - } else { - position = { - latitude: secureRandomBetween(-90, 90), - longitude: secureRandomBetween(-180, 180), - }; - } - - return { - id: deposit.id, - type, - state, - position, - size: secureRandomBetween(0.02, 0.05), - purity: config?.purity || 50, - amount: config?.amount, - classificationId: deposit.discovery - }; - }); - - setDeposits(parsedDeposits); - } - - // Fetch child classifications - const childRes = await fetch( - `/api/gameplay/classifications?classificationParent=${idAsNumber}&orderBy=created_at&ascending=false&limit=500` - ); - const childPayload = await childRes.json().catch(() => ({})); - const childClassData = childRes.ok ? childPayload?.classifications : []; - const childError = !childRes.ok ? childPayload?.error : null; - - - - if (!childError && childClassData) { - const parsedChildren: ChildClassification[] = childClassData.map((c: any) => { - let position: { latitude: number; longitude: number }; - const configLocation = c.classificationConfiguration?.location; - if (configLocation) { - try { - position = typeof configLocation === 'string' ? JSON.parse(configLocation) : configLocation; - } catch { - position = { - latitude: secureRandomBetween(-90, 90), - longitude: secureRandomBetween(-180, 180), - }; - } - } else { - position = { - latitude: secureRandomBetween(-90, 90), - longitude: secureRandomBetween(-180, 180), - }; - } - - return { - id: c.id, - type: c.classificationtype || "unknown", - position, - content: c.content - }; - }); - - setChildClassifications(parsedChildren); - } - }; - - fetchExportedValues(); - }, [classificationId]); - - const exportConfig = JSON.stringify(planetConfig, null, 2); - - return ( -
- {/* Debug info */} -
- 🔍 Deposits: {deposits.length} | Children: {childClassifications.length} -
- -
- {/* Planet Viewer */} -
- { - setSelectedDeposit(deposit); - setDepositDialogOpen(true); - }} - onChildClassificationClick={(child) => { - setSelectedChildClassification(child); - setChildClassificationDialogOpen(true); - }} - hideBackground={hideBackground} - hideSky={hideSky} - hideZoom={hideZoom} - cameraZoom={cameraZoom} - /> -
- - {/* Settings Panel */} - {!hideControls && !hideSettings && ( -
- -
- )} -
- - {/* Import Dialog */} - {!hideControls && ( - - - - Import Planet Configuration - Paste JSON below - -