1import { QueryList } from "./queryModule.js?ver=1.2";//{ QueryList as queryList } 2import { PausAR_Popup as Popup } from "./popupModule.js?ver=1.2"; 3 4//Global variables, which are used throughout all AR functions 5let startARButtons = []; //Array of all available AR Buttons (or for current version) 6let widgetContainers = []; //Array of all available Widgets (or for current version) 7let modelPreviews = []; //JSON Array with ALL ModelViewers and sources (needed for fullscreen mode) 8let currentLoadingPreviews = new Map(); //A KeyMap of all "modelPreviews" 3D viewers (not just the current widget) that are currently loading a 3D model 9let queryPopup; 10let xrarSupport = false; 11let ignoreAutostart = false; //Set to FALSE, if the autostart was already triggert or a other ar scene did start 12//fullscreen Variables 13let originalOverflow = null; 14let originalTouchAction = null; 15const fullscreenFallbackStyles = document.createElement("style"); //Additional StylesheetRules for the fallback fullscreen 16let escapeListener; 17let fullscreenListener; 18let fullscreenTransition = false; 19 20//window.onmessage = function(event) { 21// iframeLinkOverwrite = event.data; 22//}; 23 24if (document.readyState === "complete" || document.readyState === "loaded") { 25 initAdditionalEvents(); 26} else { 27 document.addEventListener("DOMContentLoaded", initAdditionalEvents); 28} 29 30function initAdditionalEvents() { 31 32 QueryList.isXRARDevice().then(function(result) { 33 xrarSupport = result; 34 //Add Elementor Popup EventListener 35 if(typeof jQuery !== 'undefined' && jQuery != null) { 36 jQuery( document ).on( 'elementor/popup/show', ( event, id, instance ) => { 37 init(); 38 } ); 39 } 40 //run init() once, after DOMContentLoaded 41 init(); 42 }).catch( 43 function(err) { 44 xrarSupport = false; 45 console.warn("XR Query could not perform a navigator.xr test"); 46 init(); 47 } 48 ); 49 50} 51 52/** 53 * Adds Eventlisteners to all PausAR Elements 54 */ 55function init() { 56 57 let arSupport = true;//WebXR exluded 58 queryPopup = new Popup(); 59 60 //Non-Support Query (Device and OS) 61 if(QueryList.isAndroid()) { 62 if(QueryList.getAndroidVersion() < 7) { 63 arSupport = false; 64 } 65 } else if(QueryList.isIOS()) { 66 //if(!QueryList.isQuicklook()) { 67 // arSupport = false; 68 //} 69 } else if(!QueryList.isDesktop() && QueryList.hasTouchScreen()) { 70 //Windows-Phone or other misc mobile devices 71 arSupport = false; 72 } 73 74 //startARButtons = document.querySelectorAll(".pausAR-UI-ArButton[pausar_version='elementor']"); 75 //widgetContainers = document.querySelectorAll("div.pausAR-UI-widgetContainer[pausar_version='elementor']"); 76 startARButtons = document.querySelectorAll(".pausAR-UI-ArButton[pausar]"); 77 widgetContainers = document.querySelectorAll("div.pausAR-UI-widgetContainer[pausar]"); 78 for(let x = 0; x < widgetContainers.length; x++) { 79 let modelPreview = widgetContainers[x].querySelector("model-viewer.pausAR-UI-modelViewer[pausar]"); 80 if(modelPreview) { 81 modelPreviews.push({ 82 "viewer": modelPreview, 83 "widget": widgetContainers[x], 84 "touchAction" : modelPreview.getAttribute('touch-action'), 85 }); 86 } 87 } 88 89 //------------- 90 //Start Button Handler 91 //------------- 92 93 for(let i = 0; i < startARButtons.length; i++) { 94 95 validatePaths(startARButtons[i]); 96 97 let androidModel = (startARButtons[i].getAttribute("paus_glb") != null && startARButtons[i].getAttribute("paus_glb") != "") ? true : false; 98 let iosModel = (startARButtons[i].getAttribute("paus_ios") != null && startARButtons[i].getAttribute("paus_ios") != "") ? true : false; 99 100 101 //3D Model Query (Missing files?) 102 if((QueryList.isAndroid() && !androidModel) || (QueryList.isIOS() && !iosModel)) { 103 startARButtons[i].remove(); 104 } 105 106 if(!arSupport) { 107 startARButtons[i].remove(); 108 } else { 109 startARButtons[i].addEventListener('click', startModelSession); 110 } 111 112 } 113 114 //------------- 115 //Fullscreen Handler 116 //------------- 117 118 for(let j = 0; j < widgetContainers.length; j++) { 119 let innerFullscreenButton = widgetContainers[j].querySelector("div.pausAR-UI-modelPreviewContainer .pausAR-UI-modelPreviewInterface button.pausAR-UI-fullscreenButton[pausar]"); 120 if(innerFullscreenButton) { 121 innerFullscreenButton.addEventListener('click', function() { 122 toggleFullscreen(widgetContainers[j], innerFullscreenButton); 123 }); 124 innerFullscreenButton.classList.remove('pausAR-UI-elementHide'); 125 innerFullscreenButton.disabled = false;
126 } 127 128 } 129 130 //------------- 131 //Autostart 132 //------------- 133 134 let autoStartButton = null; 135 if(QueryList.getSearchParameter) { 136 let searchParameter = QueryList.getSearchParameter(false); 137 for(let q = 0; q < searchParameter.length; q++) { 138 if(searchParameter[q].name == QueryList.autostartParameterName) { 139 //Remove parameter 140 let sanatizedSearchParameter = removeArrayElement(searchParameter, q); 141 let replacedSearchParameter = ""; 142 for(let j = 0; j < sanatizedSearchParameter.length; j++) { 143 144 if(sanatizedSearchParameter[j].value != "") { 145 replacedSearchParameter += sanatizedSearchParameter[j].name + "=" + sanatizedSearchParameter[j].value; 146 } else { 147 replacedSearchParameter += sanatizedSearchParameter[j].name; 148 } 149 150 if(j < (sanatizedSearchParameter.length-1)) { 151 replacedSearchParameter += "&"; 152 } 153 } 154 if(replacedSearchParameter != "") { 155 replacedSearchParameter = "?" + replacedSearchParameter; 156 } 157 window.history.replaceState( {} , "", window.location.origin + window.location.pathname + replacedSearchParameter + window.location.hash ); 158 for(let r = 0; r < startARButtons.length; r++) { 159 if(startARButtons[r].getAttribute('id') == searchParameter[q].value) { 160 autoStartButton = startARButtons[r]; 161 break; 162 } 163 } 164 } 165 } 166 } 167 //Check if autostart can be activated 168 let autostartSupport = false; 169 if(QueryList.supportAutostart && autoStartButton != null) { 170 autostartSupport = QueryList.supportAutostart(autoStartButton); 171 } 172 if(autostartSupport == true) { 173 console.log("[PausAR]: Start AR scene \"" + autoStartButton.getAttribute("id") + "\" by autostart parameter."); 174 if(document.body.contains(autoStartButton)) { 175 if(modelPreviews.length > 0) { 176 queryPopup.openPopup('autostartLoader'); 177 queryPopup.addExternalCloseEventListener(function () { 178 ignoreAutostart = true;//aborting autostart 179 }); 180 function detectPreviewLoading(event) { 181 if (event.detail.reason == 'model-load') { 182 currentLoadingPreviews.set(event.target, true); 183 event.target.removeEventListener('progress', detectPreviewLoading);//Remove itself as a listener 184 } 185 } 186 function detectPreviewLoadingFinished(event) { 187 if (currentLoadingPreviews.has(event.target)) {//Loading of target has finished 188 currentLoadingPreviews.delete(event.target); 189 } 190 if (currentLoadingPreviews.size <= 0 && event.detail.visible == true) { 191 //All visible models have loaded. Trigger autostart 192 for (let xy = 0; xy < modelPreviews.length; xy++) { 193 if(modelPreviews[xy].viewer != null) { 194 modelPreviews[xy].viewer.removeEventListener("progress", detectPreviewLoading); 195 modelPreviews[xy].viewer.removeEventListener("model-visibility", detectPreviewLoadingFinished); 196 } 197 } 198 queryPopup.closePopup(); 199 if (!ignoreAutostart) { 200 startModelSession(autoStartButton); 201 } 202 event.target.removeEventListener('model-visibility', detectPreviewLoadingFinished);//Remove itself as a listener 203 } 204 } 205 for (let xy = 0; xy < modelPreviews.length; xy++) { 206 modelPreviews[xy].viewer.addEventListener("progress", detectPreviewLoading); 207 modelPreviews[xy].viewer.addEventListener("model-visibility", detectPreviewLoadingFinished); 208 } 209 } else { 210 //No 3D Preview on the website 211 if(!ignoreAutostart) { 212 startModelSession(autoStartButton); 213 } 214 } 215 } else { 216 console.warn("[PausAR]: Autostart failed. The ar scene is not fully configured."); 217 } 218 } 219 220 221}//init 222 223function startModelSession(ev) { 224 225 ignoreAutostart = true; 226 227 let arButton = null 228 229 if(typeof ev.getAttribute === 'function') { 230 if(typeof ev.getAttribute("pausar") === "string") { 231 arButton = ev; 232 } else { 233 return ; 234 } 235 } else { 236 arButton = getTargetButton(ev.target); 237 } 238 239 if(arButton == null) { 240 return ; 241 } 242 243 let widgetID = arButton.getAttribute("pausar"); 244 if(!widgetID) { 245 return ; 246 } 247 248 if(arButton.getAttribute('editmode') == 'true' || arButton.getAttribute('editmode') == true) { 249 console.info("[PausAR] AR Session could not be started inside the Editor"); 250 return ; 251 } 252 253 queryPopup.cloneColor(arButton); 254 255 if(arButton.getAttribute('paus_iframe') !== null) { 256 queryPopup.setFullscreen(); 257 let iframeHostLink = arButton.getAttribute('paus_iframe'); 258 if(typeof iframeHostLink === 'string' && iframeHostLink != "") { 259 if(QueryList.validateURL) { 260 iframeHostLink = QueryList.validateURL(decodeURIComponent(iframeHostLink)); 261 } else { 262 iframeHostLink = decodeURIComponent(iframeHostLink); 263 } 264 if(typeof iframeHostLink === 'string') { 265 queryPopup.setAnchor(arButton, iframeHostLink);//Overwrite Anchor for usage inside an iframe 266 } else { 267 queryPopup.setAnchor(arButton); 268 } 269 } else { 270 queryPopup.setAnchor(arButton); 271 } 272 } else { 273 queryPopup.setAnchor(arButton); 274 } 275 276 //Query 277 278 if(QueryList.isAndroid()) { 279 if(QueryList.getAndroidVersion() >= 7) { 280 //AR Core Device (Android) 281 282 if(QueryList.isFirefox()) { 283 queryPopup.openPopup("missingSupportAndroid"); 284 } else { 285 startSceneViewer(); 286 } 287 } else { 288 //Android Device without AR Core (no support) (not reachble) 289 queryPopup.openPopup("missingSupport"); 290 } 291 } else { 292 if(QueryList.isIOS()) { 293 //IOSDevice 294 if(QueryList.isLinkedIn() || QueryList.isInstagram() || QueryList.isFacebook()) { 295 queryPopup.openPopup("missingSupportQuicklookSocial"); 296 } else { 297 //iOS nonSocialBrowsers 298 //if(QueryList.isQuicklook()) { 299 //Quicklook 300 startQuicklook(); 301 //} else { 302 // //nonQuicklook (not reachable) 303 // queryPopup.openPopup("missingSupportQuicklook"); 304 //} 305 } 306 307 } else { 308 if(QueryList.isDesktop() || !QueryList.hasTouchScreen()) { 309 //Desktop Hint (QR Code) 310 queryPopup.openPopup("desktop"); 311 } else { 312 //Windows-Phones, misc Handheld devices, etc. (not reachable) 313 //queryPopup.setPopupState(); 314 queryPopup.openPopup("missingSupport"); 315 } 316 } 317 } 318 319 320 //internal Helpers 321 322 function startSceneViewer() { 323 324 let androidModelFile = (arButton.getAttribute("paus_glb") != null && arButton.getAttribute("paus_glb") != "") ? "file=" + arButton.getAttribute("paus_glb") : ""; 325 let androidModelTitle = (arButton.getAttribute("paus_scene_title") != null && arButton.getAttribute("paus_scene_title") != "") ? "title=" + (arButton.getAttribute("paus_scene_title")) + "&" : ""; //UserInput -> Encoded 326 let androidModelLink = (arButton.getAttribute("paus_scene_link") != null && arButton.getAttribute("paus_scene_link") != "") ? (arButton.getAttribute("paus_scene_link")) : ""; //UserInput -> Encoded 327 let androidModelResizable = (arButton.getAttribute("paus_scene_resizable") == true || arButton.getAttribute("paus_scene_resizable") == 'true' || arButton.getAttribute("paus_scene_resizable") == 1) ? "resizable=true&" : "resizable=false&"; 328 let verticalPlacement = arButton.getAttribute("paus_scene_placement") == 'vertical' ? "enable_vertical_placement=true&" : "enable_vertical_placement=false&"; 329 let androidSoundFile = (arButton.getAttribute("paus_scene_sound") != null && arButton.getAttribute("paus_scene_sound") != "") ? ("sound=" + arButton.getAttribute("paus_scene_sound") + "&") : ""; 330 let androidModelOcclusion = (arButton.getAttribute("paus_scene_occlusion") == true || arButton.getAttribute("paus_scene_occlusion") == 'true' || arButton.getAttribute("paus_scene_occlusion") == 1) ? "disable_occlusion=false" : "disable_occlusion=true"; 331 332 if(androidModelFile == "") { 333 return ; 334 } 335 336 //Validate the CallToAction Link: 337 338 //=================================== 339 //temporarily removed, due to an error when using the âcontainsâ condition. Naive validation is therefore preferred for the URL. 340 //----------------------------------- 341 /* 342 if(QueryList.validateURL) { 343 androidModelLink = QueryList.validateURL(decodeURIComponent(androidModelLink), false, true); 344 if(androidModelLink != null) { 345 androidModelLink = "link=" + encodeURIComponent(androidModelLink) + "&"; 346 } else { 347 androidModelLink = ""; 348 } 349 } else { 350 androidModelLink = ""; 351 } 352 */ 353 //-----------------------------------
354 //temporary replaced with naive validation (add protocol, if missing from string) 355 //----------------------------------- 356 androidModelLink = decodeURIComponent(androidModelLink); 357 if(androidModelLink.indexOf('http://') != 0 && androidModelLink.indexOf('https://') != 0) { 358 //adding missing https:// protocol 359 androidModelLink = "https://" + androidModelLink; 360 } 361 if(QueryList.validateURL) { 362 androidModelLink = QueryList.validateURL(androidModelLink, false, true); 363 if(androidModelLink != null && androidModelLink != "") { 364 androidModelLink = "link=" + androidModelLink + "&"; 365 } else { 366 androidModelLink = ""; 367 } 368 } else { 369 androidModelLink = ""; 370 } 371 //=================================== 372 373 const intentURL = "arvr.google.com/scene-viewer/1.1"; 374 const mode = "mode=ar_preferred"; 375 const scheme = "scheme=https"; 376 const svPackage = "package=com.google.ar.core";//"package=com.google.android.googlequicksearchbox;"; 377 const action = "action=android.intent.action.VIEW"; 378 const fallbackURL = "https://play.google.com/store/apps/details?id=com.google.ar.core&pcampaignid=web_share"; 379 380 //Link 381 let intentLink = 382 "intent://" + 383 intentURL + "?" + 384 androidModelFile + "&" + 385 mode + "&" + 386 androidModelTitle + //& included 387 androidModelLink + //& included 388 androidModelResizable + //& included 389 verticalPlacement + //& included 390 androidSoundFile + //& included 391 androidModelOcclusion + 392 "#Intent;" + 393 scheme + ";" + 394 svPackage + ";" + 395 action + ";" + 396 "end;"; 397 window.location.href = intentLink; 398 } 399 400 function startQuicklook() { 401 402 let iosModelFile = (arButton.getAttribute("paus_ios") != null && arButton.getAttribute("paus_ios") != "") ? arButton.getAttribute("paus_ios") : ""; 403 let iOSModelTitle = (arButton.getAttribute("paus_scene_title") != null && arButton.getAttribute("paus_scene_title") != "") ? "checkoutTitle=" + (arButton.getAttribute("paus_scene_title")) : ""; //UserInput -> Encoded 404 let iOSModelDescription = (arButton.getAttribute("paus_scene_description") != null && arButton.getAttribute("paus_scene_description") != "") ? "checkoutSubtitle=" + (arButton.getAttribute("paus_scene_description")) : "";//UserInput -> Encoded 405 let iOSModelLink = (arButton.getAttribute("paus_scene_link") != null && arButton.getAttribute("paus_scene_link") != "") ? arButton.getAttribute("paus_scene_link") : ""; //UserInput -> Encoded 406 let buttonText = (arButton.getAttribute("paus_scene_cta") != null && arButton.getAttribute("paus_scene_cta") != "") ? "callToAction=" + (arButton.getAttribute("paus_scene_cta")) : "callToAction=Visit"; //UserInput -> Encoded 407 let iosModelResizable = (arButton.getAttribute("paus_scene_resizable") == true || arButton.getAttribute("paus_scene_resizable") == 'true' || arButton.getAttribute("paus_scene_resizable") == 1) ? "allowsContentScaling=1" : "allowsContentScaling=0"; 408 if(iosModelFile == "") { 409 return ; 410 } 411 412 //Validate the CallToAction Link: 413 414 //=================================== 415 //temporarily removed, due to an error when using the âcontainsâ condition. Naive validation is therefore preferred for the URL. 416 //----------------------------------- 417 /* 418 if(QueryList.validateURL) { 419 iOSModelLink = QueryList.validateURL(decodeURIComponent(iOSModelLink), false, true); 420 if(iOSModelLink == null) { 421 buttonText = "callToAction=Close"; 422 } 423 } else { 424 buttonText = "callToAction=Close"; 425 } 426 */ 427 //-----------------------------------
428 //temporary replaced with naive validation (add protocol, if missing from string) 429 //----------------------------------- 430 iOSModelLink = decodeURIComponent(iOSModelLink); 431 if(iOSModelLink.indexOf('http://') != 0 && iOSModelLink.indexOf('https://') != 0) { 432 //adding missing https:// protocol 433 iOSModelLink = "https://" + iOSModelLink; 434 } 435 if(QueryList.validateURL) { 436 iOSModelLink = QueryList.validateURL(iOSModelLink, false, true); 437 if(iOSModelLink == null) { 438 buttonText = "callToAction=Close"; 439 } 440 } else { 441 iOSModelLink = null; 442 buttonText = "callToAction=Close"; 443 } 444 //=================================== 445 446 let filename = iosModelFile; 447 448 for(let i = filename.length-1; i >= 0; i--) { 449 if(filename[i] == "/" || filename[i] == "\\") { 450 filename = filename.substring(i+1, filename.length); 451 break; 452 } 453 } 454 455 let usdzURL = 456 iosModelFile +"#"+ 457 iosModelResizable +"&"+ 458 iOSModelTitle +"&"+ 459 iOSModelDescription +"&"+ 460 buttonText; 461 462 let downloadElement = null; 463 downloadElement = document.querySelector(".pausAR-UI-HiddenButtonQuicklook[pausar=" + arButton.getAttribute("pausar") + "]"); 464 465 if(downloadElement == null || typeof downloadElement === 'undefined') { 466 downloadElement = document.createElement("a"); 467 downloadElement.className = "pausAR-UI-HiddenButtonQuicklook"; 468 downloadElement.setAttribute("pausar", arButton.getAttribute("pausar")); 469 downloadElement.style.display = "none"; 470 downloadElement.style.zIndex = -1; 471 downloadElement.style.position = "fixed"; 472 downloadElement.style.left = -1000; 473 downloadElement.style.top = -1000; 474 downloadElement.style.visibility = "hidden"; 475 downloadElement.style.height = 0; 476 downloadElement.style.width = 0; 477 downloadElement.style.opacity = 0; 478 479 arButton.parentNode.appendChild(downloadElement); 480 downloadElement.appendChild(document.createElement("img")); 481 482 } 483 downloadElement.addEventListener("message", customCallToAction, false); 484 downloadElement.setAttribute("rel", "ar"); 485 downloadElement.href = usdzURL; 486 downloadElement.download = filename; 487 downloadElement.dispatchEvent( 488 new MouseEvent('click', { 489 bubbles: true, 490 cancelable: true, 491 view: window 492 }) 493 ); 494 495 function customCallToAction(event) { 496 if (event.data == "_apple_ar_quicklook_button_tapped") { 497 if(typeof iOSModelLink === 'string' && iOSModelLink != "") { 498 499 if(arButton.getAttribute('paus_iframe') !== null) { 500 //iframe mode 501 if (typeof top === 'object') { 502 try { 503 top.window.location.href = iOSModelLink; 504 return ; 505 } catch (err) { 506 console.warn("[PausAR]: iframe could not open link in the top window."); 507 } 508 509 } 510 if (window.parent == window.top) {//iframe is embedded directly on the host page and not nested in other iframes 511 try { 512 window.parent.location.href = iOSModelLink; 513 return ; 514 } catch (err) { 515 console.warn("[PausAR]: iframe could not open link in the parent window."); 516 } 517 } 518 } else { 519 //regular 520 window.location.href = iOSModelLink; 521 } 522 523 } 524 //downloadElement.remove(); 525 } 526 } 527 } 528 529} 530//=== 531 532 533//================================================================== 534// Helpers 535//================================================================== 536 537 538/** 539 * Recursively searches (upwards) for the reference to the selected AR button. 540 * Clicking on an icon or label does not return the correct target-element. 541 * @param {Element} target Target element that started the event. 542 * @returns 543 */ 544function getTargetButton(target) { 545 546 let bodyReached = false;
547 do { 548 for(let i = 0; i < startARButtons.length; i++) { 549 if(target === startARButtons[i]) { 550 return target; 551 } 552 } 553 if(target.parentNode) { 554 if(typeof target.parentNode === 'object') { 555 target = target.parentNode; 556 if(target === document.body) { 557 bodyReached = true; 558 } 559 } else { 560 bodyReached = true; 561 } 562 } else { 563 bodyReached = true; 564 } 565 } while(!bodyReached); 566 return null; 567} 568 569/** 570 * Recursively searches for the reference to the 3D preview of the selected AR button. 571 * @param {Element} target AR Button (DOM element) inside the pausAR widget 572 * @returns 573 */ 574function getTargetPreview(target) { 575 //Anchor 576 if(typeof target !== 'object') { 577 return null; 578 } 579 let validTarget = false; 580 for(let i = 0; i < startARButtons.length; i++) { 581 if(target === startARButtons[i]) { 582 validTarget = true; 583 } 584 } 585 if(!validTarget) { 586 return null; 587 } 588 //--- 589 let arButton = null; 590 let currentWidgetID; 591 arButton = target; 592 currentWidgetID = arButton.getAttribute('pausar'); 593 594 //get widget 595 let widgetContainer = null; 596 do { 597 for(let j = 0; j < target.classList.length; j++) { 598 if(target.classList[j] == "pausAR-UI-widgetContainer") { 599 widgetContainer = target; 600 break; 601 } 602 } 603 604 if(target.parentNode) { 605 if(typeof target.parentNode === 'object') { 606 target = target.parentNode; 607 if(target === document.body) { 608 return null; 609 } 610 } else { 611 return null; 612 } 613 } else { 614 return null; 615 } 616 } while(widgetContainer == null); 617 618 let modelPreview = widgetContainer.querySelector("model-viewer[pausar=" + currentWidgetID + "]"); 619 if(modelPreview != null) { 620 return modelPreview; 621 } 622 let imagePreview = widgetContainer.querySelector(".pausAR-UI-previewContainer"); 623 if(imagePreview != null) { 624 return imagePreview; 625 } 626 return null; 627 628} 629 630/** 631 * Checks all file paths (Android, Preview and iOS) for validity and corrects errors in specifying the protocols if the path points to the current host page. 632 * @param {Element} arButton DOM reference of a the AR Button 633 * @returns 634 */ 635function validatePaths(arButton) { 636 637 if(typeof arButton !== 'object') { 638 return ; 639 } 640 let validTarget = false;
641 for(let i = 0; i < startARButtons.length; i++) { 642 if(arButton === startARButtons[i]) { 643 validTarget = true; 644 } 645 } 646 if(!validTarget) { 647 return null; 648 } 649 650 if(arButton.getAttribute('editmode') == 'true' || arButton.getAttribute('editmode') == true) { 651 return ; 652 } 653 //--- 654 655 if(typeof QueryList.validateModelPath === 'function') { 656 //paus_glb 657 let pausAndroidSrc = arButton.getAttribute('paus_glb'); 658 if(typeof pausAndroidSrc === "string") { 659 arButton.setAttribute('paus_glb', QueryList.validateModelPath(pausAndroidSrc)); 660 } 661 662 //paus_ios 663 let pausIosSrc = arButton.getAttribute('paus_ios'); 664 if(typeof pausIosSrc === "string") { 665 arButton.setAttribute('paus_ios', QueryList.validateModelPath(pausIosSrc, true)); 666 } 667 668 //Preview src 669 let pausPreviewElement = getTargetPreview(arButton); 670 if(pausPreviewElement != null && typeof pausPreviewElement !== 'undefined') { 671 if(pausPreviewElement.tagName == 'MODEL-VIEWER') { 672 let pausPreviewSrc = pausPreviewElement.getAttribute("src"); 673 if(typeof pausPreviewSrc === "string") { 674 pausPreviewElement.setAttribute('src', QueryList.validateModelPath(pausPreviewSrc)); 675 } 676 } 677 } 678 679 } 680} 681 682/** 683 * Removes the element with the specified index from an array 684 * @param {Array} arr The corresponding array 685 * @param {Number} index Index of the element to be removed 686 * @returns {Array | undefined} The reduced array | Invalid parameters return nothing 687 */ 688function removeArrayElement(arr, index) { 689 690 if(typeof arr === 'undefined' || arr == null) { 691 return ; 692 } 693 if(typeof arr.length !== 'number' || typeof arr === "string") { 694 return ; 695 } 696 if(typeof index !== 'number') { 697 return arr; 698 } 699 if(index < 0 || index > arr.length) { 700 return arr; 701 } 702 703 return arr.slice(0, index).concat(arr.slice(index+1, arr.length)); 704 705 } 706 707//=================================== 708// Fullscreen Functions 709//=================================== 710 711 712async function toggleFullscreen(container, targetButton) { 713 714 715 if(fullscreenTransition == true) { 716 //console.warn("[PausAR]: Fullscreen in transition"); 717 return ; 718 } 719 720 let popupInstance = document.querySelector(".pausAR-UI-popupContainerDialog"); 721 if(popupInstance) { 722 return ; 723 } 724 725 if(typeof container === 'undefined' || container == null) {return ;} 726 if(typeof targetButton === 'undefined' || targetButton == null) {return ;} 727 if(container.getAttribute('editmode') == 'true' || container.getAttribute('editmode') == true) {return ;} 728 if(container.getAttribute('paus_iframe') !== null) {return ;} 729 730 fullscreenTransition = true; 731 732 if(isWidgetFullscreenActive(container)) {//Deactivate fullscreen 733 734 if(isFullscreenClassActive()) { 735 closeFullscreenClasses(); 736 } 737 if(isFullscreenAPIActive()) { 738 await closeFullscreenAPI(); 739 } 740 741 loadViewers(); 742 changePanY(container); 743 744 fullscreenTransition = false; 745 746 } else {//Activate (another) fullscreen 747 748 //Check, if other widgets are in fullscreen mode 749 if(isFullscreenClassActive()) { 750 closeFullscreenClasses(); 751 } 752 if(isFullscreenAPIActive()) { 753 await closeFullscreenAPI(); 754 } 755 let fullScreenActivation = await openFullscreenAPI(container); 756 if(fullScreenActivation == false) { 757 openFullscreenClasses(container);//Fallback, if fullscreen API is not available 758 } else if(fullScreenActivation == true) { 759 openFullscreenClasses(container, true);//Fullscreen API active. Just add the classes, without the 'fallback'. 760 } else { 761 //console.error("[PausAR]: No container or widget was given for the fullscreen request"); 762 fullscreenTransition = false; 763 return ; 764 } 765 766 unloadViewers(container); 767 changePanY(container, true); 768 769 fullscreenTransition = false;
770 771 } 772} 773 774/** 775 * Checks if the given widget Container contains the 'fullscreen' class. 776 * @param {Element} widgetElement Reference to the widget container 777 * @returns 778 */ 779function isWidgetFullscreenActive(widgetElement) { 780 if(typeof widgetElement === 'undefined' || widgetElement == null) { 781 return false; 782 } 783 // 784 for(let j = 0; j < widgetElement.classList.length; j++) { 785 if(widgetElement.classList[j] == "pausAR-UI-widgetFullscreen") { 786 return true; 787 } 788 } 789 return false; 790} 791 792function isFullscreenClassActive() { 793 for(let i = 0; i < widgetContainers.length; i++) { 794 for(let j = 0; j < widgetContainers[i].classList.length; j++) { 795 if(widgetContainers[i].classList[j] == "pausAR-UI-widgetFullscreen") { 796 return true; 797 } 798 } 799 } 800 return false; 801} 802 803function isFullscreenAPIActive() { 804 805 if(document.fullscreenElement) { 806 if(document.fullscreenElement.getAttribute("pausar") != null) { 807 return true; 808 } 809 } 810 if(document.webkitFullscreenElement) { 811 if(document.webkitFullscreenElement.getAttribute("pausar") != null) { 812 return true; 813 } 814 } 815 if(document.msFullscreenElement) { 816 if(document.msFullscreenElement.getAttribute("pausar") != null) { 817 return true; 818 } 819 } 820 return false; 821} 822 823/** 824 * Removes all fullscreen classes from all widgets and removes the eventListeners. 825 */ 826function closeFullscreenClasses() { 827 for(let i = 0; i < widgetContainers.length; i++) { 828 for(let j = 0; j < widgetContainers[i].classList.length; j++) { 829 if(widgetContainers[i].classList[j] == "pausAR-UI-widgetFullscreen") { 830 widgetContainers[i].classList.remove("pausAR-UI-widgetFullscreen"); 831 break; 832 } 833 } 834 } 835 836 if(typeof originalOverflow === 'string') { 837 document.body.style.overflow = originalOverflow; 838 originalOverflow = null; 839 } 840 if(typeof originalTouchAction === 'string') { 841 document.body.style.touchAction = originalTouchAction; 842 originalTouchAction = null; 843 } 844 845 846 if(fullscreenFallbackStyles.isConnected) {//Remove additional Stylesheet for the fallback 847 fullscreenFallbackStyles.remove();//Remove element from DOM 848 if(typeof fullscreenFallbackStyles.sheet !== 'undefined' && fullscreenFallbackStyles.sheet != null) { 849 let currentRules = fullscreenFallbackStyles.sheet.rules; 850 for(let r = 0; r < currentRules.length; r++) { 851 if(typeof fullscreenFallbackStyles.sheet.removeRule === 'function') { 852 fullscreenFallbackStyles.sheet.removeRule(currentRules[r]); 853 } else if(typeof fullscreenFallbackStyles.sheet.deleteRule === 'function') { 854 fullscreenFallbackStyles.sheet.deleteRule(currentRules[r]); 855 } 856 } 857 } 858 859 } 860 861 if(escapeListener != null) { 862 document.removeEventListener('keydown', escapeListener); 863 escapeListener = null; 864 } 865} 866 867/** 868 * Async Function. 869 * Closes all active fullscreen widgets and removes the eventListeners. 870 */ 871async function closeFullscreenAPI() { 872 if(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement) { 873 874 if (document.exitFullscreen) { 875 await document.exitFullscreen(); 876 } else if (document.webkitExitFullscreen) { /* Safari */ 877 await document.webkitExitFullscreen(); 878 } else if(document.webkitCancelFullScreen) { /* Safari (alternative) */ 879 await document.webkitCancelFullScreen(); 880 } else if (document.msExitFullscreen) { /* IE11 */ 881 await document.msExitFullscreen(); 882 } 883 884 } 885 886 if(fullscreenListener != null) { 887 document.removeEventListener('fullscreenchange', fullscreenListener); 888 document.removeEventListener('webkitfullscreenchange', fullscreenListener); 889 fullscreenListener = null; 890 } 891} 892 893function openFullscreenClasses(widget, ignoreEvents) { 894 895 if(typeof widget === 'undefined' || widget == null) { 896 return ; 897 } 898 // 899 widget.classList.add('pausAR-UI-widgetFullscreen'); 900 901 originalOverflow = document.body.style.overflow; 902 originalTouchAction = document.body.style.touchAction; 903 //document.body.style.overflow = "hidden"; 904 //document.body.style.touchAction = "none"; 905 document.body.style.setProperty("overflow", "hidden", "important"); 906 document.body.style.setProperty("touch-action", "none", "important"); 907 908 if(!ignoreEvents) {//Fallback 909 if (!fullscreenFallbackStyles.isConnected) { 910 document.head.appendChild(fullscreenFallbackStyles); 911 //fullscreenFallbackStyles.textContent = ""; 912 } 913 //z-index reset for all other elements, that not belong to the current widget 914 fullscreenFallbackStyles.sheet.insertRule('body *:not(.pausAR-UI-widgetContainer.pausAR-UI-widgetFullscreen[pausar='+widget.getAttribute("pausar")+'], .pausAR-UI-widgetContainer.pausAR-UI-widgetFullscreen[pausar='+widget.getAttribute("pausar")+'] * ) {\ 915 z-index: unset !important;\ 916 }'); 917 } 918 //------------------- 919 920 //Events 921 if (escapeListener == null && !ignoreEvents) { 922 escapeListener = async function (evt) { 923 evt = evt || window.event; 924 var isEscape = false;
925 if ("key" in evt) { 926 isEscape = (evt.key === "Escape" || evt.key === "Esc"); 927 } else { 928 isEscape = (evt.keyCode === 27); 929 } 930 if (isEscape) { 931 //Close Fullscreen 932 if(fullscreenTransition == true) { 933 return ; 934 } 935 fullscreenTransition = true; 936 if(isFullscreenClassActive()) { 937 closeFullscreenClasses(); 938 } 939 if(isFullscreenAPIActive()) { 940 await closeFullscreenAPI(); 941 } 942 loadViewers(); 943 fullscreenTransition = false; 944 } 945 }; 946 document.addEventListener("keydown", escapeListener); 947 } 948} 949 950async function openFullscreenAPI(widget) { 951 return new Promise(async function(resolve) { 952 if(typeof widget === 'undefined' || widget == null) { 953 resolve("no widget found"); 954 return ; 955 } 956 //Check for API availability or allready activated fullscreen 957 if (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled) { 958 if (typeof widget.requestFullscreen === 'function') { 959 try { 960 await widget.requestFullscreen(); 961 } catch (error) { 962 resolve(false); 963 return ; 964 } 965 fullscreenAPIRequested(); 966 } else if (typeof widget.webkitRequestFullscreen === 'function') { /* Safari */ 967 try { 968 await widget.webkitRequestFullscreen(); 969 } catch (error) { 970 resolve(false); 971 return ; 972 } 973 fullscreenAPIRequested("webkit"); 974 } else if (typeof widget.msRequestFullscreen === 'function') { /* IE11 */ 975 try { 976 await widget.msRequestFullscreen(); 977 } catch (error) { 978 resolve(false); 979 return ; 980 } 981 fullscreenAPIRequested(); 982 } else { 983 console.warn("No Fullscreen API found"); 984 resolve(false);//No Fullscreen API found 985 return ; 986 } 987 resolve(true); 988 return ; 989 //Listener 990 function fullscreenAPIRequested(apiType) { 991 /*//Lock orientation to landscape 992 if(typeof screen !== 'undefined' || screen != null) { 993 if(typeof screen.oriantation !== 'undefined' || screen.orientation != null) { 994 if(typeof screen.orientation.lock === 'function') { 995 screen.orientation.lock('landscape-primary').catch(function() {console.warn("[PausAR] Screen.orientation.lock is not supported");}); 996 } 997 } 998 } 999 */ 1000 1001 if(fullscreenListener == null) { 1002 fullscreenListener = async function() { 1003 if(!document.fullscreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) { //fullscreen API closed 1004 //Clean up style changes 1005 if(isFullscreenClassActive()) { 1006 closeFullscreenClasses(); 1007 } 1008 loadViewers(); 1009 fullscreenTransition = false; 1010 } 1011 }; 1012 switch (apiType) { 1013 case "webkit": 1014 document.addEventListener('webkitfullscreenchange', fullscreenListener); 1015 break; 1016 1017 default: 1018 document.addEventListener('fullscreenchange', fullscreenListener); 1019 break; 1020 } 1021 } 1022 } 1023 } 1024 //No Fullscreen API found or allready requested 1025 resolve(false); 1026 return ; 1027 }); 1028} 1029 1030function unloadViewers(widget) { 1031 if(typeof widget === 'undefined' || widget == null) { 1032 return ; 1033 } 1034 // 1035 for(let i = 0; i < modelPreviews.length; i++) { 1036 if(widget != modelPreviews[i].widget) { 1037 modelPreviews[i].viewer.showPoster(); 1038 } else if(modelPreviews[i].viewer.loaded ) { 1039 modelPreviews[i].viewer.dismissPoster(); 1040 } 1041 } 1042} 1043 1044function loadViewers() { 1045 for(let i = 0; i < modelPreviews.length; i++) { 1046 modelPreviews[i].viewer.dismissPoster(); 1047 } 1048} 1049 1050function changePanY(widget, remove) { 1051 if(typeof widget === 'undefined' || widget == null) { 1052 return ; 1053 } 1054 if(typeof remove !== 'boolean') { 1055 remove = false;
1056 } 1057 // 1058 for(let i = 0; i < modelPreviews.length; i++) { 1059 if(widget == modelPreviews[i].widget) { 1060 if(remove) { 1061 modelPreviews[i].touchAction = modelPreviews[i].viewer.getAttribute("touch-action"); 1062 modelPreviews[i].viewer.removeAttribute("touch-action"); 1063 } else if(modelPreviews[i].touchAction != null) { 1064 modelPreviews[i].viewer.setAttribute("touch-action", modelPreviews[i].touchAction); 1065 } 1066 } 1067 } 1068}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.