-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
230 lines (203 loc) · 8.35 KB
/
App.tsx
File metadata and controls
230 lines (203 loc) · 8.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import React, {useRef, useState, useEffect} from 'react';
import {AuthoringComponent} from "./components/AuthoringComponent";
import {EngineType} from "./components/engineViews/EngineType";
import {RenderIf} from "./components/RenderIf";
import {LoggingEngineComponent} from "./components/engineViews/LoggingEngineComponent";
import {BabylonEngineComponent} from "./components/engineViews/BabylonEngineComponent";
import {ThreeEngineComponent} from "./components/engineViews/ThreeEngineComponent";
import {Tab, Tabs} from "react-bootstrap";
import {Spacer} from "./components/Spacer";
import { InteractivityGraphProvider } from './InteractivityGraphContext';
import { SampleSidebar } from './components/SampleSidebar';
// Storage key for persisting the engine type
const ENGINE_TYPE_STORAGE_KEY = 'interactivity-graph-engine-type';
export const App = () => {
const [engineType, setEngineType] = useState<EngineType>(EngineType.BABYLON);
const [modelUrl, setModelUrl] = useState<string | null>(null);
// Load stored engine type on initial render and check URL parameters
useEffect(() => {
// Parse URL parameters
const params = new URLSearchParams(window.location.search);
const engineParam = params.get('engine');
const modelParam = params.get('model');
// Set engine type from URL parameter or localStorage
if (engineParam) {
switch (engineParam.toLowerCase()) {
case 'logging':
setEngineType(EngineType.LOGGING);
break;
case 'three':
setEngineType(EngineType.THREE);
break;
case 'babylon':
setEngineType(EngineType.BABYLON);
break;
default:
// Load from localStorage if URL param is invalid
const storedEngineType = localStorage.getItem(ENGINE_TYPE_STORAGE_KEY);
if (storedEngineType && Object.values(EngineType).includes(storedEngineType as EngineType)) {
setEngineType(storedEngineType as EngineType);
}
}
} else {
// No URL param, load from localStorage
const storedEngineType = localStorage.getItem(ENGINE_TYPE_STORAGE_KEY);
if (storedEngineType && Object.values(EngineType).includes(storedEngineType as EngineType)) {
setEngineType(storedEngineType as EngineType);
}
}
// Set model URL from URL parameter
if (modelParam) {
setModelUrl(modelParam);
}
}, []);
// Handle browser back/forward navigation
useEffect(() => {
const handlePopState = () => {
// Get the model URL from the URL parameters
const params = new URLSearchParams(window.location.search);
const modelParam = params.get('model');
const engineParam = params.get('engine');
// Update the model URL state if it exists in the URL
if (modelParam) {
setModelUrl(modelParam);
}
// Update engine type if needed
if (engineParam) {
switch (engineParam.toLowerCase()) {
case 'logging':
setEngineType(EngineType.LOGGING);
break;
case 'three':
setEngineType(EngineType.THREE);
break;
case 'babylon':
setEngineType(EngineType.BABYLON);
break;
}
}
};
// Add event listener for popstate
window.addEventListener('popstate', handlePopState);
// Clean up the event listener when component unmounts
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, []);
// Save engine type when it changes
const handleEngineTypeChange = (type: EngineType) => {
setEngineType(type);
localStorage.setItem(ENGINE_TYPE_STORAGE_KEY, type);
// Update URL with engine type
const params = new URLSearchParams(window.location.search);
params.set('engine', type.toLowerCase());
// Keep model parameter if it exists
if (modelUrl) {
params.set('model', modelUrl);
}
window.history.pushState({ engineType: type, modelUrl }, '', `${window.location.pathname}?${params}`);
};
const handleModelUrlChange = (url: string) => {
setModelUrl(url);
};
useEffect(() => {
if (modelUrl) {
const params = new URLSearchParams(window.location.search);
params.set('model', modelUrl);
// set title based on model name
const modelName = modelUrl.split('/').pop()?.split('.').shift();
if (modelName) {
document.title = `${modelName}`;
} else {
document.title = 'glTF Interactivity';
}
// only push state if modelUrl is different from current URL parameter
const currentModelParam = new URLSearchParams(window.location.search).get('model');
if (currentModelParam !== modelUrl) {
// Update the URL without reloading the page
window.history.pushState({ modelUrl }, '', `${window.location.pathname}?${params}`);
}
}
}, [modelUrl]);
return (
<InteractivityGraphProvider>
<div style={{width: "100vw", height: "100vh"}}>
<EngineSelector setEngineType={handleEngineTypeChange} currentEngineType={engineType} />
<SampleSidebar onSelectModel={handleModelUrlChange} />
<Spacer width={0} height={32}/>
<RenderIf shouldShow={engineType === EngineType.LOGGING}>
<LoggingEngineComponent modelUrl={modelUrl} />
</RenderIf>
<RenderIf shouldShow={engineType === EngineType.BABYLON}>
<BabylonEngineComponent modelUrl={modelUrl} />
</RenderIf>
<RenderIf shouldShow={engineType === EngineType.THREE}>
<ThreeEngineComponent modelUrl={modelUrl} />
</RenderIf>
<AuthoringComponent/>
</div>
</InteractivityGraphProvider>
);
}
interface EngineSelectorProps {
setEngineType: (engine: EngineType) => void;
currentEngineType: EngineType;
}
export const EngineSelector: React.FC<EngineSelectorProps> = ({ setEngineType, currentEngineType }) => {
// Initialize the activeKey based on the engineType prop
const getInitialTabKey = () => {
switch (currentEngineType) {
case EngineType.LOGGING:
return '1';
case EngineType.BABYLON:
return '2';
case EngineType.THREE:
return '3';
default:
return '2'; // Default to Babylon
}
};
const [activeKey, setActiveKey] = useState(getInitialTabKey());
// Update tab key when engineType changes
useEffect(() => {
setActiveKey(getInitialTabKey());
}, [currentEngineType]);
const handleEngineChange = (key: string | null) => {
if (key) {
let engine;
switch (key) {
case '1':
engine = EngineType.LOGGING;
break;
case '2':
engine = EngineType.BABYLON;
break;
case '3':
engine = EngineType.THREE;
break;
default:
throw Error("Invalid Selection")
}
setActiveKey(key);
setEngineType(engine);
}
};
return (
<div style={{width: "90vw", margin: "0 auto", textAlign: "center", marginTop: 32}}>
<h2>glTF Interactivity Editor and Viewer</h2>
<p style={{marginBottom: "0"}}>This web app allows interacting with, graph inspection and authoring of glTF files using the <a href="https://github.com/KhronosGroup/glTF/blob/interactivity/extensions/2.0/Khronos/KHR_interactivity/Specification.adoc" target="_blank">KHR_interactivity</a> extension.</p>
<p style={{marginBottom: "0"}}>You can load samples and test assets and inspect their graphs, or create your own files with the experimental graph UI.</p>
<p>Maintained by <a href="https://needle.tools">Needle</a>. Forked from Khronos' <a href="https://github.com/KhronosGroup/glTF-InteractivityGraph-AuthoringTool">Interactivity Graph Authoring Tool</a>.</p>
<div data-testid={"engine-selector"}>
<Tabs
activeKey={activeKey}
onSelect={handleEngineChange}
>
<Tab title={"Babylon Engine"} eventKey={2}/>
<Tab title={"Three.js (experimental)"} eventKey={3}/>
<Tab title={"Logging Engine (for development)"} eventKey={1}/>
</Tabs>
</div>
</div>
);
}