MikaFil commited on
Commit
107e90f
·
verified ·
1 Parent(s): a2e7f03

Create interface_pr_env.js

Browse files
deplacement_dans_env/interface_pr_env.js ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // interface.js
2
+ // ==============================
3
+
4
+ const currentScriptTag = document.currentScript;
5
+
6
+ (async function () {
7
+ // 1) Localiser la balise <script> et lire data-config
8
+ let scriptTag = currentScriptTag;
9
+
10
+ if (!scriptTag) {
11
+ const scripts = document.getElementsByTagName('script');
12
+ for (let i = 0; i < scripts.length; i++) {
13
+ if (
14
+ scripts[i].src.includes('interface.js') &&
15
+ scripts[i].hasAttribute('data-config')
16
+ ) {
17
+ scriptTag = scripts[i];
18
+ break;
19
+ }
20
+ }
21
+ if (!scriptTag && scripts.length > 0) {
22
+ scriptTag = scripts[scripts.length - 1];
23
+ }
24
+ }
25
+
26
+ const configUrl = scriptTag.getAttribute('data-config');
27
+ let config = {};
28
+
29
+ if (configUrl) {
30
+ try {
31
+ const response = await fetch(configUrl);
32
+ config = await response.json();
33
+ } catch (error) {
34
+ return;
35
+ }
36
+ } else {
37
+ return;
38
+ }
39
+
40
+ // 2) CSS optionnelle
41
+ if (config.css_url) {
42
+ const linkEl = document.createElement('link');
43
+ linkEl.rel = 'stylesheet';
44
+ linkEl.href = config.css_url;
45
+ document.head.appendChild(linkEl);
46
+ }
47
+
48
+ // 3) ID d’instance
49
+ const instanceId = Math.random().toString(36).substr(2, 8);
50
+
51
+ // 4) Aspect ratio
52
+ let aspectPercent = '100%';
53
+ if (config.aspect) {
54
+ if (config.aspect.includes(':')) {
55
+ const parts = config.aspect.split(':');
56
+ const w = parseFloat(parts[0]);
57
+ const h = parseFloat(parts[1]);
58
+ if (!isNaN(w) && !isNaN(h) && w > 0) {
59
+ aspectPercent = (h / w) * 100 + '%';
60
+ }
61
+ } else {
62
+ const aspectValue = parseFloat(config.aspect);
63
+ if (!isNaN(aspectValue) && aspectValue > 0) {
64
+ aspectPercent = 100 / aspectValue + '%';
65
+ }
66
+ }
67
+ } else {
68
+ const parentContainer = scriptTag.parentNode;
69
+ const containerWidth = parentContainer.offsetWidth;
70
+ const containerHeight = parentContainer.offsetHeight;
71
+ if (containerWidth > 0 && containerHeight > 0) {
72
+ aspectPercent = (containerHeight / containerWidth) * 100 + '%';
73
+ }
74
+ }
75
+
76
+ // 5) Conteneur widget
77
+ const widgetContainer = document.createElement('div');
78
+ widgetContainer.id = 'ply-widget-container-' + instanceId;
79
+ widgetContainer.classList.add('ply-widget-container');
80
+ widgetContainer.style.height = '0';
81
+ widgetContainer.style.paddingBottom = aspectPercent;
82
+ widgetContainer.setAttribute('data-original-aspect', aspectPercent);
83
+
84
+ const tooltipsButtonHTML = config.tooltips_url
85
+ ? `<button id="tooltips-toggle-${instanceId}" class="widget-button tooltips-toggle">⦿</button>`
86
+ : '';
87
+
88
+ // HTML du widget (IDs spécifiques à l’instance)
89
+ widgetContainer.innerHTML = `
90
+ <div id="viewer-container-${instanceId}" class="viewer-container">
91
+ <div id="progress-dialog-${instanceId}" class="progress-dialog">
92
+ <progress id="progress-indicator-${instanceId}" max="100" value="0"></progress>
93
+ </div>
94
+ <button id="fullscreen-toggle-${instanceId}" class="widget-button fullscreen-toggle">⇱</button>
95
+ <button id="help-toggle-${instanceId}" class="widget-button help-toggle">?</button>
96
+ <button id="reset-camera-btn-${instanceId}" class="widget-button reset-camera-btn">
97
+ <span class="reset-icon">⟲</span>
98
+ </button>
99
+ ${tooltipsButtonHTML}
100
+ <div id="menu-content-${instanceId}" class="menu-content">
101
+ <span id="help-close-${instanceId}" class="help-close">×</span>
102
+ <div class="help-text"></div>
103
+ </div>
104
+ </div>
105
+
106
+ <div id="tooltip-panel-${instanceId}" class="tooltip-panel" style="display: none;">
107
+ <div class="tooltip-content">
108
+ <span id="tooltip-close-${instanceId}" class="tooltip-close">×</span>
109
+ <div id="tooltip-text-${instanceId}" class="tooltip-text"></div>
110
+ <img id="tooltip-image-${instanceId}" class="tooltip-image" src="" alt="" style="display: none;" />
111
+ </div>
112
+ </div>
113
+ `;
114
+
115
+ // Insérer dans le DOM
116
+ scriptTag.parentNode.appendChild(widgetContainer);
117
+
118
+ // 6) Références DOM de l’instance
119
+ const viewerContainerElem = document.getElementById('viewer-container-' + instanceId);
120
+ const fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
121
+ const helpToggle = document.getElementById('help-toggle-' + instanceId);
122
+ const helpCloseBtn = document.getElementById('help-close-' + instanceId);
123
+ const resetCameraBtn = document.getElementById('reset-camera-btn-' + instanceId);
124
+ const tooltipsToggleBtn = document.getElementById('tooltips-toggle-' + instanceId);
125
+ const menuContent = document.getElementById('menu-content-' + instanceId);
126
+ const helpTextDiv = menuContent.querySelector('.help-text');
127
+
128
+ const tooltipPanel = document.getElementById('tooltip-panel-' + instanceId);
129
+ const tooltipTextDiv = document.getElementById('tooltip-text-' + instanceId);
130
+ const tooltipImage = document.getElementById('tooltip-image-' + instanceId);
131
+ const tooltipCloseBtn = document.getElementById('tooltip-close-' + instanceId);
132
+
133
+ // 7) Aide / textes
134
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
135
+ const isMobile = isIOS || /Android/i.test(navigator.userAgent);
136
+
137
+ const tooltipInstruction = config.tooltips_url
138
+ ? '- ⦿ : annotations.<br>'
139
+ : '';
140
+
141
+ if (isMobile) {
142
+ helpTextDiv.innerHTML =
143
+ "- Déplacement avec deux doigts.<br>" +
144
+ "- Rotation avec un doigt.<br>" +
145
+ '- Zoom en pinçant avec deux doigts.<br>' +
146
+ tooltipInstruction +
147
+ '- ⟲ réinitialisation de la caméra.<br>' +
148
+ '- ⇱ plein écran.<br>';
149
+ } else {
150
+ helpTextDiv.innerHTML =
151
+ '- Rotation avec le clic droit ou maj + ←↑↓→<br>' +
152
+ '- Zoom avec la molette ou ctrl + ↑↓<br>' +
153
+ '- Déplacement avec le clic gauche ou ←↑↓→<br>' +
154
+ tooltipInstruction +
155
+ '- ⟲ réinitialisation de la caméra.<br>' +
156
+ '- ⇱ plein écran.<br>';
157
+ }
158
+
159
+ // 8) Sizing dynamique du panneau d’aide
160
+ function setMenuContentMaxSize() {
161
+ if (!isMobile) {
162
+ menuContent.style.maxWidth = '';
163
+ menuContent.style.maxHeight = '';
164
+ menuContent.style.width = '';
165
+ menuContent.style.height = '';
166
+ menuContent.style.overflowY = '';
167
+ menuContent.style.overflowX = '';
168
+ return;
169
+ }
170
+ const parent = viewerContainerElem;
171
+ if (parent) {
172
+ const vw = parent.offsetWidth;
173
+ const vh = parent.offsetHeight;
174
+ if (vw && vh) {
175
+ menuContent.style.maxWidth = Math.round(vw * 0.8) + 'px';
176
+ menuContent.style.maxHeight = Math.round(vh * 0.8) + 'px';
177
+ menuContent.style.width = '';
178
+ menuContent.style.height = '';
179
+ menuContent.style.overflowY = 'auto';
180
+ menuContent.style.overflowX = 'auto';
181
+ } else {
182
+ menuContent.style.maxWidth = '80vw';
183
+ menuContent.style.maxHeight = '80vh';
184
+ menuContent.style.overflowY = 'auto';
185
+ menuContent.style.overflowX = 'auto';
186
+ }
187
+ }
188
+ }
189
+
190
+ setMenuContentMaxSize();
191
+ window.addEventListener('resize', setMenuContentMaxSize);
192
+ document.addEventListener('fullscreenchange', setMenuContentMaxSize);
193
+ window.addEventListener('orientationchange', setMenuContentMaxSize);
194
+
195
+ // 9) Aide visible par défaut
196
+ menuContent.style.display = 'block';
197
+ viewerContainerElem.style.display = 'block';
198
+
199
+ // 10) Gestion du panneau tooltips
200
+ let dragHide = null;
201
+
202
+ function hideTooltipPanel() {
203
+ if (dragHide) {
204
+ viewerContainerElem.removeEventListener('pointermove', dragHide);
205
+ dragHide = null;
206
+ }
207
+ tooltipPanel.style.display = 'none';
208
+ }
209
+
210
+ function hideHelpPanel() {
211
+ menuContent.style.display = 'none';
212
+ }
213
+
214
+ // 11) Charger viewer.js (avec cache-busting par instance)
215
+ let viewerModule;
216
+ try {
217
+ const viewerUrl = `https://mikafil-viewer-sgos.static.hf.space/deplacement_dans_env/viewer_pr_env.js?inst=${instanceId}`;
218
+ viewerModule = await import(viewerUrl);
219
+ await viewerModule.initializeViewer(config, instanceId);
220
+ } catch (err) {
221
+ return;
222
+ }
223
+
224
+ const canvasId = 'canvas-' + instanceId;
225
+ const canvasEl = document.getElementById(canvasId);
226
+
227
+ // 12) Bouton tooltips : cacher si URL non valide
228
+ if (tooltipsToggleBtn) {
229
+ if (!config.tooltips_url) {
230
+ tooltipsToggleBtn.style.display = 'none';
231
+ } else {
232
+ fetch(config.tooltips_url)
233
+ .then((resp) => {
234
+ if (!resp.ok) tooltipsToggleBtn.style.display = 'none';
235
+ })
236
+ .catch(() => {
237
+ tooltipsToggleBtn.style.display = 'none';
238
+ });
239
+ }
240
+ }
241
+
242
+ // 13) Interactions locales / tooltips
243
+ if (canvasEl) {
244
+ canvasEl.addEventListener('wheel', hideTooltipPanel, { passive: true });
245
+ }
246
+
247
+ document.addEventListener('tooltip-selected', (evt) => {
248
+ // Toujours afficher le panneau, annuler un hide différé si présent
249
+ if (dragHide) {
250
+ viewerContainerElem.removeEventListener('pointermove', dragHide);
251
+ dragHide = null;
252
+ }
253
+
254
+ const { title, description, imgUrl } = evt.detail || {};
255
+ tooltipTextDiv.innerHTML = `<strong>${title || ''}</strong><br>${description || ''}`;
256
+
257
+ // Forcer un repaint : nettoyer src avant de réassigner
258
+ tooltipImage.style.display = 'none';
259
+ tooltipImage.src = '';
260
+
261
+ if (imgUrl) {
262
+ tooltipImage.onload = () => {
263
+ tooltipImage.style.display = 'block';
264
+ };
265
+ tooltipImage.src = imgUrl;
266
+ } else {
267
+ tooltipImage.style.display = 'none';
268
+ }
269
+
270
+ tooltipPanel.style.display = 'flex';
271
+
272
+ // Fermer en cas de drag (après un petit délai pour éviter un flicker)
273
+ setTimeout(() => {
274
+ dragHide = (e) => {
275
+ if (
276
+ (e.pointerType === 'mouse' && e.buttons !== 0) ||
277
+ e.pointerType === 'touch'
278
+ ) {
279
+ hideTooltipPanel();
280
+ }
281
+ };
282
+ viewerContainerElem.addEventListener('pointermove', dragHide);
283
+ }, 100);
284
+ });
285
+
286
+ // 14) Fullscreen
287
+ let isFullscreen = false;
288
+ let savedState = null;
289
+
290
+ function saveCurrentState() {
291
+ if (isFullscreen) return;
292
+ const originalAspect =
293
+ widgetContainer.getAttribute('data-original-aspect') || aspectPercent;
294
+
295
+ savedState = {
296
+ widget: {
297
+ position: widgetContainer.style.position,
298
+ top: widgetContainer.style.top,
299
+ left: widgetContainer.style.left,
300
+ width: widgetContainer.style.width,
301
+ height: widgetContainer.style.height,
302
+ maxWidth: widgetContainer.style.maxWidth,
303
+ maxHeight: widgetContainer.style.maxHeight,
304
+ paddingBottom: widgetContainer.style.paddingBottom || originalAspect,
305
+ margin: widgetContainer.style.margin
306
+ },
307
+ viewer: {
308
+ borderRadius: viewerContainerElem.style.borderRadius,
309
+ border: viewerContainerElem.style.border
310
+ }
311
+ };
312
+ }
313
+
314
+ function restoreOriginalStyles() {
315
+ if (!savedState) return;
316
+ const aspectToUse = savedState.widget.paddingBottom;
317
+
318
+ widgetContainer.style.position = savedState.widget.position || '';
319
+ widgetContainer.style.top = savedState.widget.top || '';
320
+ widgetContainer.style.left = savedState.widget.left || '';
321
+ widgetContainer.style.width = '100%';
322
+ widgetContainer.style.height = '0';
323
+ widgetContainer.style.maxWidth = savedState.widget.maxWidth || '';
324
+ widgetContainer.style.maxHeight = savedState.widget.maxHeight || '';
325
+ widgetContainer.style.paddingBottom = aspectToUse;
326
+ widgetContainer.style.margin = savedState.widget.margin || '';
327
+ widgetContainer.classList.remove('fake-fullscreen');
328
+
329
+ viewerContainerElem.style.position = 'absolute';
330
+ viewerContainerElem.style.top = '0';
331
+ viewerContainerElem.style.left = '0';
332
+ viewerContainerElem.style.right = '0';
333
+ viewerContainerElem.style.bottom = '0';
334
+ viewerContainerElem.style.width = '100%';
335
+ viewerContainerElem.style.height = '100%';
336
+ viewerContainerElem.style.borderRadius = savedState.viewer.borderRadius || '';
337
+ viewerContainerElem.style.border = savedState.viewer.border || '';
338
+
339
+ if (viewerModule.app) {
340
+ viewerModule.app.resizeCanvas(
341
+ viewerContainerElem.clientWidth,
342
+ viewerContainerElem.clientHeight
343
+ );
344
+ }
345
+
346
+ if (fullscreenToggle) fullscreenToggle.textContent = '⇱';
347
+
348
+ savedState = null;
349
+ setMenuContentMaxSize();
350
+ }
351
+
352
+ function applyFullscreenStyles() {
353
+ widgetContainer.style.position = 'fixed';
354
+ widgetContainer.style.top = '0';
355
+ widgetContainer.style.left = '0';
356
+ widgetContainer.style.width = '100vw';
357
+ widgetContainer.style.height = '100vh';
358
+ widgetContainer.style.maxWidth = '100vw';
359
+ widgetContainer.style.maxHeight = '100vh';
360
+ widgetContainer.style.paddingBottom = '0';
361
+ widgetContainer.style.margin = '0';
362
+ widgetContainer.style.border = 'none';
363
+ widgetContainer.style.borderRadius = '0';
364
+
365
+ viewerContainerElem.style.width = '100%';
366
+ viewerContainerElem.style.height = '100%';
367
+ viewerContainerElem.style.borderRadius = '0';
368
+ viewerContainerElem.style.border = 'none';
369
+
370
+ if (viewerModule.app) {
371
+ viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
372
+ }
373
+
374
+ if (fullscreenToggle) fullscreenToggle.textContent = '⇲';
375
+ isFullscreen = true;
376
+ setMenuContentMaxSize();
377
+ }
378
+
379
+ function enterFullscreen() {
380
+ if (!savedState) saveCurrentState();
381
+
382
+ if (isIOS) {
383
+ applyFullscreenStyles();
384
+ widgetContainer.classList.add('fake-fullscreen');
385
+ } else if (widgetContainer.requestFullscreen) {
386
+ widgetContainer
387
+ .requestFullscreen()
388
+ .then(applyFullscreenStyles)
389
+ .catch(() => {
390
+ applyFullscreenStyles();
391
+ widgetContainer.classList.add('fake-fullscreen');
392
+ });
393
+ } else {
394
+ applyFullscreenStyles();
395
+ widgetContainer.classList.add('fake-fullscreen');
396
+ }
397
+ }
398
+
399
+ function exitFullscreen() {
400
+ if (document.fullscreenElement === widgetContainer && document.exitFullscreen) {
401
+ document.exitFullscreen().catch(() => {});
402
+ }
403
+ widgetContainer.classList.remove('fake-fullscreen');
404
+ restoreOriginalStyles();
405
+ isFullscreen = false;
406
+ if (fullscreenToggle) fullscreenToggle.textContent = '⇱';
407
+ setMenuContentMaxSize();
408
+ }
409
+
410
+ fullscreenToggle.addEventListener('click', () => {
411
+ hideTooltipPanel();
412
+ isFullscreen ? exitFullscreen() : enterFullscreen();
413
+ });
414
+
415
+ document.addEventListener('fullscreenchange', () => {
416
+ if (!document.fullscreenElement && isFullscreen) {
417
+ isFullscreen = false;
418
+ restoreOriginalStyles();
419
+ if (fullscreenToggle) fullscreenToggle.textContent = '⇱';
420
+ } else if (document.fullscreenElement === widgetContainer) {
421
+ if (fullscreenToggle) fullscreenToggle.textContent = '⇲';
422
+ }
423
+ setMenuContentMaxSize();
424
+ });
425
+
426
+ // 15) Aide / boutons
427
+ helpToggle.addEventListener('click', (e) => {
428
+ hideTooltipPanel();
429
+ e.stopPropagation();
430
+ if (menuContent.style.display === 'block') {
431
+ menuContent.style.display = 'none';
432
+ } else {
433
+ menuContent.style.display = 'block';
434
+ setMenuContentMaxSize();
435
+ }
436
+ });
437
+
438
+ helpCloseBtn.addEventListener('click', hideHelpPanel);
439
+
440
+ resetCameraBtn.addEventListener('click', () => {
441
+ hideTooltipPanel();
442
+ if (viewerModule.resetViewerCamera) {
443
+ viewerModule.resetViewerCamera();
444
+ }
445
+ });
446
+
447
+ if (tooltipsToggleBtn) {
448
+ let tooltipsVisible = !!config.showTooltipsDefault;
449
+ tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
450
+ tooltipsToggleBtn.addEventListener('click', () => {
451
+ hideTooltipPanel();
452
+ tooltipsVisible = !tooltipsVisible;
453
+ tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
454
+ document.dispatchEvent(
455
+ new CustomEvent('toggle-tooltips', { detail: { visible: tooltipsVisible } })
456
+ );
457
+ });
458
+ }
459
+
460
+ tooltipCloseBtn.addEventListener('click', hideTooltipPanel);
461
+
462
+ // 16) Échappement / resize
463
+ document.addEventListener('keydown', (e) => {
464
+ if ((e.key === 'Escape' || e.key === 'Esc') && isFullscreen) {
465
+ exitFullscreen();
466
+ }
467
+ });
468
+
469
+ window.addEventListener('resize', () => {
470
+ if (viewerModule.app) {
471
+ if (isFullscreen) {
472
+ viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
473
+ } else {
474
+ viewerModule.app.resizeCanvas(
475
+ viewerContainerElem.clientWidth,
476
+ viewerContainerElem.clientHeight
477
+ );
478
+ }
479
+ }
480
+ setMenuContentMaxSize();
481
+ });
482
+
483
+ // 17) Init par défaut
484
+ setTimeout(() => {
485
+ // Sauvegarder l’état non-fullscreen
486
+ saveCurrentState();
487
+ // Propager l’état par défaut des tooltips
488
+ document.dispatchEvent(
489
+ new CustomEvent('toggle-tooltips', { detail: { visible: !!config.showTooltipsDefault } })
490
+ );
491
+ setMenuContentMaxSize();
492
+ }, 200);
493
+ })();