MikaFil commited on
Commit
465cac7
Β·
verified Β·
1 Parent(s): 96bde52

Update interface.js

Browse files
Files changed (1) hide show
  1. interface.js +452 -65
interface.js CHANGED
@@ -2,6 +2,7 @@
2
  // interface.js
3
  // ==============================
4
 
 
5
  const currentScriptTag = document.currentScript;
6
 
7
  (async function() {
@@ -9,121 +10,507 @@ const currentScriptTag = document.currentScript;
9
  let scriptTag = currentScriptTag;
10
  if (!scriptTag) {
11
  const scripts = document.getElementsByTagName('script');
12
- for (let s of scripts) {
13
- if (s.src.includes('interface.js') && s.hasAttribute('data-config')) {
14
- scriptTag = s;
15
  break;
16
  }
17
  }
18
- if (!scriptTag && scripts.length) scriptTag = scripts[scripts.length - 1];
 
 
19
  }
20
 
21
  const configUrl = scriptTag.getAttribute('data-config');
22
- if (!configUrl) {
23
- console.error("No config file provided. Please set a data-config attribute on the <script> tag.");
24
- return;
25
- }
26
-
27
  let config = {};
28
- try {
29
- const resp = await fetch(configUrl);
30
- config = await resp.json();
31
- } catch (err) {
32
- console.error("Error loading config file:", err);
 
 
 
 
 
33
  return;
34
  }
35
 
36
- // ─── 2. Conditionally inject CSS ───────────────────────────────────────────────
37
  if (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. Unique widget instance ID ───────────────────────────────────────────────
45
  const instanceId = Math.random().toString(36).substr(2, 8);
46
 
47
- // ─── 4. Compute aspect ratio padding ────────────────────────────────────────────
48
  let aspectPercent = "100%";
49
  if (config.aspect) {
50
  if (config.aspect.includes(":")) {
51
- const [w, h] = config.aspect.split(":").map(parseFloat);
52
- if (w > 0 && h > 0) aspectPercent = (h / w * 100) + "%";
53
- } else if (+config.aspect > 0) {
54
- aspectPercent = (100 / +config.aspect) + "%";
 
 
 
 
 
 
 
55
  }
56
  } else {
57
- const parent = scriptTag.parentNode;
58
- if (parent.offsetWidth && parent.offsetHeight) {
59
- aspectPercent = (parent.offsetHeight / parent.offsetWidth * 100) + "%";
 
 
60
  }
61
  }
62
 
63
- // ─── 5. Create container & inner HTML ──────────────────────────────────────────
64
  const widgetContainer = document.createElement('div');
65
  widgetContainer.id = 'ply-widget-container-' + instanceId;
66
  widgetContainer.classList.add('ply-widget-container');
67
- widgetContainer.style.height = "0";
68
  widgetContainer.style.paddingBottom = aspectPercent;
69
  widgetContainer.setAttribute('data-original-aspect', aspectPercent);
70
 
71
- // Conditionally include tooltips-toggle only if a URL is provided
72
- const tooltipBtn = config.tooltips_url
73
- ? `<button id="tooltips-toggle-${instanceId}" class="widget-button tooltips-toggle">β¦Ώ</button>`
74
  : '';
75
 
76
- widgetContainer.innerHTML = `
 
77
  <div id="viewer-container-${instanceId}" class="viewer-container">
78
  <div id="progress-dialog-${instanceId}" class="progress-dialog">
79
  <progress id="progress-indicator-${instanceId}" max="100" value="0"></progress>
80
  </div>
81
- </div>
82
-
83
- <button id="fullscreen-toggle-${instanceId}" class="widget-button fullscreen-toggle">⇱</button>
84
- <button id="help-toggle-${instanceId}" class="widget-button help-toggle">?</button>
85
- <button id="reset-camera-btn-${instanceId}" class="widget-button reset-camera-btn">⟲</button>
86
- ${tooltipBtn}
87
-
88
- <div id="menu-content-${instanceId}" class="menu-content" style="display: block;">
89
- <span id="help-close-${instanceId}" class="help-close">Γ—</span>
90
- <div class="help-text">
91
- ${(/iPad|iPhone|iPod/.test(navigator.userAgent) ? `
92
- - Pour vous dΓ©placer, glissez deux doigts sur l'Γ©cran.<br>
93
- - Pour orbiter, un doigt.<br>
94
- - Pour zoomer, pincez.<br>
95
- ` : `
96
- - Orbitez avec le clic droit.<br>
97
- - Zoomez avec la molette.<br>
98
- - DΓ©placez-vous avec le clic gauche.<br>
99
- `)}
100
- ${config.tooltips_url ? '- Cliquez sur β¦Ώ pour afficher/masquer les tooltips.<br>' : ''}
101
- ⟲ Réinitialise la caméra.<br>
102
- ⇱ Passe en plein Γ©cran.
103
  </div>
104
  </div>
105
-
106
  <div id="tooltip-panel" class="tooltip-panel" style="display: none;">
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" style="display: none;" />
 
 
110
  </div>
111
- `;
112
 
113
- // Insert into the page
114
  scriptTag.parentNode.appendChild(widgetContainer);
115
 
116
- // ─── 6. Grab elements & hook up listeners ─────────────────────────────────────
117
- // (all your existing listener code goes here, unchanged)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- // ─── 7. Load and initialize PlayCanvas viewer ─────────────────────────────────
 
120
  try {
121
- const viewerModule = await import('./viewer.js');
122
  await viewerModule.initializeViewer(config, instanceId);
123
  } catch (err) {
124
  console.error("Failed to load viewer.js or initialize the 3D viewer:", err);
 
125
  }
126
 
127
- // ─── 8. (rest of interface.js…) ───────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
- })();
 
2
  // interface.js
3
  // ==============================
4
 
5
+ // Store a reference to the <script> tag that loaded this file
6
  const currentScriptTag = document.currentScript;
7
 
8
  (async function() {
 
10
  let scriptTag = currentScriptTag;
11
  if (!scriptTag) {
12
  const scripts = document.getElementsByTagName('script');
13
+ for (let i = 0; i < scripts.length; i++) {
14
+ if (scripts[i].src.includes('interface.js') && scripts[i].hasAttribute('data-config')) {
15
+ scriptTag = scripts[i];
16
  break;
17
  }
18
  }
19
+ if (!scriptTag && scripts.length > 0) {
20
+ scriptTag = scripts[scripts.length - 1];
21
+ }
22
  }
23
 
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("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";
43
  linkEl.href = config.css_url;
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 and non-empty
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 + β¦Ώ toggle + tooltip + help HTML
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>
102
+ <div class="help-text"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  </div>
104
  </div>
 
105
  <div id="tooltip-panel" class="tooltip-panel" style="display: none;">
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);
121
+ const helpCloseBtn = document.getElementById('help-close-' + instanceId);
122
+ const resetCameraBtn = document.getElementById('reset-camera-btn-' + 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 appropriate instructions
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
+ ;
150
+ } else {
151
+ helpTextDiv.innerHTML =
152
+ - orbitez avec le clic droit<br>
153
+ - zoomez avec la molette<br>
154
+ - dΓ©placez vous avec le clic gauche<br>
155
+ ${tooltipInstruction}
156
+ ;
157
+ }
158
+
159
+ // Ensure instructions panel is visible by default
160
+ menuContent.style.display = 'block';
161
+
162
+ viewerContainerElem.style.display = 'block';
163
+
164
+ // Variable to hold the drag-hide listener reference
165
+ let dragHide = null;
166
+
167
+ // Utility: hide tooltip and remove drag-hide listener
168
+ function hideTooltipPanel() {
169
+ if (dragHide) {
170
+ viewerContainerElem.removeEventListener('pointermove', dragHide);
171
+ dragHide = null;
172
+ }
173
+ tooltipPanel.style.display = 'none';
174
+ }
175
+
176
+ // Utility: hide instructions panel
177
+ function hideHelpPanel() {
178
+ menuContent.style.display = 'none';
179
+ }
180
 
181
+ // ─── 7. Dynamically load viewer.js ─────────────────────────────────────────
182
+ let viewerModule;
183
  try {
184
+ viewerModule = await import('./viewer.js');
185
  await viewerModule.initializeViewer(config, instanceId);
186
  } catch (err) {
187
  console.error("Failed to load viewer.js or initialize the 3D viewer:", err);
188
+ return;
189
  }
190
 
191
+ // After viewer is ready, grab the canvas element
192
+ const canvasId = 'canvas-' + instanceId;
193
+ const canvasEl = document.getElementById(canvasId);
194
+
195
+ // ─── 8. Conditional display of tooltips-toggle button ─────────────────────────
196
+ if (tooltipsToggleBtn) {
197
+ if (!config.tooltips_url) {
198
+ tooltipsToggleBtn.style.display = 'none';
199
+ } else {
200
+ fetch(config.tooltips_url)
201
+ .then(resp => {
202
+ if (!resp.ok) {
203
+ tooltipsToggleBtn.style.display = 'none';
204
+ }
205
+ })
206
+ .catch(() => {
207
+ tooltipsToggleBtn.style.display = 'none';
208
+ });
209
+ }
210
+ }
211
+
212
+ // ─── 9. Fullscreen / state-preservation logic ───────────────────────────────
213
+ let isFullscreen = false;
214
+ let savedState = null;
215
+
216
+ function saveCurrentState() {
217
+ if (isFullscreen) return;
218
+ const computedWidget = window.getComputedStyle(widgetContainer);
219
+ const computedViewer = window.getComputedStyle(viewerContainerElem);
220
+ const originalAspect = widgetContainer.getAttribute('data-original-aspect') || aspectPercent;
221
+ const containerWidth = widgetContainer.offsetWidth;
222
+ const containerHeight = widgetContainer.clientHeight || viewerContainerElem.offsetHeight;
223
+ const calculatedRatio = (containerHeight / containerWidth * 100) + '%';
224
+
225
+ savedState = {
226
+ widget: {
227
+ position: widgetContainer.style.position,
228
+ top: widgetContainer.style.top,
229
+ left: widgetContainer.style.left,
230
+ width: widgetContainer.style.width,
231
+ height: widgetContainer.style.height,
232
+ maxWidth: widgetContainer.style.maxWidth,
233
+ maxHeight: widgetContainer.style.maxHeight,
234
+ paddingBottom: widgetContainer.style.paddingBottom || originalAspect,
235
+ margin: widgetContainer.style.margin,
236
+ aspectPercent: originalAspect,
237
+ calculatedAspect: calculatedRatio,
238
+ computedWidth: computedWidget.width,
239
+ computedHeight: computedWidget.height,
240
+ offsetWidth: containerWidth,
241
+ offsetHeight: containerHeight
242
+ },
243
+ viewer: {
244
+ borderRadius: viewerContainerElem.style.borderRadius,
245
+ border: viewerContainerElem.style.border,
246
+ computedWidth: computedViewer.width,
247
+ computedHeight: computedViewer.height
248
+ }
249
+ };
250
+ }
251
+
252
+ function restoreOriginalStyles() {
253
+ if (!savedState) return;
254
+ let aspectToUse = aspectPercent;
255
+ if (savedState.widget.offsetWidth && savedState.widget.offsetHeight) {
256
+ const actualRatio = (savedState.widget.offsetHeight / savedState.widget.offsetWidth * 100) + '%';
257
+ aspectToUse = actualRatio;
258
+ } else if (savedState.widget.calculatedAspect) {
259
+ aspectToUse = savedState.widget.calculatedAspect;
260
+ } else if (savedState.widget.aspectPercent) {
261
+ aspectToUse = savedState.widget.aspectPercent;
262
+ } else if (savedState.widget.paddingBottom) {
263
+ aspectToUse = savedState.widget.paddingBottom;
264
+ }
265
+
266
+ widgetContainer.style.position = savedState.widget.position || "";
267
+ widgetContainer.style.top = savedState.widget.top || "";
268
+ widgetContainer.style.left = savedState.widget.left || "";
269
+ widgetContainer.style.width = "100%";
270
+ widgetContainer.style.height = "0";
271
+ widgetContainer.style.maxWidth = savedState.widget.maxWidth || "";
272
+ widgetContainer.style.maxHeight = savedState.widget.maxHeight || "";
273
+ widgetContainer.style.paddingBottom= aspectToUse;
274
+ widgetContainer.style.margin = savedState.widget.margin || "";
275
+ widgetContainer.style.border = savedState.widget.border || "";
276
+ widgetContainer.style.borderRadius = savedState.widget.borderRadius|| "";
277
+ widgetContainer.style.overflow = savedState.widget.overflow || "";
278
+ widgetContainer.classList.remove('fake-fullscreen');
279
+
280
+ viewerContainerElem.style.position = "absolute";
281
+ viewerContainerElem.style.top = "0";
282
+ viewerContainerElem.style.left = "0";
283
+ viewerContainerElem.style.right = "0";
284
+ viewerContainerElem.style.bottom = "0";
285
+ viewerContainerElem.style.width = "100%";
286
+ viewerContainerElem.style.height = "100%";
287
+ viewerContainerElem.style.borderRadius= savedState.viewer.borderRadius || "";
288
+ viewerContainerElem.style.border = savedState.viewer.border || "";
289
+
290
+ if (viewerModule.app) {
291
+ const cw = viewerContainerElem.clientWidth;
292
+ const ch = viewerContainerElem.clientHeight;
293
+ viewerModule.app.resizeCanvas(cw, ch);
294
+ }
295
+
296
+ savedState = null;
297
+ }
298
+
299
+ function applyFullscreenStyles() {
300
+ widgetContainer.style.position = 'fixed';
301
+ widgetContainer.style.top = '0';
302
+ widgetContainer.style.left = '0';
303
+ widgetContainer.style.width = '100vw';
304
+ widgetContainer.style.height = '100vh';
305
+ widgetContainer.style.maxWidth = '100vw';
306
+ widgetContainer.style.maxHeight = '100vh';
307
+ widgetContainer.style.paddingBottom= '0';
308
+ widgetContainer.style.margin = '0';
309
+ widgetContainer.style.border = 'none';
310
+ widgetContainer.style.borderRadius = '0';
311
+
312
+ viewerContainerElem.style.width = '100%';
313
+ viewerContainerElem.style.height = '100%';
314
+ viewerContainerElem.style.borderRadius= '0';
315
+ viewerContainerElem.style.border = 'none';
316
+
317
+ if (viewerModule.app) {
318
+ viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
319
+ }
320
+
321
+ fullscreenToggle.textContent = '⇲';
322
+ isFullscreen = true;
323
+ }
324
+
325
+ function enterFullscreen() {
326
+ if (!savedState) {
327
+ saveCurrentState();
328
+ }
329
+ if (isIOS) {
330
+ applyFullscreenStyles();
331
+ widgetContainer.classList.add('fake-fullscreen');
332
+ } else {
333
+ if (widgetContainer.requestFullscreen) {
334
+ widgetContainer.requestFullscreen()
335
+ .then(() => {
336
+ applyFullscreenStyles();
337
+ })
338
+ .catch(err => {
339
+ console.error("Fullscreen request failed:", err);
340
+ applyFullscreenStyles();
341
+ widgetContainer.classList.add('fake-fullscreen');
342
+ });
343
+ } else if (widgetContainer.webkitRequestFullscreen) {
344
+ widgetContainer.webkitRequestFullscreen();
345
+ applyFullscreenStyles();
346
+ } else if (widgetContainer.mozRequestFullScreen) {
347
+ widgetContainer.mozRequestFullScreen();
348
+ applyFullscreenStyles();
349
+ } else if (widgetContainer.msRequestFullscreen) {
350
+ widgetContainer.msRequestFullscreen();
351
+ applyFullscreenStyles();
352
+ } else {
353
+ applyFullscreenStyles();
354
+ widgetContainer.classList.add('fake-fullscreen');
355
+ }
356
+ }
357
+ }
358
+
359
+ function exitFullscreen() {
360
+ if (document.fullscreenElement === widgetContainer) {
361
+ if (document.exitFullscreen) {
362
+ document.exitFullscreen().catch(err => {
363
+ console.error("Error exiting fullscreen:", err);
364
+ });
365
+ } else if (document.webkitExitFullscreen) {
366
+ document.webkitExitFullscreen();
367
+ } else if (document.mozCancelFullScreen) {
368
+ document.mozCancelFullScreen();
369
+ } else if (document.msExitFullscreen) {
370
+ document.msExitFullscreen();
371
+ }
372
+ }
373
+
374
+ widgetContainer.classList.remove('fake-fullscreen');
375
+ restoreOriginalStyles();
376
+ isFullscreen = false;
377
+ }
378
+
379
+ // ─── 10. Hook up event listeners ───────────────────────────────────────────
380
+
381
+ fullscreenToggle.addEventListener('click', () => {
382
+ hideTooltipPanel();
383
+ if (!isFullscreen) {
384
+ enterFullscreen();
385
+ } else {
386
+ exitFullscreen();
387
+ }
388
+ });
389
+
390
+ document.addEventListener('fullscreenchange', () => {
391
+ if (document.fullscreenElement === widgetContainer) {
392
+ isFullscreen = true;
393
+ applyFullscreenStyles();
394
+ } else if (isFullscreen) {
395
+ isFullscreen = false;
396
+ restoreOriginalStyles();
397
+ }
398
+ });
399
+ document.addEventListener('webkitfullscreenchange', () => {
400
+ if (document.webkitFullscreenElement === widgetContainer) {
401
+ isFullscreen = true;
402
+ applyFullscreenStyles();
403
+ } else if (isFullscreen) {
404
+ isFullscreen = false;
405
+ restoreOriginalStyles();
406
+ }
407
+ });
408
+ document.addEventListener('mozfullscreenchange', () => {
409
+ if (document.mozFullScreenElement === widgetContainer) {
410
+ isFullscreen = true;
411
+ applyFullscreenStyles();
412
+ } else if (isFullscreen) {
413
+ isFullscreen = false;
414
+ restoreOriginalStyles();
415
+ }
416
+ });
417
+ document.addEventListener('MSFullscreenChange', () => {
418
+ if (document.msFullscreenElement === widgetContainer) {
419
+ isFullscreen = true;
420
+ applyFullscreenStyles();
421
+ } else if (isFullscreen) {
422
+ isFullscreen = false;
423
+ restoreOriginalStyles();
424
+ }
425
+ });
426
+
427
+ helpToggle.addEventListener('click', (e) => {
428
+ hideTooltipPanel();
429
+ e.stopPropagation();
430
+ menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
431
+ });
432
+
433
+ helpCloseBtn.addEventListener('click', () => {
434
+ hideHelpPanel();
435
+ });
436
+
437
+ resetCameraBtn.addEventListener('click', () => {
438
+ hideTooltipPanel();
439
+ if (viewerModule.resetViewerCamera) {
440
+ viewerModule.resetViewerCamera();
441
+ }
442
+ });
443
+
444
+ // β¦Ώ toggle button: only if it exists in the DOM
445
+ let tooltipsVisible = !!config.showTooltipsDefault;
446
+ if (tooltipsToggleBtn && tooltipsToggleBtn.style.display !== 'none') {
447
+ if (!tooltipsVisible) {
448
+ tooltipsToggleBtn.style.opacity = '0.5';
449
+ }
450
+ tooltipsToggleBtn.addEventListener('click', () => {
451
+ hideTooltipPanel();
452
+ tooltipsVisible = !tooltipsVisible;
453
+ tooltipsToggleBtn.style.opacity = tooltipsVisible ? '1' : '0.5';
454
+ document.dispatchEvent(new CustomEvent('toggle-tooltips', { detail: { visible: tooltipsVisible } }));
455
+ });
456
+ }
457
+
458
+ // Close tooltip panel on button click
459
+ tooltipCloseBtn.addEventListener('click', () => {
460
+ hideTooltipPanel();
461
+ });
462
+
463
+ // Listen for tooltip-selection events and show tooltip panel
464
+ document.addEventListener('tooltip-selected', (evt) => {
465
+ const { title, description, imgUrl } = evt.detail;
466
+ tooltipTextDiv.innerHTML = <strong>${title}</strong><br>${description};
467
+ if (imgUrl) {
468
+ tooltipImage.src = imgUrl;
469
+ tooltipImage.style.display = 'block';
470
+ } else {
471
+ tooltipImage.style.display = 'none';
472
+ }
473
+ tooltipPanel.style.display = 'flex';
474
+
475
+ // Attach drag-based hide listener
476
+ dragHide = (e) => {
477
+ if ((e.pointerType === 'mouse' && e.buttons !== 0) ||
478
+ (e.pointerType === 'touch')) {
479
+ hideTooltipPanel();
480
+ }
481
+ };
482
+ viewerContainerElem.addEventListener('pointermove', dragHide);
483
+ });
484
+
485
+ // Hide on any wheel event (zoom)
486
+ if (canvasEl) {
487
+ canvasEl.addEventListener('wheel', () => {
488
+ hideTooltipPanel();
489
+ }, { passive: true });
490
+ }
491
+
492
+ // Escape key also exits fullscreen
493
+ document.addEventListener('keydown', (e) => {
494
+ if ((e.key === 'Escape' || e.key === 'Esc') && isFullscreen) {
495
+ exitFullscreen();
496
+ }
497
+ });
498
+
499
+ // Window resize β†’ resize PlayCanvas canvas
500
+ window.addEventListener('resize', () => {
501
+ if (isFullscreen && viewerModule.app) {
502
+ viewerModule.app.resizeCanvas(window.innerWidth, window.innerHeight);
503
+ } else if (viewerModule.app) {
504
+ const cw = viewerContainerElem.clientWidth;
505
+ const ch = viewerContainerElem.clientHeight;
506
+ viewerModule.app.resizeCanvas(cw, ch);
507
+ }
508
+ });
509
+
510
+ // Save β€œinitial state” after a brief delay
511
+ setTimeout(() => {
512
+ saveCurrentState();
513
+ document.dispatchEvent(new CustomEvent('toggle-tooltips', { detail: { visible: tooltipsVisible } }));
514
+ }, 200);
515
 
516
+ })();