From ee308ed2f9c53d6007e584fbd3f8f855f5e088df Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 26 Feb 2026 06:25:23 +0000 Subject: [PATCH] 2 --- frontend/src/pages/observation.tsx | 141 +++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 19 deletions(-) diff --git a/frontend/src/pages/observation.tsx b/frontend/src/pages/observation.tsx index 6315848..c2edc37 100644 --- a/frontend/src/pages/observation.tsx +++ b/frontend/src/pages/observation.tsx @@ -1,23 +1,22 @@ - import React, { useEffect, useRef, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; -import { useRouter } from 'next/router'; import { mdiClose, - mdiCamera, - mdiTarget, - mdiInformationOutline, mdiTelescope, mdiMagnifyPlusOutline, mdiMagnifyMinusOutline, mdiOrbitVariant, mdiFlare, mdiWeatherNight, - mdiCrosshairsGps + mdiCrosshairsGps, + mdiRecord, + mdiStop, + mdiDownload, + mdiAutoFix } from '@mdi/js'; import BaseIcon from '../components/BaseIcon'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { useAppDispatch } from '../stores/hooks'; import { fetch as fetchSkyObjects } from '../stores/sky_objects/sky_objectsSlice'; import LayoutAuthenticated from '../layouts/Authenticated'; @@ -58,7 +57,7 @@ const PRESET_TARGETS = [ id: 'pillars', name: 'Pillars of Creation', type: 'Nebula', - img: 'https://images-assets.nasa.gov/image/as11-40-5874/as11-40-5874~medium.jpg', // Using a generic high-res space image placeholder + img: 'https://images-assets.nasa.gov/image/as11-40-5874/as11-40-5874~medium.jpg', dist: '6,500 ly', temp: '15K' } @@ -71,12 +70,19 @@ const ObservationPage = () => { const [zoom, setZoom] = useState(1); const [selectedTarget, setSelectedTarget] = useState(null); const [isFocusing, setIsFocusing] = useState(false); + const [isSharpnessMax, setIsSharpnessMax] = useState(false); const [telemetry, setTelemetry] = useState({ temp: -233, dist: 1.5, focal: 131, }); + // Recording states + const [isRecording, setIsRecording] = useState(false); + const [mediaRecorder, setMediaRecorder] = useState(null); + const [recordedChunks, setRecordedChunks] = useState([]); + const [videoUrl, setVideoUrl] = useState(null); + const dispatch = useAppDispatch(); useEffect(() => { @@ -86,7 +92,11 @@ const ObservationPage = () => { const startCamera = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ - video: { facingMode: 'environment' } + video: { + facingMode: 'environment', + width: { ideal: 1920 }, + height: { ideal: 1080 } + } }); if (videoRef.current) { videoRef.current.srcObject = stream; @@ -103,6 +113,7 @@ const ObservationPage = () => { const tracks = (videoRef.current.srcObject as MediaStream).getTracks(); tracks.forEach(track => track.stop()); setIsCameraActive(false); + if (isRecording) stopRecording(); } }; @@ -124,7 +135,6 @@ const ObservationPage = () => { setSelectedTarget(target); setIsFocusing(true); - // Animate zoom in let currentZoom = zoom; const targetZoom = 1000000; const interval = setInterval(() => { @@ -139,7 +149,6 @@ const ObservationPage = () => { }, 100); }; - // Simulate telemetry fluctuations useEffect(() => { const interval = setInterval(() => { setTelemetry(prev => ({ @@ -152,10 +161,65 @@ const ObservationPage = () => { }, []); const getFilter = () => { + let base = 'none'; switch (mode) { - case 'ir': return 'contrast(1.2) brightness(1.1) hue-rotate(180deg) saturate(1.5)'; - case 'deep': return 'contrast(1.5) brightness(0.8) saturate(0.5) blur(0.5px)'; - default: return 'none'; + case 'ir': base = 'contrast(1.2) brightness(1.1) hue-rotate(180deg) saturate(1.5)'; break; + case 'deep': base = 'contrast(1.5) brightness(0.8) saturate(0.5) blur(0.5px)'; break; + default: base = 'none'; + } + + if (isSharpnessMax) { + base += ' contrast(1.4) brightness(1.1) saturate(1.2) drop-shadow(0 0 1px white)'; + } + + return base; + }; + + // Recording Logic + const startRecording = () => { + if (!videoRef.current || !videoRef.current.srcObject) return; + + const stream = videoRef.current.srcObject as MediaStream; + const recorder = new MediaRecorder(stream, { + mimeType: 'video/webm' + }); + + const chunks: Blob[] = []; + recorder.ondataavailable = (event) => { + if (event.data.size > 0) { + chunks.push(event.data); + } + }; + + recorder.onstop = () => { + const blob = new Blob(chunks, { type: 'video/webm' }); + const url = URL.createObjectURL(blob); + setVideoUrl(url); + }; + + recorder.start(); + setIsRecording(true); + setMediaRecorder(recorder); + setVideoUrl(null); + }; + + const stopRecording = () => { + if (mediaRecorder && mediaRecorder.state !== 'inactive') { + mediaRecorder.stop(); + setIsRecording(false); + } + }; + + const downloadVideo = () => { + if (videoUrl) { + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = videoUrl; + a.download = `JWST-REC-${new Date().getTime()}.webm`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(videoUrl); + setVideoUrl(null); } }; @@ -202,7 +266,8 @@ const ObservationPage = () => { style={{ filter: getFilter(), transform: `scale(${1 + Math.log10(zoom)})`, - transition: 'transform 0.5s ease-out, opacity 1.5s ease-in-out' + transition: 'transform 0.5s ease-out, opacity 1.5s ease-in-out', + imageRendering: isSharpnessMax ? 'crisp-edges' : 'auto' }} /> @@ -213,7 +278,8 @@ const ObservationPage = () => { style={{ backgroundImage: `url(${selectedTarget.img})`, transform: `scale(${1 + (Math.log10(zoom) - 3) / 20})`, - filter: getFilter() + filter: getFilter(), + imageRendering: isSharpnessMax ? 'crisp-edges' : 'auto' }} /> )} @@ -251,9 +317,9 @@ const ObservationPage = () => {
Mission Control: NASA-ESA-CSA
-
+
- {isFocusing ? 'Acquiring Target' : 'Observation Stable'} + {isRecording ? 'REC ACTIVE' : (isFocusing ? 'Acquiring Target' : 'Observation Stable')}
@@ -277,6 +343,43 @@ const ObservationPage = () => {
+ {/* Recording System */} +
+ {!isRecording ? ( + + ) : ( + + )} + {videoUrl && ( + + )} +
+ +