Juanfa commited on
Commit
5f81703
·
verified ·
1 Parent(s): 80cd2db

Upload pages/index.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. pages/index.js +156 -0
pages/index.js ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+ import Head from 'next/head';
3
+ import Header from '../components/Header';
4
+ import GaussianSplatViewer from '../components/GaussianSplatViewer';
5
+ import WorldCard from '../components/WorldCard';
6
+ import Controls from '../components/Controls';
7
+ import MetadataPanel from '../components/MetadataPanel';
8
+ import { Loader2, Grid3X3, List } from 'lucide-react';
9
+
10
+ export default function Home() {
11
+ const [worlds, setWorlds] = useState([]);
12
+ const [selectedWorld, setSelectedWorld] = useState(null);
13
+ const [loading, setLoading] = useState(true);
14
+ const [viewMode, setViewMode] = useState('grid');
15
+
16
+ useEffect(() => {
17
+ fetchWorlds();
18
+ }, []);
19
+
20
+ const fetchWorlds = async () => {
21
+ try {
22
+ const response = await fetch('/api/worlds');
23
+ const data = await response.json();
24
+ setWorlds(data);
25
+ if (data.length > 0) {
26
+ setSelectedWorld(data[0]);
27
+ }
28
+ } catch (error) {
29
+ console.error('Failed to fetch worlds:', error);
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ };
34
+
35
+ const handleReset = () => {
36
+ // Trigger camera reset through ref or state
37
+ window.dispatchEvent(new CustomEvent('reset-camera'));
38
+ };
39
+
40
+ const handleZoomIn = () => {
41
+ window.dispatchEvent(new CustomEvent('zoom-camera', { detail: 0.8 }));
42
+ };
43
+
44
+ const handleZoomOut = () => {
45
+ window.dispatchEvent(new CustomEvent('zoom-camera', { detail: 1.2 }));
46
+ };
47
+
48
+ const handleFullscreen = () => {
49
+ if (!document.fullscreenElement) {
50
+ document.documentElement.requestFullscreen();
51
+ } else {
52
+ document.exitFullscreen();
53
+ }
54
+ };
55
+
56
+ if (loading) {
57
+ return (
58
+ <div className="min-h-screen bg-slate-950 flex items-center justify-center">
59
+ <div className="text-center">
60
+ <Loader2 className="w-12 h-12 text-indigo-500 animate-spin mx-auto mb-4" />
61
+ <p className="text-slate-400">Loading worlds...</p>
62
+ </div>
63
+ </div>
64
+ );
65
+ }
66
+
67
+ return (
68
+ <div className="min-h-screen bg-slate-950">
69
+ <Head>
70
+ <title>Gaussian Splat Viewer | 360° Worlds</title>
71
+ <meta name="description" content="View 3D Gaussian Splatting captures from 360° cameras" />
72
+ </Head>
73
+
74
+ <Header />
75
+
76
+ <main className="pt-16 h-screen flex flex-col lg:flex-row overflow-hidden">
77
+ {/* Sidebar - World List */}
78
+ <aside className="w-full lg:w-80 bg-slate-900/50 border-r border-slate-800 flex flex-col">
79
+ <div className="p-4 border-b border-slate-800">
80
+ <div className="flex items-center justify-between mb-4">
81
+ <h2 className="font-semibold text-slate-200">Captured Worlds</h2>
82
+ <div className="flex gap-1">
83
+ <button
84
+ onClick={() => setViewMode('grid')}
85
+ className={`p-2 rounded ${viewMode === 'grid' ? 'bg-slate-700 text-white' : 'text-slate-400 hover:text-white'}`}
86
+ >
87
+ <Grid3X3 className="w-4 h-4" />
88
+ </button>
89
+ <button
90
+ onClick={() => setViewMode('list')}
91
+ className={`p-2 rounded ${viewMode === 'list' ? 'bg-slate-700 text-white' : 'text-slate-400 hover:text-white'}`}
92
+ >
93
+ <List className="w-4 h-4" />
94
+ </button>
95
+ </div>
96
+ </div>
97
+ <p className="text-xs text-slate-500">
98
+ {worlds.length} worlds available • 360° Gaussian Splatting
99
+ </p>
100
+ </div>
101
+
102
+ <div className="flex-1 overflow-y-auto p-4 space-y-3 scrollbar-hide">
103
+ {worlds.map((world) => (
104
+ <WorldCard
105
+ key={world.id}
106
+ world={world}
107
+ isSelected={selectedWorld?.id === world.id}
108
+ onClick={() => {
109
+ setSelectedWorld(world);
110
+ setLoading(true);
111
+ setTimeout(() => setLoading(false), 800);
112
+
113
+ />
114
+ ))}
115
+ </div>
116
+
117
+ <div className="p-4 border-t border-slate-800">
118
+ <MetadataPanel worldData={selectedWorld} />
119
+ </div>
120
+ </aside>
121
+
122
+ {/* Main Viewer Area */}
123
+ <section className="flex-1 relative bg-slate-950">
124
+ <GaussianSplatViewer
125
+ worldData={selectedWorld}
126
+ isLoading={loading}
127
+ />
128
+
129
+ <Controls
130
+ onReset={handleReset}
131
+ onZoomIn={handleZoomIn}
132
+ onZoomOut={handleZoomOut}
133
+ onFullscreen={handleFullscreen}
134
+ worldData={selectedWorld}
135
+ />
136
+
137
+ {/* World Info Overlay */}
138
+ {selectedWorld && !loading && (
139
+ <div className="absolute top-4 left-4 glass-panel rounded-xl p-4 max-w-sm">
140
+ <h2 className="text-lg font-bold text-white mb-1">{selectedWorld.name}</h2>
141
+ <p className="text-sm text-slate-300 mb-2">{selectedWorld.location}</p>
142
+ <div className="flex items-center gap-2 text-xs text-slate-400">
143
+ <span className="px-2 py-1 bg-indigo-600/30 rounded text-indigo-300">
144
+ {selectedWorld.pointCount?.toLocaleString()} points
145
+ </span>
146
+ <span className="px-2 py-1 bg-slate-700 rounded">
147
+ {selectedWorld.cameraModel}
148
+ </span>
149
+ </div>
150
+ </div>
151
+ )}
152
+ </section>
153
+ </main>
154
+ </div>
155
+ );
156
+ }