From 5aa5b4452efd7cb3816f19df3887303b5e535ddd Mon Sep 17 00:00:00 2001 From: pholmq Date: Tue, 27 Jan 2026 12:40:44 +0100 Subject: [PATCH 001/135] zodiac closer to earth --- src/components/Helpers/Zodiac.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Helpers/Zodiac.jsx b/src/components/Helpers/Zodiac.jsx index dc35677..1d2e91d 100644 --- a/src/components/Helpers/Zodiac.jsx +++ b/src/components/Helpers/Zodiac.jsx @@ -89,7 +89,7 @@ export default function Zodiac() { <> {zodiac && ( From 64b316c2864fb9d5124a7c3607b54a78a350a931 Mon Sep 17 00:00:00 2001 From: pholmq Date: Tue, 27 Jan 2026 16:09:28 +0100 Subject: [PATCH 002/135] Sidereal & Tropical Zodiac --- src/CHANGELOG.md | 7 + src/components/Helpers/TropicalZodiac.jsx | 150 ++++++++++++++++++++++ src/components/Helpers/Zodiac.jsx | 54 +++++++- src/components/LevaUI.jsx | 6 + src/components/Planet.jsx | 89 +++++++------ src/store.js | 10 +- 6 files changed, 265 insertions(+), 51 deletions(-) create mode 100644 src/CHANGELOG.md create mode 100644 src/components/Helpers/TropicalZodiac.jsx diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md new file mode 100644 index 0000000..8d2cea9 --- /dev/null +++ b/src/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change Log + +## [1.0.3] - 2026-01-27 +### Added +- Ephemerides +- HA Reys Star constellations +- diff --git a/src/components/Helpers/TropicalZodiac.jsx b/src/components/Helpers/TropicalZodiac.jsx new file mode 100644 index 0000000..909ed97 --- /dev/null +++ b/src/components/Helpers/TropicalZodiac.jsx @@ -0,0 +1,150 @@ +import { useMemo } from "react"; +import * as THREE from "three"; +import { CanvasTexture, DoubleSide } from "three"; +import getCircularText from "../../utils/getCircularText"; +import { useStore } from "../../store"; + +function ZodiacGrid() { + const geometry = useMemo(() => { + const points = []; + const radius = 260; + const radials = 12; + const divisions = 64; + + // 12 Visible Spokes + for (let i = 0; i < radials; i++) { + const angle = (i / radials) * Math.PI * 2; + const x = Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + points.push(new THREE.Vector3(0, 0, 0)); + points.push(new THREE.Vector3(x, 0, z)); + } + + // Outer Circle + for (let i = 0; i < divisions; i++) { + const t1 = (i / divisions) * Math.PI * 2; + const t2 = ((i + 1) / divisions) * Math.PI * 2; + points.push( + new THREE.Vector3(Math.cos(t1) * radius, 0, Math.sin(t1) * radius) + ); + points.push( + new THREE.Vector3(Math.cos(t2) * radius, 0, Math.sin(t2) * radius) + ); + } + + return new THREE.BufferGeometry().setFromPoints(points); + }, []); + + return ( + + + + ); +} + +function ZodiacLabels() { + const names = + " GEMINI" + + " TAURUS" + + " ARIES" + + " PISCES" + + " AQUARIUS" + + " CAPRICORN" + + " SAGITTARIUS" + + " SCORPIO" + + " LIBRA" + + " VIRGO" + + " LEO" + + " CANCER"; + const text1 = getCircularText( + names, + 800, + 0, + "right", + false, + true, + "Arial", + "18pt", + 2 + ); + const texture1 = new CanvasTexture(text1); + + const symbols = + " ♊" + + " ♉" + + " ♈" + + " ♓" + + " ♒" + + " ♑" + + " ♐" + + " ♏" + + " ♎" + + " ♍" + + " ♌" + + " ♋"; + const text2 = getCircularText( + symbols, + 800, + 0, + "right", + false, + true, + "Segoe UI Symbol", + "18pt", + 2 + ); + const texture2 = new CanvasTexture(text2); + + return ( + + + + + + + + + + + ); +} + +export default function TropicalZodiac() { + const tropicalZodiac = useStore((s) => s.tropicalZodiac); + const zodiacSize = 100; + const hScale = useStore((s) => s.hScale); + const size = (zodiacSize * hScale) / 100; + + // CALIBRATION: Adjust this if Aries doesn't align with the Vernal Equinox vector + // -1.57 is -90 degrees (standard 3 o'clock start offset) + const ROTATION_OFFSET = -Math.PI / 2; + + return ( + <> + {tropicalZodiac && ( + + + + + )} + + ); +} diff --git a/src/components/Helpers/Zodiac.jsx b/src/components/Helpers/Zodiac.jsx index 1d2e91d..643c48c 100644 --- a/src/components/Helpers/Zodiac.jsx +++ b/src/components/Helpers/Zodiac.jsx @@ -1,6 +1,51 @@ +import { useMemo } from "react"; // Added useMemo +import * as THREE from "three"; // Import THREE for Vector3 and BufferGeometry import { CanvasTexture, DoubleSide } from "three"; import getCircularText from "../../utils/getCircularText"; import { useStore } from "../../store"; + +// New component to replace PolarGridHelper +function ZodiacGrid() { + const geometry = useMemo(() => { + const points = []; + const radius = 260; // Matches original args[0] + const radials = 12; // Changed from 24 to 12 (Only visible spokes) + const divisions = 64; // Matches original args[3] (Circle smoothness) + + // 1. Generate the 12 visible spokes + for (let i = 0; i < radials; i++) { + const angle = (i / radials) * Math.PI * 2; + const x = Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + + // Line from center (0,0,0) to outer edge + points.push(new THREE.Vector3(0, 0, 0)); + points.push(new THREE.Vector3(x, 0, z)); + } + + // 2. Generate the outer circle + for (let i = 0; i < divisions; i++) { + const t1 = (i / divisions) * Math.PI * 2; + const t2 = ((i + 1) / divisions) * Math.PI * 2; + + points.push( + new THREE.Vector3(Math.cos(t1) * radius, 0, Math.sin(t1) * radius) + ); + points.push( + new THREE.Vector3(Math.cos(t2) * radius, 0, Math.sin(t2) * radius) + ); + } + + return new THREE.BufferGeometry().setFromPoints(points); + }, []); + + return ( + + {/* Only the visible grey color */} + + ); +} + function ZodiacLabels() { const names = " GEMINI" + @@ -88,12 +133,9 @@ export default function Zodiac() { return ( <> {zodiac && ( - - + + {/* Replaced polarGridHelper with custom ZodiacGrid */} + )} diff --git a/src/components/LevaUI.jsx b/src/components/LevaUI.jsx index 81299c0..9bfb11c 100644 --- a/src/components/LevaUI.jsx +++ b/src/components/LevaUI.jsx @@ -32,6 +32,8 @@ const LevaUI = () => { setZodiac, zodiacSize, setZodiacSize, + tropicalZodiac, + setTropicalZodiac, polarLine, setPolarLine, polarLineSize, @@ -267,6 +269,10 @@ const LevaUI = () => { value: zodiac, onChange: setZodiac, }, + "Tropical Zodiac": { + value: tropicalZodiac, + onChange: setTropicalZodiac, + }, "Sphere/Grid/Zodiac size": { value: hScale, min: 0.5, diff --git a/src/components/Planet.jsx b/src/components/Planet.jsx index 58a93c2..d82b24d 100644 --- a/src/components/Planet.jsx +++ b/src/components/Planet.jsx @@ -5,6 +5,7 @@ import { usePlanetCameraStore } from "./PlanetCamera/planetCameraStore"; import useTextureLoader from "../utils/useTextureLoader"; import CelestialSphere from "./Helpers/CelestialSphere"; import PolarLine from "./Helpers/PolarLine"; +import TropicalZodiac from "./Helpers/TropicalZodiac"; import HoverObj from "../components/HoverObj/HoverObj"; import PlanetRings from "./PlanetRings"; import NameLabel from "./Labels/NameLabel"; @@ -75,49 +76,53 @@ const Planet = memo(function Planet({ s, actualMoon, name }) { !(planetCamera && !cameraTransitioning && name === planetCameraTarget); return ( - - {s.name === "Earth" && } - {(s.name === "Earth" || s.name === "Sun") && ( - - )} - {showLabel && } - {showLabel && } - - - - - {s.light && } - - {s.geoSphere && geoSphere ? ( - - ) : null} - {s.rings && ( - + + {s.name === "Earth" && } + + {s.name === "Earth" && } + + {(s.name === "Earth" || s.name === "Sun") && ( + )} + {showLabel && } + {showLabel && } + + + + + {s.light && } + + {s.geoSphere && geoSphere ? ( + + ) : null} + {s.rings && ( + + )} + ); diff --git a/src/store.js b/src/store.js index 5268592..0ab12ee 100644 --- a/src/store.js +++ b/src/store.js @@ -56,7 +56,11 @@ export const useStore = create((set) => ({ zodiac: false, setZodiac: (v) => set({ zodiac: v }), - zodiacSize: 100, + + tropicalZodiac: false, + setTropicalZodiac: (v) => set({ tropicalZodiac: v }), + + zodiacSize: 130, setZodiacSize: (v) => set({ zodiacSize: v }), polarLine: false, @@ -81,7 +85,7 @@ export const useStore = create((set) => ({ officialStarDistances: true, setOfficialStarDistances: (v) => set({ officialStarDistances: v }), - + // Added Constellations State showConstellations: false, setShowConstellations: (v) => set({ showConstellations: v }), @@ -248,4 +252,4 @@ export const useStarStore = create((set, get) => ({ return { settings: newSettings }; }); }, -})); \ No newline at end of file +})); From 2584330e1868b28c06999eabfc080dcb42a27acc Mon Sep 17 00:00:00 2001 From: pholmq Date: Wed, 28 Jan 2026 11:17:04 +0100 Subject: [PATCH 003/135] Moved Sidereal Zodiac back to its former position --- package-lock.json | 4 ++-- src/components/Helpers/Zodiac.jsx | 6 +++++- src/components/LevaUI.jsx | 11 ++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33ece5d..7d06f92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "the_tychosium", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "the_tychosium", - "version": "1.0.2", + "version": "1.0.3", "license": "GPL-2.0-or-only", "dependencies": { "@react-three/drei": "9.102.6", diff --git a/src/components/Helpers/Zodiac.jsx b/src/components/Helpers/Zodiac.jsx index 643c48c..7ae3cfd 100644 --- a/src/components/Helpers/Zodiac.jsx +++ b/src/components/Helpers/Zodiac.jsx @@ -133,7 +133,11 @@ export default function Zodiac() { return ( <> {zodiac && ( - + {/* Replaced polarGridHelper with custom ZodiacGrid */} diff --git a/src/components/LevaUI.jsx b/src/components/LevaUI.jsx index 9bfb11c..7d1694e 100644 --- a/src/components/LevaUI.jsx +++ b/src/components/LevaUI.jsx @@ -155,7 +155,7 @@ const LevaUI = () => { { collapsed: false } ), - "Trace": folder( + Trace: folder( { TraceOnOff: { label: "Trace On/Off", @@ -246,18 +246,19 @@ const LevaUI = () => { step: 100, onChange: setStarDistanceModifier, }, - "Equidistant stars": { + //Renamed equdistant stars to Celestial sphere in the meny. Easier to understand. + "Celestial sphere": { value: false, min: 1, step: 100, onChange: setEquidistantStars, }, // Added Constellations here - "Constellations": { + Constellations: { value: showConstellations, onChange: setShowConstellations, }, - "Celestial sphere": { + "Celestial grid": { value: celestialSphere, onChange: setCelestialSphere, }, @@ -369,4 +370,4 @@ const LevaUI = () => { ); }; -export default LevaUI; \ No newline at end of file +export default LevaUI; From 3bb8ab005b5c334cb9adacfc51e82541ab770dbe Mon Sep 17 00:00:00 2001 From: pholmq Date: Thu, 29 Jan 2026 08:09:23 +0100 Subject: [PATCH 004/135] added stars --- src/components/Stars/LabeledStars.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/Stars/LabeledStars.jsx b/src/components/Stars/LabeledStars.jsx index ce236ca..1157b3a 100644 --- a/src/components/Stars/LabeledStars.jsx +++ b/src/components/Stars/LabeledStars.jsx @@ -20,6 +20,16 @@ export const LABELED_STARS = [ "Achernar", "Polaris Australis", "Hadar", + "Spica", + "Rigil Kentaurus", + "Acrux", + "Antares", + "Pollux", + "Formalhaut", + "Mimosa", + "Regulus", + "Adhara", + "Castor", ]; export default function LabeledStars() { From 74e4d316210f58a7726a13f477dc42e2062054e3 Mon Sep 17 00:00:00 2001 From: pholmq Date: Thu, 29 Jan 2026 09:24:08 +0100 Subject: [PATCH 005/135] eph work --- src/TSNext.jsx | 8 +- src/components/EphController.jsx | 134 --------- src/components/Ephemerides/EphController.jsx | 113 +++++++ .../{Menus => Ephemerides}/Ephemerides.jsx | 10 +- .../Ephemerides/EphemeridesResult.jsx | 275 ++++++++++++++++++ .../Ephemerides/ephemeridesStore.js | 46 +++ src/components/Menus/ephemeridesStore.js | 8 - 7 files changed, 444 insertions(+), 150 deletions(-) delete mode 100644 src/components/EphController.jsx create mode 100644 src/components/Ephemerides/EphController.jsx rename src/components/{Menus => Ephemerides}/Ephemerides.jsx (96%) create mode 100644 src/components/Ephemerides/EphemeridesResult.jsx create mode 100644 src/components/Ephemerides/ephemeridesStore.js delete mode 100644 src/components/Menus/ephemeridesStore.js diff --git a/src/TSNext.jsx b/src/TSNext.jsx index a7e756d..021deb5 100644 --- a/src/TSNext.jsx +++ b/src/TSNext.jsx @@ -13,8 +13,9 @@ import PlanetsPositionsMenu from "./components/Menus/PlanetsPositionsMenu"; import StarsHelpersMenu from "./components/Menus/StarsHelpersMenu"; import PosController from "./components/PosController"; import Positions from "./components/Menus/Positions"; -import Ephemerides from "./components/Menus/Ephemerides"; -import EphController from "./components/EphController"; +import Ephemerides from "./components/Ephemerides/Ephemerides"; +import EphController from "./components/Ephemerides/EphController"; +import EphemeridesResult from "./components/Ephemerides/EphemeridesResult"; import Stars from "./components/Stars/Stars"; import LabeledStars from "./components/Stars/LabeledStars"; // import BSCStars from "./components/Stars/BSCStars"; @@ -32,7 +33,7 @@ import HighlightSelectedStar from "./components/StarSearch/HighlightSelectedStar import Help from "./components/Help/Help"; import PlanetCameraCompass from "./components/PlanetCamera/PlanetCameraCompass"; import TransitionCamera from "./components/PlanetCamera/TransitionCamera"; -import Constellations from"./components/Stars/Constellations"; +import Constellations from "./components/Stars/Constellations"; const isTouchDevice = () => { return ( @@ -103,6 +104,7 @@ const TSNext = () => { + diff --git a/src/components/EphController.jsx b/src/components/EphController.jsx deleted file mode 100644 index 2bab03e..0000000 --- a/src/components/EphController.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import { useEffect, useState } from "react"; -import { useThree } from "@react-three/fiber"; -import { useStore, usePlotStore, useSettingsStore } from "../store"; -import { useEphemeridesStore } from "./Menus/ephemeridesStore"; -import useFrameInterval from "../utils/useFrameInterval"; -import { - posToDate, - posToTime, - dateTimeToPos, - sDay, -} from "../utils/time-date-functions"; -import { - movePlotModel, - getPlotModelRaDecDistance, -} from "../utils/plotModelFunctions"; - -// --- Helper function to save file --- -const saveEphemeridesAsText = (data, params) => { - let output = "--- EPHEMERIDES REPORT ---\n"; - output += `Generated on: ${new Date().toLocaleString()}\n`; - output += `Start Date: ${params.startDate}\n`; - output += `End Date: ${params.endDate}\n`; - output += `Step Size: ${params.stepSize} ${params.stepFactor === 1 ? "Days" : "Years"}\n`; - output += "--------------------------------------\n\n"; - - Object.keys(data).forEach((planetName) => { - output += `PLANET: ${planetName.toUpperCase()}\n`; - // Table Header - output += `${"Date".padEnd(12)} | ${"Time".padEnd(10)} | ${"RA".padEnd(12)} | ${"Dec".padEnd(12)} | ${"Dist".padEnd(12)} | ${"Elongation".padEnd(10)}\n`; - output += "-".repeat(80) + "\n"; - - // Table Rows - data[planetName].forEach((row) => { - output += `${row.date.padEnd(12)} | ${row.time.padEnd(10)} | ${row.ra.padEnd(12)} | ${row.dec.padEnd(12)} | ${row.dist.padEnd(12)} | ${row.elong}\n`; - }); - output += "\n" + "=".repeat(80) + "\n\n"; - }); - - // Create Blob - const blob = new Blob([output], { type: "text/plain" }); - const url = URL.createObjectURL(blob); - - // Generate Filename from Start and End Dates - // We replace potential unsafe characters just in case, though standard YYYY-MM-DD is usually safe - const safeStart = params.startDate.replace(/[:/]/g, "-"); - const safeEnd = params.endDate.replace(/[:/]/g, "-"); - const filename = `Ephemerides_${safeStart}_to_${safeEnd}.txt`; - - // Trigger Download - const link = document.createElement("a"); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - - // Cleanup - document.body.removeChild(link); - URL.revokeObjectURL(url); -}; - -const EphController = () => { - const { scene } = useThree(); - const plotObjects = usePlotStore((s) => s.plotObjects); - - const { trigger, params, resetTrigger } = useEphemeridesStore(); - - const [done, setDone] = useState(true); - - useEffect(() => { - if (trigger && params) { - const startPos = dateTimeToPos(params.startDate, "00:00:00"); - const endPos = dateTimeToPos(params.endDate, "00:00:00"); - if (params.checkedPlanets.length > 0 && startPos <= endPos) { - setDone(false); - } - resetTrigger(); - } - }, [trigger, params, resetTrigger]); - - useFrameInterval(() => { - if (done) return; - - const { startDate, endDate, stepSize, stepFactor, checkedPlanets } = params; - - const startPos = dateTimeToPos(startDate, "00:00:00"); - const endPos = dateTimeToPos(endDate, "00:00:00"); - const increment = stepSize * stepFactor; - - const ephemeridesData = {}; - checkedPlanets.forEach((planet) => { - ephemeridesData[planet] = []; - }); - - let currentPos = startPos; - let steps = 0; - const MAX_STEPS = 50000; - - // console.log("--- Starting Ephemerides Generation ---"); - - while (currentPos <= endPos && steps < MAX_STEPS) { - const currentDate = posToDate(currentPos); - const currentTime = posToTime(currentPos); - - movePlotModel(plotObjects, currentPos); - - checkedPlanets.forEach((name) => { - const data = getPlotModelRaDecDistance(name, plotObjects, scene); - - if (data) { - ephemeridesData[name].push({ - date: currentDate, - time: currentTime, - ra: data.ra, - dec: data.dec, - dist: data.dist, - elong: data.elongation, - }); - } - }); - - currentPos += increment; - steps++; - } - setDone(true); - - // console.log(`Generation Complete. Steps: ${steps}`); - - // Trigger the save file dialog with the new naming convention - saveEphemeridesAsText(ephemeridesData, params); - }); - - return null; -}; -export default EphController; \ No newline at end of file diff --git a/src/components/Ephemerides/EphController.jsx b/src/components/Ephemerides/EphController.jsx new file mode 100644 index 0000000..47d591b --- /dev/null +++ b/src/components/Ephemerides/EphController.jsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from "react"; +import { useThree } from "@react-three/fiber"; +import { usePlotStore } from "../../store"; +import { useEphemeridesStore } from "./ephemeridesStore"; +import useFrameInterval from "../../utils/useFrameInterval"; +import { + posToDate, + posToTime, + dateTimeToPos, +} from "../../utils/time-date-functions"; +import { + movePlotModel, + getPlotModelRaDecDistance, +} from "../../utils/plotModelFunctions"; + +const EphController = () => { + const { scene } = useThree(); + const plotObjects = usePlotStore((s) => s.plotObjects); + + // Destructure the new actions + const { + trigger, + params, + resetTrigger, + setGeneratedData, + setGenerationError, + } = useEphemeridesStore(); + + const [done, setDone] = useState(true); + + useEffect(() => { + if (trigger && params) { + const startPos = dateTimeToPos(params.startDate, "00:00:00"); + const endPos = dateTimeToPos(params.endDate, "00:00:00"); + if (params.checkedPlanets.length > 0 && startPos <= endPos) { + setDone(false); + } + resetTrigger(); + } + }, [trigger, params, resetTrigger]); + + useFrameInterval(() => { + if (done) return; + + const { startDate, endDate, stepSize, stepFactor, checkedPlanets } = params; + + const startPos = dateTimeToPos(startDate, "00:00:00"); + const endPos = dateTimeToPos(endDate, "00:00:00"); + const increment = stepSize * stepFactor; + + // --- Pre-calculation Validation --- + const calculatedSteps = Math.floor((endPos - startPos) / increment) + 1; + const totalOperations = calculatedSteps * checkedPlanets.length; + + // Check limit + if (totalOperations > 100000) { + const errorMsg = + `Total Steps: ${calculatedSteps}\n` + + `Selected Planets: ${checkedPlanets.length}\n` + + `Total Operations: ${totalOperations}\n\n` + + `The limit is 100,000 operations. Please reduce the Date Range, increase the Step Size, or select fewer planets.`; + + setGenerationError(errorMsg); + setDone(true); + return; + } + + const ephemeridesData = {}; + checkedPlanets.forEach((planet) => { + ephemeridesData[planet] = []; + }); + + let currentPos = startPos; + let steps = 0; + + // Safety break (just in case loop logic fails) + const MAX_SAFETY_BREAK = 200000; + + while (currentPos <= endPos && steps < MAX_SAFETY_BREAK) { + const currentDate = posToDate(currentPos); + const currentTime = posToTime(currentPos); + + movePlotModel(plotObjects, currentPos); + + checkedPlanets.forEach((name) => { + const data = getPlotModelRaDecDistance(name, plotObjects, scene); + + if (data) { + ephemeridesData[name].push({ + date: currentDate, + time: currentTime, + ra: data.ra, + dec: data.dec, + dist: data.dist, + elong: data.elongation, + }); + } + }); + + currentPos += increment; + steps++; + } + setDone(true); + + console.log(`Generation Complete. Actual Steps: ${steps}`); + + // Instead of saving, send data to the Store to open the Popup + setGeneratedData(ephemeridesData); + }); + + return null; +}; +export default EphController; diff --git a/src/components/Menus/Ephemerides.jsx b/src/components/Ephemerides/Ephemerides.jsx similarity index 96% rename from src/components/Menus/Ephemerides.jsx rename to src/components/Ephemerides/Ephemerides.jsx index c646bf2..527066f 100644 --- a/src/components/Menus/Ephemerides.jsx +++ b/src/components/Ephemerides/Ephemerides.jsx @@ -69,15 +69,15 @@ const Ephemerides = () => { useEffect(() => { if (ephimerides) { const currentDate = posToDate(posRef.current); - + // Update valuesRef so generation uses the new date valuesRef.current["Start Date"] = currentDate; valuesRef.current["End Date"] = currentDate; // Update Leva UI to show the new date - levaEphStore.set({ - "Start Date": currentDate, - "End Date": currentDate + levaEphStore.set({ + "Start Date": currentDate, + "End Date": currentDate, }); } }, [ephimerides, posRef, levaEphStore]); @@ -149,4 +149,4 @@ const Ephemerides = () => { ); }; -export default Ephemerides; \ No newline at end of file +export default Ephemerides; diff --git a/src/components/Ephemerides/EphemeridesResult.jsx b/src/components/Ephemerides/EphemeridesResult.jsx new file mode 100644 index 0000000..990a6be --- /dev/null +++ b/src/components/Ephemerides/EphemeridesResult.jsx @@ -0,0 +1,275 @@ +import React, { useState, useEffect } from "react"; +import { useEphemeridesStore } from "./ephemeridesStore"; +import { FaSave, FaExclamationTriangle } from "react-icons/fa"; + +const EphemeridesResult = () => { + const { showResult, generatedData, generationError, params, closeResult } = + useEphemeridesStore(); + + const [isDragging, setIsDragging] = useState(false); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); + const [previewText, setPreviewText] = useState(""); + + // --- Formatting Logic --- + const formatDataToText = (data, parameters) => { + if (!data || !parameters) return ""; + + let output = "--- EPHEMERIDES REPORT ---\n"; + output += `Generated on: ${new Date().toLocaleString()}\n`; + output += `Start Date: ${parameters.startDate}\n`; + output += `End Date: ${parameters.endDate}\n`; + output += `Step Size: ${parameters.stepSize} ${ + parameters.stepFactor === 1 ? "Days" : "Years" + }\n`; + output += "--------------------------------------\n\n"; + + Object.keys(data).forEach((planetName) => { + output += `PLANET: ${planetName.toUpperCase()}\n`; + output += `${"Date".padEnd(12)} | ${"Time".padEnd(10)} | ${"RA".padEnd( + 12 + )} | ${"Dec".padEnd(12)} | ${"Dist".padEnd(12)} | ${"Elongation".padEnd( + 10 + )}\n`; + output += "-".repeat(80) + "\n"; + + data[planetName].forEach((row) => { + output += `${row.date.padEnd(12)} | ${row.time.padEnd( + 10 + )} | ${row.ra.padEnd(12)} | ${row.dec.padEnd(12)} | ${row.dist.padEnd( + 12 + )} | ${row.elong}\n`; + }); + output += "\n" + "=".repeat(80) + "\n\n"; + }); + + return output; + }; + + // Update preview when data arrives + useEffect(() => { + if (generatedData && params) { + setPreviewText(formatDataToText(generatedData, params)); + } + }, [generatedData, params]); + + // --- Dragging Logic --- + useEffect(() => { + const handleMouseMove = (e) => { + if (!isDragging) return; + setPosition({ + x: position.x + (e.clientX - dragStart.x), + y: position.y + (e.clientY - dragStart.y), + }); + setDragStart({ x: e.clientX, y: e.clientY }); + }; + + const handleMouseUp = () => setIsDragging(false); + + if (isDragging) { + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + } + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [isDragging, dragStart, position]); + + const handleMouseDown = (e) => { + if (e.target.closest(".popup-header")) { + setIsDragging(true); + setDragStart({ x: e.clientX, y: e.clientY }); + } + }; + + // --- Save to File --- + const handleSave = () => { + if (!previewText || !params) return; + + const blob = new Blob([previewText], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const safeStart = params.startDate.replace(/[:/]/g, "-"); + const safeEnd = params.endDate.replace(/[:/]/g, "-"); + const filename = `Ephemerides_${safeStart}_to_${safeEnd}.txt`; + + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }; + + if (!showResult) return null; + + return ( +
+ {/* Header */} +
+
+ {generationError ? ( + + ) : ( + + )} +

+ {generationError ? "Limit Exceeded" : "Ephemerides result"} +

+
+ +
+ + {/* Content Body */} +
+ {generationError ? ( +
+

+ Calculation Stopped +

+

{generationError}

+
+ ) : ( +
+

+ Preview of generated data. Click 'Save' to download the text file. +

+