MikaFil commited on
Commit
b2aae4b
·
verified ·
1 Parent(s): 1aac189

Create interface.js

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