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

Update interface.js

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