This commit is contained in:
Flatlogic Bot 2026-02-26 06:25:23 +00:00
parent c8a5aca21b
commit ee308ed2f9

View File

@ -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<any>(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<MediaRecorder | null>(null);
const [recordedChunks, setRecordedChunks] = useState<Blob[]>([]);
const [videoUrl, setVideoUrl] = useState<string | null>(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 = () => {
<div className="bg-black/80 border-l-4 border-[#E3B341] p-4 backdrop-blur-lg">
<div className="text-[10px] text-gray-400 uppercase tracking-tighter">Mission Control: NASA-ESA-CSA</div>
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-green-500 rounded-full animate-ping"></div>
<div className={`w-2 h-2 rounded-full ${isRecording ? 'bg-red-500 animate-pulse' : 'bg-green-500 animate-ping'}`}></div>
<div className="font-bold text-[#E3B341] tracking-widest uppercase">
{isFocusing ? 'Acquiring Target' : 'Observation Stable'}
{isRecording ? 'REC ACTIVE' : (isFocusing ? 'Acquiring Target' : 'Observation Stable')}
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-x-6 text-[10px]">
@ -277,6 +343,43 @@ const ObservationPage = () => {
</div>
<div className="flex space-x-2">
{/* Recording System */}
<div className="flex space-x-1 bg-black/60 p-1 border border-white/10">
{!isRecording ? (
<button
onClick={startRecording}
className="p-3 bg-black/80 border border-white/20 hover:bg-red-600 transition-all text-white"
title="Start Recording"
>
<BaseIcon path={mdiRecord} size={20} className="text-red-500" />
</button>
) : (
<button
onClick={stopRecording}
className="p-3 bg-red-600 border border-white/20 transition-all text-white animate-pulse"
title="Stop Recording"
>
<BaseIcon path={mdiStop} size={20} />
</button>
)}
{videoUrl && (
<button
onClick={downloadVideo}
className="p-3 bg-blue-600 border border-white/20 transition-all text-white"
title="Download Video"
>
<BaseIcon path={mdiDownload} size={20} />
</button>
)}
</div>
<button
onClick={() => setIsSharpnessMax(!isSharpnessMax)}
className={`p-3 bg-black/80 border transition-all ${isSharpnessMax ? 'border-[#00F2FF] text-[#00F2FF] shadow-[0_0_15px_rgba(0,242,255,0.5)]' : 'border-[#E3B341]/40 text-[#E3B341]'}`}
title="Maximum Sharpness"
>
<BaseIcon path={mdiAutoFix} size={20} />
</button>
<button
onClick={() => { setSelectedTarget(null); setZoom(1); }}
className="p-3 bg-black/80 border border-[#E3B341]/40 hover:bg-[#E3B341]/20 transition-all text-[#E3B341]"
@ -403,4 +506,4 @@ ObservationPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default ObservationPage;
export default ObservationPage;