266 lines
15 KiB
JavaScript
266 lines
15 KiB
JavaScript
// src/App.jsx
|
|
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
|
import GlobeCanvas from './components/GlobeCanvas';
|
|
import NewsPanel from './components/NewsPanel';
|
|
import StatsPanel from './components/StatsPanel';
|
|
import GlobalNav from './components/GlobalNav';
|
|
import PopUpDetail from './components/PopUpDetail';
|
|
import LocalMap from './components/LocalMap'; // ต้องสร้างไฟล์นี้ด้วย (จากคำตอบก่อนหน้า)
|
|
|
|
function App() {
|
|
const [isNewsMode, setIsNewsMode] = useState(true);
|
|
const [activePanel, setActivePanel] = useState('world_news');
|
|
const [selectedPoint, setSelectedPoint] = useState(null);
|
|
const [isPanelVisible, setIsPanelVisible] = useState(true);
|
|
|
|
// State ใหม่สำหรับ Local View
|
|
const [isLocalView, setIsLocalView] = useState(false);
|
|
const [localViewCenter, setLocalViewCenter] = useState({ lat: 0, lon: 0 });
|
|
const [localViewZoom, setLocalViewZoom] = useState(10); // ซูมเริ่มต้นสำหรับ Leaflet
|
|
|
|
// Data สำหรับ Panel ต่างๆ - เหมือนเดิม
|
|
const panelData = {
|
|
'world_news': { title: 'WORLD NEWS', type: 'news', content: 'Global events unfold across continents, shaping economies and societies.', youtubeVideoId: 'dQw4w9WgXcQ' },
|
|
'euro_news': { title: 'EURO NEWS', type: 'news', content: 'Key developments in European politics and markets are impacting global trends.', youtubeVideoId: '1' },
|
|
'america_news': { title: 'AMERICA NEWS', type: 'news', content: 'Breaking news from North and South America, focusing on economic shifts.', youtubeVideoId: '6' },
|
|
'china_japan_news': { title: 'CHINA / JAPAN NEWS', type: 'news', content: 'Latest economic and cultural updates from China and Japan, shaping regional dynamics.', youtubeVideoId: '11' },
|
|
'asia_news': { title: 'ASIA NEWS', type: 'news', content: 'Emerging tech and market news from Asia, driving innovation forward.', youtubeVideoId: 'k4F9c40tWnQ' },
|
|
'market_review': {
|
|
title: 'MARKET REVIEW', type: 'stats',
|
|
data: [
|
|
{ label: 'DOW JONES', value: '38,712', trend: 'up' },
|
|
{ label: 'S&P 500', value: '5,472', trend: 'down' },
|
|
{ label: 'NASDAQ', value: '17,857', trend: 'up' },
|
|
{ label: 'NIKKEI', value: '38,622', trend: 'same' },
|
|
{ label: 'FTSE 100', value: '8,208', trend: 'up' },
|
|
],
|
|
youtubeVideoId: 'F90M73o9y5c'
|
|
},
|
|
'next_dollar_currencies': {
|
|
title: 'NEXT DOLLAR CURRENCIES', type: 'stats',
|
|
data: [],
|
|
youtubeVideoId: '041pM7vYwYI'
|
|
},
|
|
'global_population': {
|
|
title: 'GLOBAL POPULATION', type: 'stats',
|
|
data: [
|
|
{ label: 'Current', value: '7.42 Billion People', trend: 'up' },
|
|
{ label: 'Growth Rate', value: '1.09%', trend: 'same' },
|
|
{ label: 'Births Today', value: '385,000', trend: 'up' },
|
|
{ label: 'Deaths Today', value: '160,000', trend: 'up' },
|
|
]
|
|
},
|
|
'energy_consumption': {
|
|
title: 'ENERGY CONSUMPTION', type: 'stats',
|
|
data: [
|
|
{ label: 'Oil Price (USD)', value: '80.50', trend: 'down' },
|
|
{ label: 'Natural Gas', value: '2.80 MMBtu', trend: 'up' },
|
|
{ label: 'Renewable Share', value: '25.3%', trend: 'up' },
|
|
{ label: 'Coal Consumption', value: 'Down 5%', trend: 'down' },
|
|
]
|
|
},
|
|
'russia_news_stats': {
|
|
title: 'RUSSIA ECONOMIC NEWS', type: 'stats',
|
|
data: [
|
|
{ label: 'BIX Index', value: '124.5', trend: 'up' },
|
|
{ label: 'MICEX Index', value: '3,200', trend: 'up' },
|
|
{ label: 'Oil Production', value: '10.5 M bbl/d', trend: 'same' },
|
|
],
|
|
youtubeVideoId: 'your-russia-video-id'
|
|
},
|
|
};
|
|
|
|
const globePoints = [
|
|
{ id: 'us', name: 'USA', lat: 39.8283, lon: -98.5795, type: 'news', panel: 'america_news', news: ['US stock market surges.', 'Tech innovation continues.'] },
|
|
{ id: 'uk', name: 'UK', lat: 51.5074, lon: -0.1278, type: 'news', panel: 'euro_news', news: ['Brexit impact on trade.', 'New UK climate policies.'] },
|
|
{ id: 'germany', name: 'Germany', lat: 51.1657, lon: 10.4515, type: 'news', panel: 'euro_news', news: ['German economy outlook.', 'Renewable energy growth.'] },
|
|
{ id: 'china', name: 'China', lat: 35.8617, lon: 104.1954, type: 'news', panel: 'china_japan_news', news: ['China trade balance.', 'Digital currency trials.'] },
|
|
{ id: 'japan', name: 'Japan', lat: 36.2048, lon: 138.2529, type: 'news', panel: 'china_japan_news', news: ['Japan tech advancements.', 'Olympic preparations.'] },
|
|
{ id: 'russia', name: 'Russia', lat: 61.5240, lon: 105.3188, type: 'stats', panel: 'russia_news_stats', news: ['Russia energy exports.', 'Geopolitical developments.'] },
|
|
{ id: 'brazil', name: 'Brazil', lat: -14.2350, lon: -51.9253, type: 'stats', panel: 'next_dollar_currencies', news: ['Brazilian Real fluctuation.', 'Agricultural exports outlook.'] },
|
|
{ id: 'india', name: 'India', lat: 20.5937, lon: 78.9629, type: 'stats', panel: 'market_review', news: ['Indian market growth.', 'Startup ecosystem booming.'] },
|
|
{ id: 'australia', name: 'Australia', lat: -25.2744, lon: 133.7751, type: 'news', panel: 'world_news', news: ['Australia bushfire recovery.', 'Mining sector update.'] },
|
|
];
|
|
|
|
const globePointsWithRussia = useMemo(() => {
|
|
const existingRussiaPoint = globePoints.find(p => p.id === 'russia');
|
|
if (!existingRussiaPoint) {
|
|
return [...globePoints, { id: 'russia', name: 'Russia', lat: 61.5240, lon: 105.3188, type: 'stats', panel: 'russia_news_stats', news: ['Russia energy exports.', 'Geopolitical developments.'] }];
|
|
}
|
|
return globePoints;
|
|
}, [globePoints]);
|
|
|
|
const globeConnections = [
|
|
{ id: 'us-uk', startLat: 39.8283, startLon: -98.5795, endLat: 51.5074, endLon: -0.1278 },
|
|
{ id: 'uk-germany', startLat: 51.5074, startLon: -0.1278, endLat: 51.1657, endLon: 10.4515 },
|
|
{ id: 'china-japan', startLat: 35.8617, startLon: 104.1954, endLat: 36.2048, endLon: 138.2529 },
|
|
{ id: 'us-china', startLat: 39.8283, startLon: -98.5795, endLat: 35.8617, endLon: 104.1954 },
|
|
];
|
|
|
|
const panelLocations = useMemo(() => {
|
|
const locations = {};
|
|
for (const panelId in panelData) {
|
|
locations[panelId] = { ...panelData[panelId] };
|
|
}
|
|
|
|
globePointsWithRussia.forEach(point => {
|
|
if (point.panel && locations[point.panel]) {
|
|
locations[point.panel].lat = point.lat;
|
|
locations[point.panel].lon = point.lon;
|
|
}
|
|
});
|
|
|
|
if (!locations['world_news'].lat && !locations['world_news'].lon) { locations['world_news'].lat = -25.2744; locations['world_news'].lon = 133.7751; }
|
|
if (!locations['asia_news'].lat && !locations['asia_news'].lon) { locations['asia_news'].lat = 30; locations['asia_news'].lon = 90; }
|
|
if (!locations['euro_news'].lat && !locations['euro_news'].lon) { locations['euro_news'].lat = 50; locations['euro_news'].lon = 10; }
|
|
if (!locations['america_news'].lat && !locations['america_news'].lon) { locations['america_news'].lat = 35; locations['america_news'].lon = -100; }
|
|
if (!locations['global_population'].lat && !locations['global_population'].lon) { locations['global_population'].lat = 0; locations['global_population'].lon = 0; }
|
|
if (!locations['energy_consumption'].lat && !locations['energy_consumption'].lon) { locations['energy_consumption'].lat = 0; locations['energy_consumption'].lon = 0; }
|
|
if (!locations['russia_news_stats'].lat && !locations['russia_news_stats'].lon) { locations['russia_news_stats'].lat = 61.5240; locations['russia_news_stats'].lon = 105.3188; }
|
|
|
|
return locations;
|
|
}, [panelData, globePointsWithRussia]);
|
|
|
|
const handlePointClick = useCallback((point) => {
|
|
setSelectedPoint(point);
|
|
if (point.panel) {
|
|
setActivePanel(point.panel);
|
|
if (point.type === 'news') setIsNewsMode(true);
|
|
else if (point.type === 'stats') setIsNewsMode(false);
|
|
setIsPanelVisible(true);
|
|
// ไม่ต้องเปลี่ยนเป็น Local View ที่นี่ ให้ GlobeCanvas จัดการเมื่อซูมเข้า
|
|
}
|
|
}, []);
|
|
|
|
const closePopUp = useCallback(() => {
|
|
setSelectedPoint(null);
|
|
}, []);
|
|
|
|
// Callback เมื่อ GlobeCanvas ตรวจพบการซูมเข้าสู่ Local View
|
|
const handleZoomToLocal = useCallback(({ lat, lon }) => {
|
|
if (!isLocalView) {
|
|
setIsLocalView(true);
|
|
setLocalViewCenter({ lat, lon });
|
|
setLocalViewZoom(10);
|
|
console.log(`App.jsx: Switching to Local View at Lat: ${lat}, Lon: ${lon}`);
|
|
}
|
|
}, [isLocalView]);
|
|
|
|
const handleExitLocalView = useCallback(() => {
|
|
if (isLocalView) { // ตรวจสอบเพื่อไม่ให้เปลี่ยนโหมดซ้ำซ้อน
|
|
setIsLocalView(false);
|
|
console.log("App.jsx: Exiting Local View");
|
|
}
|
|
}, [isLocalView]);
|
|
|
|
|
|
const currentPanel = panelData[activePanel];
|
|
|
|
const [currentTime, setCurrentTime] = useState('');
|
|
const [population, setPopulation] = useState('7.42 Billion People');
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
const now = new Date();
|
|
setCurrentTime(now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }));
|
|
}, 1000);
|
|
return () => clearInterval(timer);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="relative flex flex-col md:flex-row h-screen w-screen bg-black font-mono overflow-hidden">
|
|
|
|
{/* Conditional Rendering: แสดง GlobeCanvas หรือ LocalMap */}
|
|
{!isLocalView ? (
|
|
<GlobeCanvas
|
|
activePanel={activePanel}
|
|
isNewsMode={isNewsMode}
|
|
panelLocations={panelLocations}
|
|
globePoints={globePointsWithRussia}
|
|
globeConnections={globeConnections}
|
|
onPointClick={handlePointClick}
|
|
onZoomToLocal={handleZoomToLocal} // ส่ง callback ไปยัง GlobeCanvas
|
|
isLocalView={isLocalView} // ส่ง state ไปยัง GlobeCanvas เพื่อควบคุม OrbitControls
|
|
/>
|
|
) : (
|
|
<div className="absolute inset-0 z-0">
|
|
<LocalMap
|
|
center={localViewCenter}
|
|
zoom={localViewZoom}
|
|
points={globePointsWithRussia} // ส่งจุดที่ต้องการแสดงบนแผนที่
|
|
onPointClick={handlePointClick} // หากต้องการให้คลิกจุดบน Leaflet แล้วแสดง PopUp
|
|
/>
|
|
{/* ปุ่มสำหรับออกจาก Local View */}
|
|
<button
|
|
className="btn btn-secondary absolute top-4 left-1/2 -translate-x-1/2 z-30 pointer-events-auto"
|
|
onClick={handleExitLocalView}
|
|
>
|
|
Exit Local View
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
|
|
{/* UI Elements Overlay (ด้านหน้า) - ยังคงอยู่เหนือ Globe/Map */}
|
|
<div className="absolute inset-0 flex flex-col md:flex-row justify-between items-center p-8 z-20 pointer-events-none">
|
|
{/* GlobalNav - อยู่ทางซ้ายมือของโลก */}
|
|
<div className="flex-none p-4 md:p-0 pointer-events-auto w-full md:w-auto self-start md:self-center">
|
|
<GlobalNav
|
|
activePanel={activePanel}
|
|
setActivePanel={setActivePanel}
|
|
isNewsMode={isNewsMode}
|
|
setIsNewsMode={setIsNewsMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* ส่วนกลางที่ว่างอยู่สำหรับโลก (ไม่ได้มี div แยก) */}
|
|
|
|
{/* NewsPanel และ StatsPanel - อยู่ทางขวามือของโลก */}
|
|
{currentPanel && (
|
|
<div className="flex-none p-4 md:p-0 pointer-events-auto w-full md:w-auto self-end md:self-center">
|
|
<div className={`
|
|
transition-all duration-500 ease-in-out transform
|
|
${isPanelVisible ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-full pointer-events-none'}
|
|
${currentPanel.type === 'news' ? 'md:ml-auto' : ''}
|
|
`}>
|
|
{isNewsMode && currentPanel.type === 'news' && (
|
|
<NewsPanel
|
|
title={currentPanel.title}
|
|
content={currentPanel.content}
|
|
youtubeVideoId={currentPanel.youtubeVideoId}
|
|
isActive={true}
|
|
/>
|
|
)}
|
|
{!isNewsMode && currentPanel.type === 'stats' && (
|
|
<StatsPanel
|
|
title={currentPanel.title}
|
|
data={currentPanel.data}
|
|
youtubeVideoId={currentPanel.youtubeVideoId}
|
|
isActive={true}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Top Right - Time Display & Population */}
|
|
<div className="absolute top-8 right-8 text-right text-lg z-10 pointer-events-auto text-white" style={{ textShadow: '0px 0px 5px rgba(0, 0, 0, 0.5)' }}>
|
|
{/* เปลี่ยน text-base-content เป็น text-white เพื่อสีที่สว่างขึ้น */}
|
|
{/* เพิ่ม text-shadow เพื่อให้ข้อความมีมิติและอ่านง่ายขึ้นบนพื้นหลังมืด */}
|
|
<div className="font-bold text-3xl">{currentTime}</div>
|
|
<div>{population}</div>
|
|
</div>
|
|
|
|
{/* Pop-up Detail (ถ้ามี) */}
|
|
{selectedPoint && (
|
|
<PopUpDetail
|
|
point={selectedPoint}
|
|
onClose={closePopUp}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App; |