MikaFil commited on
Commit
a88370d
·
verified ·
1 Parent(s): e87f3a1

Update interface.js

Browse files
Files changed (1) hide show
  1. interface.js +109 -297
interface.js CHANGED
@@ -1,26 +1,13 @@
1
  // interface.js
2
  // ==============================
3
 
4
- // Store a reference to the <script> tag that loaded this file
5
  const currentScriptTag = document.currentScript;
6
 
7
  (async function() {
8
- alert("[interface.js] SCRIPT BEGIN");
9
-
10
- // 1. Locate the <script> and read data-config
11
- let scriptTag = currentScriptTag;
12
- if (!scriptTag) {
13
- const scripts = document.getElementsByTagName('script');
14
- for (let i = 0; i < scripts.length; i++) {
15
- if (scripts[i].src.includes('interface.js') && scripts[i].hasAttribute('data-config')) {
16
- scriptTag = scripts[i];
17
- break;
18
- }
19
- }
20
- if (!scriptTag && scripts.length > 0) {
21
- scriptTag = scripts[scripts.length - 1];
22
- }
23
- }
24
 
25
  const configUrl = scriptTag.getAttribute('data-config');
26
  let config = {};
@@ -28,77 +15,58 @@ const currentScriptTag = document.currentScript;
28
  try {
29
  const response = await fetch(configUrl);
30
  config = await response.json();
31
- alert("[interface.js] Config loaded successfully");
32
  } catch (error) {
33
- alert("[interface.js] ERROR: Config failed to load: " + error);
34
  return;
35
  }
36
  } else {
37
- alert("[interface.js] ERROR: No config file provided.");
38
  return;
39
  }
40
 
41
- // 2. If config.css_url is provided, inject a <link> to that CSS
42
  if (config.css_url) {
43
  const linkEl = document.createElement('link');
44
  linkEl.rel = "stylesheet";
45
  linkEl.href = config.css_url;
46
  document.head.appendChild(linkEl);
47
- alert("[interface.js] CSS injected");
48
  }
49
 
50
- // 3. Generate a unique instanceId for this widget
51
  const instanceId = Math.random().toString(36).substr(2, 8);
52
 
53
- // 4. Compute the aspect ratio (padding-bottom %)
54
  let aspectPercent = "100%";
55
  if (config.aspect) {
56
  if (config.aspect.includes(":")) {
57
- const parts = config.aspect.split(":");
58
- const w = parseFloat(parts[0]);
59
- const h = parseFloat(parts[1]);
60
- if (!isNaN(w) && !isNaN(h) && w > 0) {
61
- aspectPercent = (h / w * 100) + "%";
62
- }
63
  } else {
64
- const aspectValue = parseFloat(config.aspect);
65
- if (!isNaN(aspectValue) && aspectValue > 0) {
66
- aspectPercent = (100 / aspectValue) + "%";
67
- }
68
- }
69
- } else {
70
- const parentContainer = scriptTag.parentNode;
71
- const containerWidth = parentContainer.offsetWidth;
72
- const containerHeight = parentContainer.offsetHeight;
73
- if (containerWidth > 0 && containerHeight > 0) {
74
- aspectPercent = (containerHeight / containerWidth * 100) + "%";
75
  }
76
  }
77
 
78
- // 5. Create the widget container (no GIF preview, no close button)
79
  const widgetContainer = document.createElement('div');
80
  widgetContainer.id = 'ply-widget-container-' + instanceId;
81
  widgetContainer.classList.add('ply-widget-container');
82
  widgetContainer.style.height = "0";
83
  widgetContainer.style.paddingBottom = aspectPercent;
84
- widgetContainer.setAttribute('data-original-aspect', aspectPercent);
85
 
86
- // Conditionally include the “tooltips-toggle” button only if config.tooltips_url is defined
 
 
 
87
  const tooltipsButtonHTML = config.tooltips_url
88
- ? `<button id="tooltips-toggle-${instanceId}" class="widget-button tooltips-toggle">⦿</button>`
89
  : '';
90
 
91
- // Add the 3D-viewer HTML + tooltip + help HTML (all HTML in a template literal!)
92
  widgetContainer.innerHTML = `
93
- <div id="viewer-container-${instanceId}" class="viewer-container">
94
  <div id="progress-dialog-${instanceId}" class="progress-dialog">
95
  <progress id="progress-indicator-${instanceId}" max="100" value="0"></progress>
96
  </div>
97
  <button id="fullscreen-toggle-${instanceId}" class="widget-button fullscreen-toggle">⇱</button>
98
  <button id="help-toggle-${instanceId}" class="widget-button help-toggle">?</button>
99
- <button id="reset-camera-btn-${instanceId}" class="widget-button reset-camera-btn">
100
- <span class="reset-icon">⟲</span>
101
- </button>
102
  ${tooltipsButtonHTML}
103
  <div id="menu-content-${instanceId}" class="menu-content">
104
  <span id="help-close-${instanceId}" class="help-close">×</span>
@@ -109,16 +77,13 @@ const currentScriptTag = document.currentScript;
109
  <div class="tooltip-content">
110
  <span id="tooltip-close" class="tooltip-close">×</span>
111
  <div id="tooltip-text" class="tooltip-text"></div>
112
- <img id="tooltip-image" class="tooltip-image" src="" alt="" style="display: none;" />
113
  </div>
114
  </div>
115
  `;
116
-
117
- // Append the widget container immediately after the <script> tag
118
  scriptTag.parentNode.appendChild(widgetContainer);
119
- alert("[interface.js] Widget HTML injected");
120
 
121
- // 6. Grab references to new DOM elements
122
  const viewerContainerElem = document.getElementById('viewer-container-' + instanceId);
123
  const fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
124
  const helpToggle = document.getElementById('help-toggle-' + instanceId);
@@ -127,283 +92,130 @@ const currentScriptTag = document.currentScript;
127
  const tooltipsToggleBtn = document.getElementById('tooltips-toggle-' + instanceId);
128
  const menuContent = document.getElementById('menu-content-' + instanceId);
129
  const helpTextDiv = menuContent.querySelector('.help-text');
 
 
 
 
130
 
131
- // Tooltip panel elements
132
- const tooltipPanel = document.getElementById('tooltip-panel');
133
- const tooltipTextDiv = document.getElementById('tooltip-text');
134
- const tooltipImage = document.getElementById('tooltip-image');
135
- const tooltipCloseBtn = document.getElementById('tooltip-close');
136
-
137
- // 6a. Detect mobile vs. desktop
138
- const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
139
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
140
 
141
- // Conditionally include the French tooltip instruction line if tooltips_url exists
142
- const tooltipInstruction = config.tooltips_url
143
- ? '- Cliquez sur ⦿ pour afficher/masquer les tooltips.<br>'
144
- : '';
145
-
146
- // Fill help text with instructions plus the two new French lines
147
- if (isMobile) {
148
- helpTextDiv.innerHTML =
149
- '- Pour vous déplacer, glissez deux doigts sur l\'écran.<br>' +
150
- '- Pour orbiter, utilisez un doigt.<br>' +
151
- '- Pour zoomer, pincez avec deux doigts.<br>' +
152
- tooltipInstruction +
153
- '- ⟲ Réinitialise la caméra.<br>' +
154
- '- Passe en plein écran.<br>';
155
- } else {
156
- helpTextDiv.innerHTML =
157
- '- orbitez avec le clic droit<br>' +
158
- '- zoomez avec la molette<br>' +
159
- '- déplacez vous avec le clic gauche<br>' +
160
- tooltipInstruction +
161
- '- ⟲ Réinitialise la caméra.<br>' +
162
- '- ⇱ Passe en plein écran.<br>';
163
- }
164
-
165
- // Ensure instructions panel is visible by default
166
- menuContent.style.display = 'block';
167
- viewerContainerElem.style.display = 'block';
168
-
169
- // Variable to hold the drag-hide listener reference
170
- let dragHide = null;
171
-
172
- // Utilities to hide panels
173
- function hideTooltipPanel() {
174
- if (dragHide) {
175
- viewerContainerElem.removeEventListener('pointermove', dragHide);
176
- dragHide = null;
177
- }
178
- tooltipPanel.style.display = 'none';
179
- }
180
- function hideHelpPanel() {
181
- menuContent.style.display = 'none';
182
- }
183
-
184
- // 7. Dynamically load viewer.js
185
  let viewerModule;
186
  try {
187
  viewerModule = await import('https://mikafil-viewer-gs.static.hf.space/viewer.js');
188
- alert("[interface.js] viewer.js module loaded");
189
  await viewerModule.initializeViewer(config, instanceId);
190
- alert("[interface.js] viewer.js initializeViewer() returned");
191
  } catch (err) {
192
- alert("[interface.js] ERROR: Failed to load or init viewer.js: " + err);
193
  return;
194
  }
 
195
 
196
- const canvasId = 'canvas-' + instanceId;
197
- const canvasEl = document.getElementById(canvasId);
198
-
199
- // 8. Conditional display of tooltips-toggle button
200
- if (tooltipsToggleBtn) {
201
- if (!config.tooltips_url) {
202
- tooltipsToggleBtn.style.display = 'none';
203
- } else {
204
- fetch(config.tooltips_url)
205
- .then(resp => { if (!resp.ok) tooltipsToggleBtn.style.display = 'none'; })
206
- .catch(() => { tooltipsToggleBtn.style.display = 'none'; });
207
- }
208
- }
209
-
210
- // 9. Fullscreen / state-preservation logic
211
- let isFullscreen = false;
212
- let savedState = null;
213
-
214
- function saveCurrentState() {
215
- if (isFullscreen) return;
216
- const originalAspect = widgetContainer.getAttribute('data-original-aspect') || aspectPercent;
217
- savedState = {
218
- widget: {
219
- position: widgetContainer.style.position,
220
- top: widgetContainer.style.top,
221
- left: widgetContainer.style.left,
222
- width: widgetContainer.style.width,
223
- height: widgetContainer.style.height,
224
- maxWidth: widgetContainer.style.maxWidth,
225
- maxHeight:widgetContainer.style.maxHeight,
226
- paddingBottom: widgetContainer.style.paddingBottom || originalAspect,
227
- margin: widgetContainer.style.margin,
228
- },
229
- viewer: {
230
- borderRadius: viewerContainerElem.style.borderRadius,
231
- border: viewerContainerElem.style.border,
232
- }
233
- };
234
- }
235
-
236
- function restoreOriginalStyles() {
237
- if (!savedState) return;
238
- const aspectToUse = savedState.widget.paddingBottom;
239
- widgetContainer.style.position = savedState.widget.position || "";
240
- widgetContainer.style.top = savedState.widget.top || "";
241
- widgetContainer.style.left = savedState.widget.left || "";
242
- widgetContainer.style.width = "100%";
243
- widgetContainer.style.height = "0";
244
- widgetContainer.style.maxWidth = savedState.widget.maxWidth || "";
245
- widgetContainer.style.maxHeight = savedState.widget.maxHeight || "";
246
- widgetContainer.style.paddingBottom= aspectToUse;
247
- widgetContainer.style.margin = savedState.widget.margin || "";
248
- widgetContainer.classList.remove('fake-fullscreen');
249
-
250
- viewerContainerElem.style.position = "absolute";
251
- viewerContainerElem.style.top = "0";
252
- viewerContainerElem.style.left = "0";
253
- viewerContainerElem.style.right = "0";
254
- viewerContainerElem.style.bottom = "0";
255
- viewerContainerElem.style.width = "100%";
256
- viewerContainerElem.style.height = "100%";
257
- viewerContainerElem.style.borderRadius = savedState.viewer.borderRadius || "";
258
- viewerContainerElem.style.border = savedState.viewer.border || "";
259
-
260
- if (viewerModule.app) {
261
- viewerModule.app.resizeCanvas(
262
- viewerContainerElem.clientWidth,
263
- viewerContainerElem.clientHeight
264
- );
265
- }
266
-
267
- savedState = null;
268
- }
269
-
270
- function applyFullscreenStyles() {
271
- widgetContainer.style.position = 'fixed';
272
- widgetContainer.style.top = '0';
273
- widgetContainer.style.left = '0';
274
- widgetContainer.style.width = '100vw';
275
- widgetContainer.style.height = '100vh';
276
- widgetContainer.style.maxWidth = '100vw';
277
- widgetContainer.style.maxHeight = '100vh';
278
- widgetContainer.style.paddingBottom = '0';
279
- widgetContainer.style.margin = '0';
280
- widgetContainer.style.border = 'none';
281
- widgetContainer.style.borderRadius = '0';
282
-
283
- viewerContainerElem.style.width = '100%';
284
- viewerContainerElem.style.height = '100%';
285
- viewerContainerElem.style.borderRadius= '0';
286
- viewerContainerElem.style.border = 'none';
287
-
288
- if (viewerModule.app) {
289
- viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
290
- }
291
-
292
- fullscreenToggle.textContent = '⇲';
293
- isFullscreen = true;
294
- }
295
-
296
- function enterFullscreen() {
297
- alert("[interface.js] Entering fullscreen");
298
- if (!savedState) saveCurrentState();
299
- if (isIOS) {
300
- applyFullscreenStyles();
301
- widgetContainer.classList.add('fake-fullscreen');
302
- } else if (widgetContainer.requestFullscreen) {
303
- widgetContainer.requestFullscreen()
304
- .then(applyFullscreenStyles)
305
- .catch(() => {
306
- applyFullscreenStyles();
307
- widgetContainer.classList.add('fake-fullscreen');
308
- });
309
- } else {
310
- applyFullscreenStyles();
311
- widgetContainer.classList.add('fake-fullscreen');
312
- }
313
- }
314
-
315
- function exitFullscreen() {
316
- alert("[interface.js] Exiting fullscreen");
317
- if (document.fullscreenElement === widgetContainer && document.exitFullscreen) {
318
- document.exitFullscreen().catch(() => {});
319
- }
320
- widgetContainer.classList.remove('fake-fullscreen');
321
- restoreOriginalStyles();
322
- isFullscreen = false;
323
  }
324
 
325
- // 10. Hook up event listeners
326
- fullscreenToggle.addEventListener('click', () => {
327
- hideTooltipPanel();
328
- isFullscreen ? exitFullscreen() : enterFullscreen();
329
- });
330
- document.addEventListener('fullscreenchange', () => {
331
- if (!document.fullscreenElement && isFullscreen) {
332
- isFullscreen = false;
333
- restoreOriginalStyles();
334
- }
335
- });
336
-
337
- helpToggle.addEventListener('click', (e) => {
338
- hideTooltipPanel();
339
- e.stopPropagation();
340
- menuContent.style.display = menuContent.style.display === 'block' ? 'none' : 'block';
341
- });
342
- helpCloseBtn.addEventListener('click', hideHelpPanel);
343
-
344
- resetCameraBtn.addEventListener('click', () => {
345
- hideTooltipPanel();
346
- alert("[interface.js] Reset camera button clicked");
347
- if (viewerModule.resetViewerCamera) {
348
- viewerModule.resetViewerCamera();
349
- }
350
- });
351
-
352
  if (tooltipsToggleBtn) {
353
  let tooltipsVisible = !!config.showTooltipsDefault;
354
  tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
355
- tooltipsToggleBtn.addEventListener('click', () => {
356
- hideTooltipPanel();
357
  tooltipsVisible = !tooltipsVisible;
358
  tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
359
- alert("[interface.js] Tooltips toggle clicked. Now: " + (tooltipsVisible ? "ON" : "OFF"));
360
  document.dispatchEvent(new CustomEvent('toggle-tooltips', { detail: { visible: tooltipsVisible } }));
 
 
 
 
 
 
 
361
  });
362
  }
363
 
364
- tooltipCloseBtn.addEventListener('click', hideTooltipPanel);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
- document.addEventListener('tooltip-selected', (evt) => {
 
 
 
 
 
 
 
 
 
 
367
  const { title, description, imgUrl } = evt.detail;
368
  tooltipTextDiv.innerHTML = `<strong>${title}</strong><br>${description}`;
369
  if (imgUrl) {
370
  tooltipImage.src = imgUrl;
371
  tooltipImage.style.display = 'block';
372
- } else {
373
- tooltipImage.style.display = 'none';
374
- }
375
  tooltipPanel.style.display = 'flex';
376
- dragHide = (e) => {
377
- if ((e.pointerType === 'mouse' && e.buttons !== 0) || e.pointerType === 'touch') {
378
- hideTooltipPanel();
379
- }
380
- };
381
- viewerContainerElem.addEventListener('pointermove', dragHide);
382
  });
383
 
 
384
  if (canvasEl) {
385
- canvasEl.addEventListener('wheel', hideTooltipPanel, { passive: true });
 
386
  }
387
- document.addEventListener('keydown', (e) => {
388
- if ((e.key === 'Escape' || e.key === 'Esc') && isFullscreen) exitFullscreen();
389
- });
390
- window.addEventListener('resize', () => {
391
- if (viewerModule.app) {
392
- if (isFullscreen) {
393
- viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
394
- } else {
395
- viewerModule.app.resizeCanvas(
396
- viewerContainerElem.clientWidth,
397
- viewerContainerElem.clientHeight
398
- );
399
- }
400
- }
401
- });
402
 
 
403
  setTimeout(() => {
404
- saveCurrentState();
405
  document.dispatchEvent(new CustomEvent('toggle-tooltips', { detail: { visible: !!config.showTooltipsDefault } }));
406
- alert("[interface.js] Widget setup complete!");
407
  }, 200);
408
-
409
  })();
 
