MikaFil commited on
Commit
7d61a65
·
verified ·
1 Parent(s): 9c44d5b

Update interface.js

Browse files
Files changed (1) hide show
  1. interface.js +236 -268
interface.js CHANGED
@@ -1,66 +1,63 @@
1
  // interface.js
2
  // ==============================
3
 
4
- const currentScriptTag = document.currentScript;
5
-
6
  (async function () {
7
- // 1) Retrouver la balise <script> porteuse de data-config
8
- let scriptTag = currentScriptTag;
9
- if (!scriptTag) {
10
- const scripts = document.getElementsByTagName('script');
11
- for (let i = 0; i < scripts.length; i++) {
12
- if (scripts[i].src.includes('interface.js') && scripts[i].hasAttribute('data-config')) {
13
- scriptTag = scripts[i];
14
- break;
15
- }
16
- }
17
- if (!scriptTag && scripts.length > 0) {
18
- scriptTag = scripts[scripts.length - 1];
 
19
  }
20
  }
21
- if (!scriptTag) return;
 
 
 
 
 
22
 
23
- // 2) Charger la config
24
- const configUrl = scriptTag.getAttribute('data-config');
25
  let config = {};
26
- if (configUrl) {
27
- try {
28
- const response = await fetch(configUrl);
29
- config = await response.json();
30
- } catch (error) {
31
- console.error('[interface.js] Failed to load config:', error);
32
- return;
33
- }
34
- } else {
35
- console.warn('[interface.js] No data-config provided.');
36
  return;
37
  }
38
 
39
- // 3) CSS optionnel
40
- if (config.css_url) {
41
- const linkEl = document.createElement('link');
42
- linkEl.rel = 'stylesheet';
43
  linkEl.href = config.css_url;
44
  document.head.appendChild(linkEl);
45
  }
46
 
47
- // 4) ID d’instance
48
  const instanceId = Math.random().toString(36).substr(2, 8);
49
 
50
- // 5) Calcul de l’aspect
51
- let aspectPercent = '100%';
52
  if (config.aspect) {
53
- if (config.aspect.includes(':')) {
54
- const parts = config.aspect.split(':');
55
  const w = parseFloat(parts[0]);
56
  const h = parseFloat(parts[1]);
57
  if (!isNaN(w) && !isNaN(h) && w > 0) {
58
- aspectPercent = (h / w * 100) + '%';
59
  }
60
  } else {
61
  const aspectValue = parseFloat(config.aspect);
62
  if (!isNaN(aspectValue) && aspectValue > 0) {
63
- aspectPercent = (100 / aspectValue) + '%';
64
  }
65
  }
66
  } else {
@@ -68,30 +65,23 @@ const currentScriptTag = document.currentScript;
68
  const containerWidth = parentContainer.offsetWidth;
69
  const containerHeight = parentContainer.offsetHeight;
70
  if (containerWidth > 0 && containerHeight > 0) {
71
- aspectPercent = (containerHeight / containerWidth * 100) + '%';
72
  }
73
  }
74
 
75
- // 6) Conteneur widget
76
- const widgetContainer = document.createElement('div');
77
- widgetContainer.id = 'ply-widget-container-' + instanceId;
78
- widgetContainer.classList.add('ply-widget-container');
79
- widgetContainer.style.height = '0';
80
  widgetContainer.style.paddingBottom = aspectPercent;
81
- widgetContainer.setAttribute('data-original-aspect', aspectPercent);
82
 
83
  const tooltipsButtonHTML = config.tooltips_url
84
  ? `<button id="tooltips-toggle-${instanceId}" class="widget-button tooltips-toggle">⦿</button>`
85
- : '';
86
-
87
- // IDs uniques pour le panneau tooltips
88
- const tooltipIds = {
89
- panel: `tooltip-panel-${instanceId}`,
90
- close: `tooltip-close-${instanceId}`,
91
- text: `tooltip-text-${instanceId}`,
92
- image: `tooltip-image-${instanceId}`
93
- };
94
 
 
95
  widgetContainer.innerHTML = `
96
  <div id="viewer-container-${instanceId}" class="viewer-container">
97
  <div id="progress-dialog-${instanceId}" class="progress-dialog">
@@ -99,74 +89,75 @@ const currentScriptTag = document.currentScript;
99
  </div>
100
  <button id="fullscreen-toggle-${instanceId}" class="widget-button fullscreen-toggle">⇱</button>
101
  <button id="help-toggle-${instanceId}" class="widget-button help-toggle">?</button>
102
- <button id="reset-camera-btn-${instanceId}" class="widget-button reset-camera-btn"><span class="reset-icon">⟲</span></button>
 
 
103
  ${tooltipsButtonHTML}
104
  <div id="menu-content-${instanceId}" class="menu-content">
105
  <span id="help-close-${instanceId}" class="help-close">×</span>
106
  <div class="help-text"></div>
107
  </div>
108
  </div>
109
- <div id="${tooltipIds.panel}" class="tooltip-panel" style="display: none;">
110
  <div class="tooltip-content">
111
- <span id="${tooltipIds.close}" class="tooltip-close">×</span>
112
- <div id="${tooltipIds.text}" class="tooltip-text"></div>
113
- <img id="${tooltipIds.image}" class="tooltip-image" src="" alt="" style="display: none;" />
114
  </div>
115
  </div>
116
  `;
117
 
 
118
  scriptTag.parentNode.appendChild(widgetContainer);
119
 
120
- // 7) Sélecteurs
121
- const viewerContainerElem = document.getElementById('viewer-container-' + instanceId);
122
- const fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
123
- const helpToggle = document.getElementById('help-toggle-' + instanceId);
124
- const helpCloseBtn = document.getElementById('help-close-' + instanceId);
125
- const resetCameraBtn = document.getElementById('reset-camera-btn-' + instanceId);
126
- const tooltipsToggleBtn = document.getElementById('tooltips-toggle-' + instanceId);
127
- const menuContent = document.getElementById('menu-content-' + instanceId);
128
- const helpTextDiv = menuContent.querySelector('.help-text');
129
-
130
- // Tooltips (IDs uniques)
131
- const tooltipPanel = document.getElementById(tooltipIds.panel);
132
- const tooltipTextDiv = document.getElementById(tooltipIds.text);
133
- const tooltipImage = document.getElementById(tooltipIds.image);
134
- const tooltipCloseBtn = document.getElementById(tooltipIds.close);
135
 
136
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
137
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
138
 
139
  const tooltipInstruction = config.tooltips_url
140
- ? '- Cliquez sur ⦿ pour afficher/masquer les tooltips.<br>'
141
- : '';
142
 
143
  if (isMobile) {
144
  helpTextDiv.innerHTML =
145
- '- Déplacez vous en glissant deux doigts sur l\'écran.<br>' +
146
- '- Orbitez en glissant un doigt.<br>' +
147
- '- Zoomez en pinçant avec deux doigts.<br>' +
148
  tooltipInstruction +
149
- '- Cliquez sur ⟲ pour réinitialiser la caméra.<br>' +
150
- '- Cliquez sur ⇱ pour passer en plein écran.<br>';
151
  } else {
152
  helpTextDiv.innerHTML =
153
- '- Orbitez avec le clic droit ou shift + ←↑↓→<br>' +
154
- '- Zoomez avec la molette ou ctrl + ↑↓<br>' +
155
- '- Déplacez vous avec le clic gauche ou ←↑↓→<br>' +
156
  tooltipInstruction +
157
- '- Cliquez sur ⟲ pour réinitialiser la caméra.<br>' +
158
- '- Cliquez sur ⇱ pour passer en plein écran.<br>';
159
  }
160
 
161
- // Menu responsive
162
  function setMenuContentMaxSize() {
163
  if (!isMobile) {
164
- menuContent.style.maxWidth = '';
165
- menuContent.style.maxHeight = '';
166
- menuContent.style.width = '';
167
- menuContent.style.height = '';
168
- menuContent.style.overflowY = '';
169
- menuContent.style.overflowX = '';
170
  return;
171
  }
172
  const parent = viewerContainerElem;
@@ -174,74 +165,115 @@ const currentScriptTag = document.currentScript;
174
  const vw = parent.offsetWidth;
175
  const vh = parent.offsetHeight;
176
  if (vw && vh) {
177
- menuContent.style.maxWidth = Math.round(vw * 0.8) + 'px';
178
- menuContent.style.maxHeight = Math.round(vh * 0.8) + 'px';
179
- menuContent.style.width = '';
180
- menuContent.style.height = '';
181
- menuContent.style.overflowY = 'auto';
182
- menuContent.style.overflowX = 'auto';
183
  } else {
184
- menuContent.style.maxWidth = '80vw';
185
- menuContent.style.maxHeight = '80vh';
186
- menuContent.style.overflowY = 'auto';
187
- menuContent.style.overflowX = 'auto';
188
  }
189
  }
190
  }
191
  setMenuContentMaxSize();
192
- window.addEventListener('resize', setMenuContentMaxSize);
193
- document.addEventListener('fullscreenchange', setMenuContentMaxSize);
194
- window.addEventListener('orientationchange', setMenuContentMaxSize);
195
-
196
- // Afficher l’aide par défaut
197
- menuContent.style.display = 'block';
198
- viewerContainerElem.style.display = 'block';
199
 
200
- // Fonctions panneau tooltips / aide
201
- let dragHide = null;
202
- function hideTooltipPanel() {
203
- if (dragHide) {
204
- viewerContainerElem.removeEventListener('pointermove', dragHide);
205
- dragHide = null;
206
- }
207
- tooltipPanel.style.display = 'none';
208
- }
209
- function hideHelpPanel() {
210
- menuContent.style.display = 'none';
211
- }
212
 
213
- // 8) Charger viewer.js (anti-cache multi-instance via query param)
 
214
  let viewerModule;
215
  try {
216
- const viewerUrl = `https://mikafil-viewer-sgos.static.hf.space/viewer.js?i=${instanceId}`;
217
  viewerModule = await import(viewerUrl);
218
  await viewerModule.initializeViewer(config, instanceId);
219
  } catch (err) {
220
- console.error('[interface.js] Failed to init viewer:', err);
221
  return;
222
  }
223
 
224
- const canvasId = 'canvas-' + instanceId;
225
  const canvasEl = document.getElementById(canvasId);
226
 
227
- // Si tooltips non dispo, masquer le bouton
228
  if (tooltipsToggleBtn) {
229
  if (!config.tooltips_url) {
230
- tooltipsToggleBtn.style.display = 'none';
231
  } else {
232
  fetch(config.tooltips_url)
233
- .then(resp => { if (!resp.ok) tooltipsToggleBtn.style.display = 'none'; })
234
- .catch(() => { tooltipsToggleBtn.style.display = 'none'; });
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
 
 
 
 
 
 
 
 
236
  }
237
 
238
- // 9) Plein écran
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  let isFullscreen = false;
240
  let savedState = null;
241
 
242
  function saveCurrentState() {
243
  if (isFullscreen) return;
244
- const originalAspect = widgetContainer.getAttribute('data-original-aspect') || aspectPercent;
245
  savedState = {
246
  widget: {
247
  position: widgetContainer.style.position,
@@ -265,26 +297,26 @@ const currentScriptTag = document.currentScript;
265
  if (!savedState) return;
266
  const aspectToUse = savedState.widget.paddingBottom;
267
 
268
- widgetContainer.style.position = savedState.widget.position || '';
269
- widgetContainer.style.top = savedState.widget.top || '';
270
- widgetContainer.style.left = savedState.widget.left || '';
271
- widgetContainer.style.width = '100%';
272
- widgetContainer.style.height = '0';
273
- widgetContainer.style.maxWidth = savedState.widget.maxWidth || '';
274
- widgetContainer.style.maxHeight = savedState.widget.maxHeight || '';
275
  widgetContainer.style.paddingBottom = aspectToUse;
276
- widgetContainer.style.margin = savedState.widget.margin || '';
277
- widgetContainer.classList.remove('fake-fullscreen');
278
-
279
- viewerContainerElem.style.position = 'absolute';
280
- viewerContainerElem.style.top = '0';
281
- viewerContainerElem.style.left = '0';
282
- viewerContainerElem.style.right = '0';
283
- viewerContainerElem.style.bottom = '0';
284
- viewerContainerElem.style.width = '100%';
285
- viewerContainerElem.style.height = '100%';
286
- viewerContainerElem.style.borderRadius = savedState.viewer.borderRadius || '';
287
- viewerContainerElem.style.border = savedState.viewer.border || '';
288
 
289
  if (viewerModule.app) {
290
  viewerModule.app.resizeCanvas(
@@ -293,189 +325,124 @@ const currentScriptTag = document.currentScript;
293
  );
294
  }
295
 
296
- if (fullscreenToggle) fullscreenToggle.textContent = '';
297
 
298
  savedState = null;
299
  setMenuContentMaxSize();
300
  }
301
 
302
  function applyFullscreenStyles() {
303
- widgetContainer.style.position = 'fixed';
304
- widgetContainer.style.top = '0';
305
- widgetContainer.style.left = '0';
306
- widgetContainer.style.width = '100vw';
307
- widgetContainer.style.height = '100vh';
308
- widgetContainer.style.maxWidth = '100vw';
309
- widgetContainer.style.maxHeight = '100vh';
310
- widgetContainer.style.paddingBottom = '0';
311
- widgetContainer.style.margin = '0';
312
- widgetContainer.style.border = 'none';
313
- widgetContainer.style.borderRadius = '0';
314
-
315
- viewerContainerElem.style.width = '100%';
316
- viewerContainerElem.style.height = '100%';
317
- viewerContainerElem.style.borderRadius = '0';
318
- viewerContainerElem.style.border = 'none';
319
 
320
  if (viewerModule.app) {
321
  viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
322
  }
323
 
324
- if (fullscreenToggle) fullscreenToggle.textContent = '';
325
  isFullscreen = true;
326
  setMenuContentMaxSize();
327
  }
328
 
329
  function enterFullscreen() {
330
  if (!savedState) saveCurrentState();
331
-
332
  if (isIOS) {
333
  applyFullscreenStyles();
334
- widgetContainer.classList.add('fake-fullscreen');
335
  } else if (widgetContainer.requestFullscreen) {
336
- widgetContainer.requestFullscreen()
 
337
  .then(applyFullscreenStyles)
338
  .catch(() => {
339
  applyFullscreenStyles();
340
- widgetContainer.classList.add('fake-fullscreen');
341
  });
342
  } else {
343
  applyFullscreenStyles();
344
- widgetContainer.classList.add('fake-fullscreen');
345
  }
346
  }
347
 
348
  function exitFullscreen() {
349
  if (document.fullscreenElement === widgetContainer && document.exitFullscreen) {
350
- document.exitFullscreen().catch(() => { /* noop */ });
351
  }
352
- widgetContainer.classList.remove('fake-fullscreen');
353
  restoreOriginalStyles();
354
  isFullscreen = false;
355
- if (fullscreenToggle) fullscreenToggle.textContent = '';
356
  setMenuContentMaxSize();
357
  }
358
 
359
- fullscreenToggle.addEventListener('click', () => {
360
  hideTooltipPanel();
361
  isFullscreen ? exitFullscreen() : enterFullscreen();
362
  });
363
 
364
- document.addEventListener('fullscreenchange', () => {
365
  if (!document.fullscreenElement && isFullscreen) {
366
  isFullscreen = false;
367
  restoreOriginalStyles();
368
- if (fullscreenToggle) fullscreenToggle.textContent = '';
369
  } else if (document.fullscreenElement === widgetContainer) {
370
- if (fullscreenToggle) fullscreenToggle.textContent = '';
371
  }
372
  setMenuContentMaxSize();
373
  });
374
 
375
- // 10) Aide / reset
376
- helpToggle.addEventListener('click', (e) => {
377
  hideTooltipPanel();
378
  e.stopPropagation();
379
- if (menuContent.style.display === 'block') {
380
- menuContent.style.display = 'none';
381
  } else {
382
- menuContent.style.display = 'block';
383
  setMenuContentMaxSize();
384
  }
385
  });
386
- helpCloseBtn.addEventListener('click', hideHelpPanel);
387
 
388
- resetCameraBtn.addEventListener('click', () => {
389
  hideTooltipPanel();
390
  if (viewerModule.resetViewerCamera) {
391
  viewerModule.resetViewerCamera();
392
  }
393
  });
394
 
395
- // 11) Tooltips (bouton)
396
  if (tooltipsToggleBtn) {
397
  let tooltipsVisible = !!config.showTooltipsDefault;
398
- tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
399
- tooltipsToggleBtn.addEventListener('click', () => {
400
  hideTooltipPanel();
401
  tooltipsVisible = !tooltipsVisible;
402
- tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
403
-
404
- // Envoi global (compat actuel tooltips.js)
405
- document.dispatchEvent(new CustomEvent('toggle-tooltips', {
406
- detail: { visible: tooltipsVisible }
407
- }));
408
-
409
- // Envoi namespacé (si tu mets à jour tooltips.js pour l'écouter)
410
- document.dispatchEvent(new CustomEvent(`toggle-tooltips-${instanceId}`, {
411
- detail: { visible: tooltipsVisible, instanceId }
412
- }));
413
  });
414
  }
415
 
416
- // 12) Panneau tooltips : fermeture
417
- tooltipCloseBtn.addEventListener('click', hideTooltipPanel);
418
-
419
- // 13) Gestion évènements "tooltip-selected"
420
- // a) Évènement namespacé (préféré si tooltips.js est mis à jour)
421
- const namespacedEvtName = `tooltip-selected-${instanceId}`;
422
- document.addEventListener(namespacedEvtName, (evt) => {
423
- const { title, description, imgUrl } = (evt.detail || {});
424
- showTooltipPanel(title, description, imgUrl);
425
- });
426
 
427
- // b) Fallback évènement global (tooltips.js actuel)
428
- // Pour éviter les collisions multi-instances, on ne réagit que si
429
- // le pointeur survole VOTRE canvas au moment du clic.
430
- let isPointerOver = false;
431
- if (canvasEl) {
432
- const enter = () => { isPointerOver = true; };
433
- const leave = () => { isPointerOver = false; };
434
- canvasEl.addEventListener('pointerenter', enter);
435
- canvasEl.addEventListener('pointerleave', leave);
436
- canvasEl.addEventListener('mouseenter', enter);
437
- canvasEl.addEventListener('mouseleave', leave);
438
- }
439
- document.addEventListener('tooltip-selected', (evt) => {
440
- if (!isPointerOver) return; // ignorer les autres viewers
441
- const { title, description, imgUrl } = (evt.detail || {});
442
- showTooltipPanel(title, description, imgUrl);
443
  });
444
 
445
- function showTooltipPanel(title, description, imgUrl) {
446
- // Annuler un éventuel handler différé
447
- if (dragHide) {
448
- viewerContainerElem.removeEventListener('pointermove', dragHide);
449
- dragHide = null;
450
- }
451
- tooltipTextDiv.innerHTML = `<strong>${title || ''}</strong><br>${description || ''}`;
452
- tooltipImage.style.display = 'none';
453
- tooltipImage.src = '';
454
- if (imgUrl) {
455
- tooltipImage.onload = () => { tooltipImage.style.display = 'block'; };
456
- tooltipImage.src = imgUrl;
457
- }
458
- tooltipPanel.style.display = 'flex';
459
-
460
- // Cacher si on “traîne” (drag) ou touche
461
- setTimeout(() => {
462
- dragHide = (e) => {
463
- if ((e.pointerType === 'mouse' && e.buttons !== 0) || e.pointerType === 'touch') {
464
- hideTooltipPanel();
465
- }
466
- };
467
- viewerContainerElem.addEventListener('pointermove', dragHide);
468
- }, 100);
469
- }
470
-
471
- // 14) Divers
472
- if (canvasEl) {
473
- canvasEl.addEventListener('wheel', hideTooltipPanel, { passive: true });
474
- }
475
- document.addEventListener('keydown', (e) => {
476
- if ((e.key === 'Escape' || e.key === 'Esc') && isFullscreen) exitFullscreen();
477
- });
478
- window.addEventListener('resize', () => {
479
  if (viewerModule.app) {
480
  if (isFullscreen) {
481
  viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
@@ -489,13 +456,14 @@ const currentScriptTag = document.currentScript;
489
  setMenuContentMaxSize();
490
  });
491
 
492
- // 15) Init état par défaut
493
  setTimeout(() => {
 
494
  saveCurrentState();
495
- // Toggler initial des tooltips
496
- const initVisible = !!config.showTooltipsDefault;
497
- document.dispatchEvent(new CustomEvent('toggle-tooltips', { detail: { visible: initVisible } }));
498
- document.dispatchEvent(new CustomEvent(`toggle-tooltips-${instanceId}`, { detail: { visible: initVisible, instanceId } }));
499
  setMenuContentMaxSize();
500
  }, 200);
501
- })();
 
1
  // interface.js
2
  // ==============================
3
 
 
 
4
  (async function () {
5
+ // Trouve toutes les balises <script src=".../interface.js" data-config="...">
6
+ const scriptTags = Array.from(
7
+ document.querySelectorAll('script[src*="interface.js"][data-config]')
8
+ );
9
+ if (!scriptTags.length) return;
10
+
11
+ // Initialise une instance par balise
12
+ for (const scriptTag of scriptTags) {
13
+ try {
14
+ await initViewerInstance(scriptTag);
15
+ } catch (e) {
16
+ // Évite qu'une erreur d'instance empêche les autres de se créer
17
+ console.error("[interface.js] Instance init error:", e);
18
  }
19
  }
20
+ })();
21
+
22
+ async function initViewerInstance(scriptTag) {
23
+ // 1) Lire la config
24
+ const configUrl = scriptTag.getAttribute("data-config");
25
+ if (!configUrl) return;
26
 
 
 
27
  let config = {};
28
+ try {
29
+ const response = await fetch(configUrl, { credentials: "omit", cache: "no-store" });
30
+ config = await response.json();
31
+ } catch (error) {
32
+ console.error("[interface.js] Failed to fetch config:", error);
 
 
 
 
 
33
  return;
34
  }
35
 
36
+ // 2) Injecter la CSS si nécessaire (une seule fois par href)
37
+ if (config.css_url && !document.querySelector(`link[rel="stylesheet"][href="${CSS.escape ? CSS.escape(config.css_url) : config.css_url}"]`)) {
38
+ const linkEl = document.createElement("link");
39
+ linkEl.rel = "stylesheet";
40
  linkEl.href = config.css_url;
41
  document.head.appendChild(linkEl);
42
  }
43
 
44
+ // 3) ID unique pour l’instance
45
  const instanceId = Math.random().toString(36).substr(2, 8);
46
 
47
+ // 4) Aspect ratio
48
+ let aspectPercent = "100%";
49
  if (config.aspect) {
50
+ if (config.aspect.includes(":")) {
51
+ const parts = config.aspect.split(":");
52
  const w = parseFloat(parts[0]);
53
  const h = parseFloat(parts[1]);
54
  if (!isNaN(w) && !isNaN(h) && w > 0) {
55
+ aspectPercent = (h / w) * 100 + "%";
56
  }
57
  } else {
58
  const aspectValue = parseFloat(config.aspect);
59
  if (!isNaN(aspectValue) && aspectValue > 0) {
60
+ aspectPercent = (100 / aspectValue) + "%";
61
  }
62
  }
63
  } else {
 
65
  const containerWidth = parentContainer.offsetWidth;
66
  const containerHeight = parentContainer.offsetHeight;
67
  if (containerWidth > 0 && containerHeight > 0) {
68
+ aspectPercent = (containerHeight / containerWidth) * 100 + "%";
69
  }
70
  }
71
 
72
+ // 5) Créer le container du widget
73
+ const widgetContainer = document.createElement("div");
74
+ widgetContainer.id = "ply-widget-container-" + instanceId;
75
+ widgetContainer.classList.add("ply-widget-container");
76
+ widgetContainer.style.height = "0";
77
  widgetContainer.style.paddingBottom = aspectPercent;
78
+ widgetContainer.setAttribute("data-original-aspect", aspectPercent);
79
 
80
  const tooltipsButtonHTML = config.tooltips_url
81
  ? `<button id="tooltips-toggle-${instanceId}" class="widget-button tooltips-toggle">⦿</button>`
82
+ : "";
 
 
 
 
 
 
 
 
83
 
84
+ // NB: on rend le tooltip-panel unique par instance
85
  widgetContainer.innerHTML = `
86
  <div id="viewer-container-${instanceId}" class="viewer-container">
87
  <div id="progress-dialog-${instanceId}" class="progress-dialog">
 
89
  </div>
90
  <button id="fullscreen-toggle-${instanceId}" class="widget-button fullscreen-toggle">⇱</button>
91
  <button id="help-toggle-${instanceId}" class="widget-button help-toggle">?</button>
92
+ <button id="reset-camera-btn-${instanceId}" class="widget-button reset-camera-btn">
93
+ <span class="reset-icon">⟲</span>
94
+ </button>
95
  ${tooltipsButtonHTML}
96
  <div id="menu-content-${instanceId}" class="menu-content">
97
  <span id="help-close-${instanceId}" class="help-close">×</span>
98
  <div class="help-text"></div>
99
  </div>
100
  </div>
101
+ <div id="tooltip-panel-${instanceId}" class="tooltip-panel" style="display: none;">
102
  <div class="tooltip-content">
103
+ <span id="tooltip-close-${instanceId}" class="tooltip-close">×</span>
104
+ <div id="tooltip-text-${instanceId}" class="tooltip-text"></div>
105
+ <img id="tooltip-image-${instanceId}" class="tooltip-image" src="" alt="" style="display: none;" />
106
  </div>
107
  </div>
108
  `;
109
 
110
+ // Insérer juste après la balise script courante
111
  scriptTag.parentNode.appendChild(widgetContainer);
112
 
113
+ // 6) Références DOM pour cette instance
114
+ const viewerContainerElem = document.getElementById("viewer-container-" + instanceId);
115
+ const fullscreenToggle = document.getElementById("fullscreen-toggle-" + instanceId);
116
+ const helpToggle = document.getElementById("help-toggle-" + instanceId);
117
+ const helpCloseBtn = document.getElementById("help-close-" + instanceId);
118
+ const resetCameraBtn = document.getElementById("reset-camera-btn-" + instanceId);
119
+ const tooltipsToggleBtn = document.getElementById("tooltips-toggle-" + instanceId);
120
+ const menuContent = document.getElementById("menu-content-" + instanceId);
121
+ const helpTextDiv = menuContent.querySelector(".help-text");
122
+ const tooltipPanel = document.getElementById("tooltip-panel-" + instanceId);
123
+ const tooltipTextDiv = document.getElementById("tooltip-text-" + instanceId);
124
+ const tooltipImage = document.getElementById("tooltip-image-" + instanceId);
125
+ const tooltipCloseBtn = document.getElementById("tooltip-close-" + instanceId);
 
 
126
 
127
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
128
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
129
 
130
  const tooltipInstruction = config.tooltips_url
131
+ ? "- Cliquez sur ⦿ pour afficher/masquer les tooltips.<br>"
132
+ : "";
133
 
134
  if (isMobile) {
135
  helpTextDiv.innerHTML =
136
+ "- Déplacez vous en glissant deux doigts sur l'écran.<br>" +
137
+ "- Orbitez en glissant un doigt.<br>" +
138
+ "- Zoomez en pinçant avec deux doigts.<br>" +
139
  tooltipInstruction +
140
+ "- Cliquez sur ⟲ pour réinitialiser la caméra.<br>" +
141
+ "- Cliquez sur ⇱ pour passer en plein écran.<br>";
142
  } else {
143
  helpTextDiv.innerHTML =
144
+ "- Orbitez avec le clic droit ou shift + ←↑↓→<br>" +
145
+ "- Zoomez avec la molette ou ctrl + ↑↓<br>" +
146
+ "- Déplacez vous avec le clic gauche ou ←↑↓→<br>" +
147
  tooltipInstruction +
148
+ "- Cliquez sur ⟲ pour réinitialiser la caméra.<br>" +
149
+ "- Cliquez sur ⇱ pour passer en plein écran.<br>";
150
  }
151
 
152
+ // --- DYNAMIC MENU SIZING ---
153
  function setMenuContentMaxSize() {
154
  if (!isMobile) {
155
+ menuContent.style.maxWidth = "";
156
+ menuContent.style.maxHeight = "";
157
+ menuContent.style.width = "";
158
+ menuContent.style.height = "";
159
+ menuContent.style.overflowY = "";
160
+ menuContent.style.overflowX = "";
161
  return;
162
  }
163
  const parent = viewerContainerElem;
 
165
  const vw = parent.offsetWidth;
166
  const vh = parent.offsetHeight;
167
  if (vw && vh) {
168
+ menuContent.style.maxWidth = Math.round(vw * 0.8) + "px";
169
+ menuContent.style.maxHeight = Math.round(vh * 0.8) + "px";
170
+ menuContent.style.width = "";
171
+ menuContent.style.height = "";
172
+ menuContent.style.overflowY = "auto";
173
+ menuContent.style.overflowX = "auto";
174
  } else {
175
+ menuContent.style.maxWidth = "80vw";
176
+ menuContent.style.maxHeight = "80vh";
177
+ menuContent.style.overflowY = "auto";
178
+ menuContent.style.overflowX = "auto";
179
  }
180
  }
181
  }
182
  setMenuContentMaxSize();
183
+ window.addEventListener("resize", setMenuContentMaxSize);
184
+ document.addEventListener("fullscreenchange", setMenuContentMaxSize);
185
+ window.addEventListener("orientationchange", setMenuContentMaxSize);
 
 
 
 
186
 
187
+ // --- HELP PANEL DEFAULT VISIBILITY ---
188
+ menuContent.style.display = "block";
189
+ viewerContainerElem.style.display = "block";
 
 
 
 
 
 
 
 
 
190
 
191
+ // 7) Charger viewer.js avec un paramètre unique (évite le cache ESM)
192
+ // -> chaque instance obtient son propre module (et son propre état).
193
  let viewerModule;
194
  try {
195
+ const viewerUrl = `https://mikafil-viewer-sgos.static.hf.space/viewer.js?inst=${instanceId}`;
196
  viewerModule = await import(viewerUrl);
197
  await viewerModule.initializeViewer(config, instanceId);
198
  } catch (err) {
199
+ console.error("[interface.js] viewer.js load/init error:", err);
200
  return;
201
  }
202
 
203
+ const canvasId = "canvas-" + instanceId;
204
  const canvasEl = document.getElementById(canvasId);
205
 
206
+ // 8) Bouton tooltips : cacher si URL non valide
207
  if (tooltipsToggleBtn) {
208
  if (!config.tooltips_url) {
209
+ tooltipsToggleBtn.style.display = "none";
210
  } else {
211
  fetch(config.tooltips_url)
212
+ .then((resp) => {
213
+ if (!resp.ok) tooltipsToggleBtn.style.display = "none";
214
+ })
215
+ .catch(() => {
216
+ tooltipsToggleBtn.style.display = "none";
217
+ });
218
+ }
219
+ }
220
+
221
+ // 9) Panneau tooltips & interactions locales
222
+ let dragHide = null;
223
+ function hideTooltipPanel() {
224
+ if (dragHide) {
225
+ viewerContainerElem.removeEventListener("pointermove", dragHide);
226
+ dragHide = null;
227
  }
228
+ tooltipPanel.style.display = "none";
229
+ }
230
+ function hideHelpPanel() {
231
+ menuContent.style.display = "none";
232
+ }
233
+
234
+ if (canvasEl) {
235
+ canvasEl.addEventListener("wheel", hideTooltipPanel, { passive: true });
236
  }
237
 
238
+ // NB : l’événement 'tooltip-selected' est global (tooltips.js l’émet sur document).
239
+ // Toutes les instances l’écoutent. Pour un découplage fin, il faudrait faire
240
+ // passer un identifiant d’instance dans detail (modif. de tooltips.js).
241
+ document.addEventListener("tooltip-selected", (evt) => {
242
+ // Affiche le panneau, annule un hide différé si présent
243
+ if (dragHide) {
244
+ viewerContainerElem.removeEventListener("pointermove", dragHide);
245
+ dragHide = null;
246
+ }
247
+ const { title, description, imgUrl } = evt.detail || {};
248
+ tooltipTextDiv.innerHTML = `<strong>${title || ""}</strong><br>${description || ""}`;
249
+ tooltipImage.style.display = "none";
250
+ tooltipImage.src = "";
251
+ if (imgUrl) {
252
+ tooltipImage.onload = () => {
253
+ tooltipImage.style.display = "block";
254
+ };
255
+ tooltipImage.src = imgUrl;
256
+ }
257
+ tooltipPanel.style.display = "flex";
258
+
259
+ // Fermer en cas de drag (après un petit délai pour éviter un flicker)
260
+ setTimeout(() => {
261
+ dragHide = (e) => {
262
+ if ((e.pointerType === "mouse" && e.buttons !== 0) || e.pointerType === "touch") {
263
+ hideTooltipPanel();
264
+ }
265
+ };
266
+ viewerContainerElem.addEventListener("pointermove", dragHide);
267
+ }, 100);
268
+ });
269
+
270
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
271
  let isFullscreen = false;
272
  let savedState = null;
273
 
274
  function saveCurrentState() {
275
  if (isFullscreen) return;
276
+ const originalAspect = widgetContainer.getAttribute("data-original-aspect") || aspectPercent;
277
  savedState = {
278
  widget: {
279
  position: widgetContainer.style.position,
 
297
  if (!savedState) return;
298
  const aspectToUse = savedState.widget.paddingBottom;
299
 
300
+ widgetContainer.style.position = savedState.widget.position || "";
301
+ widgetContainer.style.top = savedState.widget.top || "";
302
+ widgetContainer.style.left = savedState.widget.left || "";
303
+ widgetContainer.style.width = "100%";
304
+ widgetContainer.style.height = "0";
305
+ widgetContainer.style.maxWidth = savedState.widget.maxWidth || "";
306
+ widgetContainer.style.maxHeight = savedState.widget.maxHeight || "";
307
  widgetContainer.style.paddingBottom = aspectToUse;
308
+ widgetContainer.style.margin = savedState.widget.margin || "";
309
+ widgetContainer.classList.remove("fake-fullscreen");
310
+
311
+ viewerContainerElem.style.position = "absolute";
312
+ viewerContainerElem.style.top = "0";
313
+ viewerContainerElem.style.left = "0";
314
+ viewerContainerElem.style.right = "0";
315
+ viewerContainerElem.style.bottom = "0";
316
+ viewerContainerElem.style.width = "100%";
317
+ viewerContainerElem.style.height = "100%";
318
+ viewerContainerElem.style.borderRadius = savedState.viewer.borderRadius || "";
319
+ viewerContainerElem.style.border = savedState.viewer.border || "";
320
 
321
  if (viewerModule.app) {
322
  viewerModule.app.resizeCanvas(
 
325
  );
326
  }
327
 
328
+ if (fullscreenToggle) fullscreenToggle.textContent = "";
329
 
330
  savedState = null;
331
  setMenuContentMaxSize();
332
  }
333
 
334
  function applyFullscreenStyles() {
335
+ widgetContainer.style.position = "fixed";
336
+ widgetContainer.style.top = "0";
337
+ widgetContainer.style.left = "0";
338
+ widgetContainer.style.width = "100vw";
339
+ widgetContainer.style.height = "100vh";
340
+ widgetContainer.style.maxWidth = "100vw";
341
+ widgetContainer.style.maxHeight = "100vh";
342
+ widgetContainer.style.paddingBottom = "0";
343
+ widgetContainer.style.margin = "0";
344
+ widgetContainer.style.border = "none";
345
+ widgetContainer.style.borderRadius = "0";
346
+
347
+ viewerContainerElem.style.width = "100%";
348
+ viewerContainerElem.style.height = "100%";
349
+ viewerContainerElem.style.borderRadius = "0";
350
+ viewerContainerElem.style.border = "none";
351
 
352
  if (viewerModule.app) {
353
  viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
354
  }
355
 
356
+ if (fullscreenToggle) fullscreenToggle.textContent = "";
357
  isFullscreen = true;
358
  setMenuContentMaxSize();
359
  }
360
 
361
  function enterFullscreen() {
362
  if (!savedState) saveCurrentState();
 
363
  if (isIOS) {
364
  applyFullscreenStyles();
365
+ widgetContainer.classList.add("fake-fullscreen");
366
  } else if (widgetContainer.requestFullscreen) {
367
+ widgetContainer
368
+ .requestFullscreen()
369
  .then(applyFullscreenStyles)
370
  .catch(() => {
371
  applyFullscreenStyles();
372
+ widgetContainer.classList.add("fake-fullscreen");
373
  });
374
  } else {
375
  applyFullscreenStyles();
376
+ widgetContainer.classList.add("fake-fullscreen");
377
  }
378
  }
379
 
380
  function exitFullscreen() {
381
  if (document.fullscreenElement === widgetContainer && document.exitFullscreen) {
382
+ document.exitFullscreen().catch(() => {});
383
  }
384
+ widgetContainer.classList.remove("fake-fullscreen");
385
  restoreOriginalStyles();
386
  isFullscreen = false;
387
+ if (fullscreenToggle) fullscreenToggle.textContent = "";
388
  setMenuContentMaxSize();
389
  }
390
 
391
+ fullscreenToggle.addEventListener("click", () => {
392
  hideTooltipPanel();
393
  isFullscreen ? exitFullscreen() : enterFullscreen();
394
  });
395
 
396
+ document.addEventListener("fullscreenchange", () => {
397
  if (!document.fullscreenElement && isFullscreen) {
398
  isFullscreen = false;
399
  restoreOriginalStyles();
400
+ if (fullscreenToggle) fullscreenToggle.textContent = "";
401
  } else if (document.fullscreenElement === widgetContainer) {
402
+ if (fullscreenToggle) fullscreenToggle.textContent = "";
403
  }
404
  setMenuContentMaxSize();
405
  });
406
 
407
+ helpToggle.addEventListener("click", (e) => {
 
408
  hideTooltipPanel();
409
  e.stopPropagation();
410
+ if (menuContent.style.display === "block") {
411
+ menuContent.style.display = "none";
412
  } else {
413
+ menuContent.style.display = "block";
414
  setMenuContentMaxSize();
415
  }
416
  });
417
+ helpCloseBtn.addEventListener("click", hideHelpPanel);
418
 
419
+ resetCameraBtn.addEventListener("click", () => {
420
  hideTooltipPanel();
421
  if (viewerModule.resetViewerCamera) {
422
  viewerModule.resetViewerCamera();
423
  }
424
  });
425
 
 
426
  if (tooltipsToggleBtn) {
427
  let tooltipsVisible = !!config.showTooltipsDefault;
428
+ tooltipsToggleBtn.style.opacity = tooltipsVisible ? "1" : "0.5";
429
+ tooltipsToggleBtn.addEventListener("click", () => {
430
  hideTooltipPanel();
431
  tooltipsVisible = !tooltipsVisible;
432
+ tooltipsToggleBtn.style.opacity = tooltipsVisible ? "1" : "0.5";
433
+ document.dispatchEvent(
434
+ new CustomEvent("toggle-tooltips", { detail: { visible: tooltipsVisible } })
435
+ );
 
 
 
 
 
 
 
436
  });
437
  }
438
 
439
+ tooltipCloseBtn.addEventListener("click", hideTooltipPanel);
 
 
 
 
 
 
 
 
 
440
 
441
+ document.addEventListener("keydown", (e) => {
442
+ if ((e.key === "Escape" || e.key === "Esc") && isFullscreen) exitFullscreen();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  });
444
 
445
+ window.addEventListener("resize", () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  if (viewerModule.app) {
447
  if (isFullscreen) {
448
  viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
 
456
  setMenuContentMaxSize();
457
  });
458
 
459
+ // Init par défaut
460
  setTimeout(() => {
461
+ // Sauvegarder l'état non-fullscreen courant
462
  saveCurrentState();
463
+ // Propager l'état par défaut des tooltips (global pour l’instant)
464
+ document.dispatchEvent(
465
+ new CustomEvent("toggle-tooltips", { detail: { visible: !!config.showTooltipsDefault } })
466
+ );
467
  setMenuContentMaxSize();
468
  }, 200);
469
+ }