MikaFil commited on
Commit
9d390ba
·
verified ·
1 Parent(s): 6c91ac1

Update interface.js

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