1
  // interface.js
2
  // ==============================
3
 
4
+ // Store reference to this script tag
5
  const currentScriptTag = document.currentScript;
6
 
7
  (async function() {
8
+ // 1. Locate script tag and fetch config
9
+ let scriptTag = currentScriptTag || document.querySelector('script[src*="interface.js"][data-config]');
10
+ if (!scriptTag) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  const configUrl = scriptTag.getAttribute('data-config');
13
  let config = {};
 
15
  try {
16
  const response = await fetch(configUrl);
17
  config = await response.json();
 
18
  } catch (error) {
 
19
  return;
20
  }
21
  } else {
 
22
  return;
23
  }
24
 
25
+ // 2. Inject CSS if provided
26
  if (config.css_url) {
27
  const linkEl = document.createElement('link');
28
  linkEl.rel = "stylesheet";
29
  linkEl.href = config.css_url;
30
  document.head.appendChild(linkEl);
 
31
  }
32
 
33
+ // 3. Generate unique instanceId
34
  const instanceId = Math.random().toString(36).substr(2, 8);
35
 
36
+ // 4. Compute aspect ratio padding-bottom
37
  let aspectPercent = "100%";
38
  if (config.aspect) {
39
  if (config.aspect.includes(":")) {
40
+ const [w, h] = config.aspect.split(":").map(Number);
41
+ aspectPercent = (h / w * 100) + "%";
 
 
 
 
42
  } else {
43
+ aspectPercent = (100 / parseFloat(config.aspect)) + "%";
 
 
 
 
 
 
 
 
 
 
44
  }
45
  }
46
 
47
+ // 5. Create widget container
48
  const widgetContainer = document.createElement('div');
49
  widgetContainer.id = 'ply-widget-container-' + instanceId;
50
  widgetContainer.classList.add('ply-widget-container');
51
  widgetContainer.style.height = "0";
52
  widgetContainer.style.paddingBottom = aspectPercent;
 
53
 
54
+ // iOS-specific touch-action fix on container
55
+ widgetContainer.style.touchAction = "none";
56
+
57
+ // Tooltip button (if tooltips_url)
58
  const tooltipsButtonHTML = config.tooltips_url
59
+ ? `<button id="tooltips-toggle-${instanceId}" class="widget-button tooltips-toggle" tabindex="0" aria-label="Toggle tooltips">⦿</button>`
60
  : '';
61
 
 
62
  widgetContainer.innerHTML = `
63
+ <div id="viewer-container-${instanceId}" class="viewer-container" style="touch-action:none">
64
  <div id="progress-dialog-${instanceId}" class="progress-dialog">
65
  <progress id="progress-indicator-${instanceId}" max="100" value="0"></progress>
66
  </div>
67
  <button id="fullscreen-toggle-${instanceId}" class="widget-button fullscreen-toggle">⇱</button>
68
  <button id="help-toggle-${instanceId}" class="widget-button help-toggle">?</button>
69
+ <button id="reset-camera-btn-${instanceId}" class="widget-button reset-camera-btn"><span class="reset-icon">⟲</span></button>
 
 
70
  ${tooltipsButtonHTML}
71
  <div id="menu-content-${instanceId}" class="menu-content">
72
  <span id="help-close-${instanceId}" class="help-close">×</span>
 
77
  <div class="tooltip-content">
78
  <span id="tooltip-close" class="tooltip-close">×</span>
79
  <div id="tooltip-text" class="tooltip-text"></div>
80
+ <img id="tooltip-image" class="tooltip-image" src="" style="display: none;" />
81
  </div>
82
  </div>
83
  `;
 
 
84
  scriptTag.parentNode.appendChild(widgetContainer);
 
85
 
86
+ // 6. Grab DOM references
87
  const viewerContainerElem = document.getElementById('viewer-container-' + instanceId);
88
  const fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
89
  const helpToggle = document.getElementById('help-toggle-' + instanceId);
 
92
  const tooltipsToggleBtn = document.getElementById('tooltips-toggle-' + instanceId);
93
  const menuContent = document.getElementById('menu-content-' + instanceId);
94
  const helpTextDiv = menuContent.querySelector('.help-text');
95
+ const tooltipPanel = document.getElementById('tooltip-panel');
96
+ const tooltipTextDiv = document.getElementById('tooltip-text');
97
+ const tooltipImage = document.getElementById('tooltip-image');
98
+ const tooltipCloseBtn = document.getElementById('tooltip-close');
99
 
100
+ // Detect mobile (iOS)
101
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
 
 
 
 
 
 
102
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
103
 
104
+ // 7. Help text
105
+ const tooltipsLine = config.tooltips_url ? '- Cliquez sur ⦿ pour afficher/masquer les tooltips.<br>' : '';
106
+ helpTextDiv.innerHTML = isMobile
107
+ ? `- Pour vous déplacer, glissez deux doigts sur l'écran.<br>
108
+ - Pour orbiter, utilisez un doigt.<br>
109
+ - Pour zoomer, pincez avec deux doigts.<br>
110
+ ${tooltipsLine}
111
+ - Réinitialise la caméra.<br>
112
+ - Passe en plein écran.<br>`
113
+ : `- orbitez avec le clic droit<br>
114
+ - zoomez avec la molette<br>
115
+ - déplacez vous avec le clic gauche<br>
116
+ ${tooltipsLine}
117
+ - Réinitialise la caméra.<br>
118
+ - Passe en plein écran.<br>`;
119
+
120
+ // 8. Dynamically load viewer.js
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  let viewerModule;
122
  try {
123
  viewerModule = await import('https://mikafil-viewer-gs.static.hf.space/viewer.js');
 
124
  await viewerModule.initializeViewer(config, instanceId);
 
125
  } catch (err) {
 
126
  return;
127
  }
128
+ const canvasEl = document.getElementById('canvas-' + instanceId);
129
 
130
+ // iOS-specific CSS/touch fixes on canvas
131
+ if (canvasEl) {
132
+ canvasEl.style.touchAction = "none";
133
+ canvasEl.style.webkitTouchCallout = "none";
134
+ canvasEl.setAttribute('tabindex', '0');
135
+ // Prevent all zoom gestures
136
+ canvasEl.addEventListener('gesturestart', e => e.preventDefault());
137
+ canvasEl.addEventListener('gesturechange', e => e.preventDefault());
138
+ canvasEl.addEventListener('gestureend', e => e.preventDefault());
139
+ canvasEl.addEventListener('dblclick', e => e.preventDefault());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  }
141
 
142
+ // 9. Tooltip toggle logic
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  if (tooltipsToggleBtn) {
144
  let tooltipsVisible = !!config.showTooltipsDefault;
145
  tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
146
+ tooltipsToggleBtn.onclick = () => {
 
147
  tooltipsVisible = !tooltipsVisible;
148
  tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
 
149
  document.dispatchEvent(new CustomEvent('toggle-tooltips', { detail: { visible: tooltipsVisible } }));
150
+ };
151
+ // Keyboard accessibility (Enter/Space toggles)
152
+ tooltipsToggleBtn.addEventListener('keydown', (e) => {
153
+ if (e.key === ' ' || e.key === 'Enter') {
154
+ e.preventDefault();
155
+ tooltipsToggleBtn.click();
156
+ }
157
  });
158
  }
159
 
160
+ // 10. Fullscreen logic
161
+ fullscreenToggle.onclick = () => {
162
+ if (document.fullscreenElement !== widgetContainer) {
163
+ widgetContainer.requestFullscreen().catch(() => {
164
+ // fallback: fake fullscreen (mobile)
165
+ widgetContainer.style.position = 'fixed';
166
+ widgetContainer.style.top = widgetContainer.style.left = 0;
167
+ widgetContainer.style.width = '100vw';
168
+ widgetContainer.style.height = '100vh';
169
+ widgetContainer.style.paddingBottom = '0';
170
+ widgetContainer.style.zIndex = 99999;
171
+ });
172
+ } else {
173
+ document.exitFullscreen();
174
+ // remove fake fullscreen
175
+ widgetContainer.style.position = "";
176
+ widgetContainer.style.width = "";
177
+ widgetContainer.style.height = "";
178
+ widgetContainer.style.paddingBottom = aspectPercent;
179
+ widgetContainer.style.zIndex = "";
180
+ }
181
+ };
182
+
183
+ // 11. Reset camera
184
+ resetCameraBtn.onclick = () => {
185
+ viewerModule.resetViewerCamera && viewerModule.resetViewerCamera();
186
+ };
187
 
188
+ // 12. Help events
189
+ helpToggle.onclick = (e) => {
190
+ menuContent.style.display = menuContent.style.display === 'block' ? 'none' : 'block';
191
+ e.stopPropagation();
192
+ };
193
+ helpCloseBtn.onclick = () => menuContent.style.display = 'none';
194
+
195
+ // 13. Tooltip panel events
196
+ tooltipCloseBtn.onclick = () => tooltipPanel.style.display = 'none';
197
+
198
+ document.addEventListener('tooltip-selected', evt => {
199
  const { title, description, imgUrl } = evt.detail;
200
  tooltipTextDiv.innerHTML = `<strong>${title}</strong><br>${description}`;
201
  if (imgUrl) {
202
  tooltipImage.src = imgUrl;
203
  tooltipImage.style.display = 'block';
204
+ } else tooltipImage.style.display = 'none';
 
 
205
  tooltipPanel.style.display = 'flex';
206
+ });
207
+ document.addEventListener('hide-tooltip', () => {
208
+ tooltipPanel.style.display = 'none';
 
 
 
209
  });
210
 
211
+ // Hide tooltip panel on any interaction with canvas
212
  if (canvasEl) {
213
+ canvasEl.addEventListener('pointerdown', () => { tooltipPanel.style.display = 'none'; });
214
+ canvasEl.addEventListener('wheel', () => { tooltipPanel.style.display = 'none'; }, { passive: true });
215
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
+ // 14. Initial tooltips visibility if any
218
  setTimeout(() => {
 
219
  document.dispatchEvent(new CustomEvent('toggle-tooltips', { detail: { visible: !!config.showTooltipsDefault } }));
 
220
  }, 200);
 
221
  })();