1/* 2 * Copyright 2016 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16'use strict'; 17 18(function() { 19 var Marzipano = window.Marzipano; 20 var bowser = window.bowser; 21 var screenfull = window.screenfull; 22 var data = window.APP_DATA; 23 24 // Grab elements from DOM. 25 var panoElement = document.querySelector('#pano'); 26 var sceneNameElement = document.querySelector('#titleBar .sceneName'); 27 var sceneListElement = document.querySelector('#sceneList'); 28 var sceneElements = document.querySelectorAll('#sceneList .scene'); 29 var sceneListToggleElement = document.querySelector('#sceneListToggle'); 30 var autorotateToggleElement = document.querySelector('#autorotateToggle'); 31 var fullscreenToggleElement = document.querySelector('#fullscreenToggle'); 32 33 // Detect desktop or mobile mode. 34 if (window.matchMedia) { 35 var setMode = function() { 36 if (mql.matches) { 37 document.body.classList.remove('desktop'); 38 document.body.classList.add('mobile'); 39 } else { 40 document.body.classList.remove('mobile'); 41 document.body.classList.add('desktop'); 42 } 43 }; 44 var mql = matchMedia("(max-width: 500px), (max-height: 500px)"); 45 setMode(); 46 mql.addListener(setMode); 47 } else { 48 document.body.classList.add('desktop'); 49 } 50 51 // Detect whether we are on a touch device. 52 document.body.classList.add('no-touch'); 53 window.addEventListener('touchstart', function() { 54 document.body.classList.remove('no-touch'); 55 document.body.classList.add('touch'); 56 }); 57 58 // Use tooltip fallback mode on IE < 11. 59 if (bowser.msie && parseFloat(bowser.version) < 11) { 60 document.body.classList.add('tooltip-fallback'); 61 } 62 63 // Viewer options. 64 var viewerOpts = { 65 controls: { 66 mouseViewMode: data.settings.mouseViewMode, 67 scrollZoom: false 68 } 69 }; 70 71 // Initialize viewer. 72 var viewer = new Marzipano.Viewer(panoElement, viewerOpts); 73 74 // Create scenes. 75 var scenes = data.scenes.map(function(data) { 76 var urlPrefix = template_url+"/assets/img/tiles"; 77 var source = Marzipano.ImageUrlSource.fromString( 78 urlPrefix + "/" + data.id + "/{z}/{f}/{y}/{x}.jpg", 79 { cubeMapPreviewUrl: urlPrefix + "/" + data.id + "/preview.jpg" }); 80 var geometry = new Marzipano.CubeGeometry(data.levels); 81 82 var limiter = Marzipano.RectilinearView.limit.traditional(data.faceSize, 100*Math.PI/180, 120*Math.PI/180); 83 var view = new Marzipano.RectilinearView(data.initialViewParameters, limiter); 84 85 var scene = viewer.createScene({ 86 source: source, 87 geometry: geometry, 88 view: view, 89 pinFirstLevel: true 90 }); 91 92 // Create link hotspots. 93 data.linkHotspots.forEach(function(hotspot) { 94 var element = createLinkHotspotElement(hotspot); 95 scene.hotspotContainer().createHotspot(element, { yaw: hotspot.yaw, pitch: hotspot.pitch }); 96 }); 97 98 // Create info hotspots. 99 data.infoHotspots.forEach(function(hotspot) { 100 var element = createInfoHotspotElement(hotspot); 101 scene.hotspotContainer().createHotspot(element, { yaw: hotspot.yaw, pitch: hotspot.pitch }); 102 }); 103 104 return { 105 data: data, 106 scene: scene, 107 view: view 108 }; 109 }); 110 111 // Set up autorotate, if enabled. 112 var autorotate = Marzipano.autorotate({ 113 yawSpeed: 0.03, 114 // yawSpeed: 0, 115 targetPitch: 0, 116 targetFov: Math.PI/2 117 }); 118 119 // data.settings.autorotateEnabled = false; 120 if (data.settings.autorotateEnabled) { 121 autorotateToggleElement.classList.add('enabled'); 122 } 123 124 // Set handler for autorotate toggle. 125 autorotateToggleElement.addEventListener('click', toggleAutorotate); 126 127 // Set up fullscreen mode, if supported. 128 if (screenfull.enabled && data.settings.fullscreenButton) { 129 document.body.classList.add('fullscreen-enabled'); 130 fullscreenToggleElement.addEventListener('click', function() {
131 screenfull.toggle(); 132 }); 133 screenfull.on('change', function() { 134 if (screenfull.isFullscreen) { 135 fullscreenToggleElement.classList.add('enabled'); 136 } else { 137 fullscreenToggleElement.classList.remove('enabled'); 138 } 139 }); 140 } else { 141 document.body.classList.add('fullscreen-disabled'); 142 } 143 144 // Set handler for scene list toggle. 145 sceneListToggleElement.addEventListener('click', toggleSceneList); 146 147 // Start with the scene list open on desktop. 148 if (!document.body.classList.contains('mobile')) { 149 showSceneList(); 150 } 151 152 // Set handler for scene switch. 153 scenes.forEach(function(scene) { 154 var el = document.querySelector('#sceneList .scene[data-id="' + scene.data.id + '"]'); 155 el.addEventListener('click', function() { 156 switchScene(scene); 157 // On mobile, hide scene list after selecting a scene. 158 if (document.body.classList.contains('mobile')) { 159 hideSceneList(); 160 } 161 }); 162 }); 163 164 // DOM elements for view controls. 165 var viewUpElement = document.querySelector('#viewUp'); 166 var viewDownElement = document.querySelector('#viewDown'); 167 var viewLeftElement = document.querySelector('#viewLeft'); 168 var viewRightElement = document.querySelector('#viewRight'); 169 var viewInElement = document.querySelector('#viewIn'); 170 var viewOutElement = document.querySelector('#viewOut'); 171 172 // Dynamic parameters for controls. 173 var velocity = 0.7; 174 var friction = 3; 175 176 // Associate view controls with elements. 177 var controls = viewer.controls(); 178 controls.registerMethod('upElement', new Marzipano.ElementPressControlMethod(viewUpElement, 'y', -velocity, friction), true); 179 controls.registerMethod('downElement', new Marzipano.ElementPressControlMethod(viewDownElement, 'y', velocity, friction), true); 180 controls.registerMethod('leftElement', new Marzipano.ElementPressControlMethod(viewLeftElement, 'x', -velocity, friction), true); 181 controls.registerMethod('rightElement', new Marzipano.ElementPressControlMethod(viewRightElement, 'x', velocity, friction), true); 182 controls.registerMethod('inElement', new Marzipano.ElementPressControlMethod(viewInElement, 'zoom', -velocity, friction), true); 183 controls.registerMethod('outElement', new Marzipano.ElementPressControlMethod(viewOutElement, 'zoom', velocity, friction), true); 184 185 function sanitize(s) { 186 return s.replace('&', '&').replace('<', '<').replace('>', '>'); 187 } 188 189 function switchScene(scene) { 190 stopAutorotate(); 191 scene.view.setParameters(scene.data.initialViewParameters); 192 scene.scene.switchTo(); 193 startAutorotate(); 194 updateSceneName(scene); 195 updateSceneList(scene); 196 } 197 198 function updateSceneName(scene) { 199 sceneNameElement.innerHTML = sanitize(scene.data.name); 200 } 201 202 function updateSceneList(scene) { 203 for (var i = 0; i < sceneElements.length; i++) { 204 var el = sceneElements[i]; 205 if (el.getAttribute('data-id') === scene.data.id) { 206 el.classList.add('current'); 207 } else { 208 el.classList.remove('current'); 209 } 210 } 211 } 212 213 function showSceneList() { 214 sceneListElement.classList.add('enabled'); 215 sceneListToggleElement.classList.add('enabled'); 216 } 217 218 function hideSceneList() { 219 sceneListElement.classList.remove('enabled'); 220 sceneListToggleElement.classList.remove('enabled'); 221 } 222 223 function toggleSceneList() { 224 sceneListElement.classList.toggle('enabled'); 225 sceneListToggleElement.classList.toggle('enabled'); 226 } 227 228 function startAutorotate() { 229 if (!autorotateToggleElement.classList.contains('enabled')) { 230 return; 231 } 232 viewer.startMovement(autorotate); 233 viewer.setIdleMovement(3000, autorotate); 234 } 235 236 function stopAutorotate() { 237 viewer.stopMovement(); 238 viewer.setIdleMovement(Infinity); 239 } 240 241 function toggleAutorotate() { 242 if (autorotateToggleElement.classList.contains('enabled')) { 243 autorotateToggleElement.classList.remove('enabled'); 244 stopAutorotate(); 245 } else { 246 autorotateToggleElement.classList.add('enabled'); 247 startAutorotate(); 248 } 249 } 250 251 function createLinkHotspotElement(hotspot) { 252 253 // Create wrapper element to hold icon and tooltip. 254 var wrapper = document.createElement('div'); 255 wrapper.classList.add('hotspot'); 256 wrapper.classList.add('link-hotspot'); 257 258 // Create image element. 259 var icon = document.createElement('img'); 260 icon.src = template_url+'/assets/img/360/link.png'; 261 icon.classList.add('link-hotspot-icon'); 262 263 // Set rotation transform. 264 var transformProperties = [ '-ms-transform', '-webkit-transform', 'transform' ]; 265 for (var i = 0; i < transformProperties.length; i++) { 266 var property = transformProperties[i]; 267 icon.style[property] = 'rotate(' + hotspot.rotation + 'rad)'; 268 } 269 270 // Add click event handler. 271 wrapper.addEventListener('click', function() { 272 switchScene(findSceneById(hotspot.target)); 273 }); 274 275 // Prevent touch and scroll events from reaching the parent element.
276 // This prevents the view control logic from interfering with the hotspot. 277 stopTouchAndScrollEventPropagation(wrapper); 278 279 // Create tooltip element. 280 var tooltip = document.createElement('div'); 281 tooltip.classList.add('hotspot-tooltip'); 282 tooltip.classList.add('link-hotspot-tooltip'); 283 tooltip.innerHTML = findSceneDataById(hotspot.target).name; 284 285 wrapper.appendChild(icon); 286 wrapper.appendChild(tooltip); 287 288 return wrapper; 289 } 290 291 function createInfoHotspotElement(hotspot) { 292 293 // Create wrapper element to hold icon and tooltip. 294 var wrapper = document.createElement('div'); 295 wrapper.classList.add('hotspot'); 296 wrapper.classList.add('info-hotspot'); 297 298 // Create hotspot/tooltip header. 299 var header = document.createElement('div'); 300 header.classList.add('info-hotspot-header'); 301 302 // Create image element. 303 var iconWrapper = document.createElement('div'); 304 iconWrapper.classList.add('info-hotspot-icon-wrapper'); 305 var icon = document.createElement('img'); 306 icon.src = template_url+'/assets/img/360/info.png'; 307 icon.classList.add('info-hotspot-icon'); 308 iconWrapper.appendChild(icon); 309 310 // Create title element. 311 var titleWrapper = document.createElement('div'); 312 titleWrapper.classList.add('info-hotspot-title-wrapper'); 313 var title = document.createElement('div'); 314 title.classList.add('info-hotspot-title'); 315 title.innerHTML = hotspot.title; 316 titleWrapper.appendChild(title); 317 318 // Create close element. 319 var closeWrapper = document.createElement('div'); 320 closeWrapper.classList.add('info-hotspot-close-wrapper'); 321 var closeIcon = document.createElement('img'); 322 closeIcon.src = template_url+'/assets/img/360/close.png'; 323 closeIcon.classList.add('info-hotspot-close-icon'); 324 closeWrapper.appendChild(closeIcon); 325 326 // Construct header element. 327 header.appendChild(iconWrapper); 328 header.appendChild(titleWrapper); 329 header.appendChild(closeWrapper); 330 331 // Create text element. 332 var text = document.createElement('div'); 333 text.classList.add('info-hotspot-text'); 334 text.innerHTML = hotspot.text; 335 336 // Place header and text into wrapper element. 337 wrapper.appendChild(header); 338 wrapper.appendChild(text); 339 340 // Create a modal for the hotspot content to appear on mobile mode. 341 var modal = document.createElement('div'); 342 modal.innerHTML = wrapper.innerHTML; 343 modal.classList.add('info-hotspot-modal'); 344 document.body.appendChild(modal); 345 346 var toggle = function() { 347 wrapper.classList.toggle('visible'); 348 modal.classList.toggle('visible'); 349 }; 350 351 // Show content when hotspot is clicked. 352 wrapper.querySelector('.info-hotspot-header').addEventListener('click', toggle); 353 354 // Hide content when close icon is clicked. 355 modal.querySelector('.info-hotspot-close-wrapper').addEventListener('click', toggle); 356 357 // Prevent touch and scroll events from reaching the parent element. 358 // This prevents the view control logic from interfering with the hotspot. 359 stopTouchAndScrollEventPropagation(wrapper); 360 361 return wrapper; 362 } 363 364 // Prevent touch and scroll events from reaching the parent element. 365 function stopTouchAndScrollEventPropagation(element, eventList) { 366 var eventList = [ 'touchstart', 'touchmove', 'touchend', 'touchcancel', 367 'wheel', 'mousewheel' ]; 368 for (var i = 0; i < eventList.length; i++) { 369 element.addEventListener(eventList[i], function(event) { 370 event.stopPropagation(); 371 }); 372 } 373 } 374 375 function findSceneById(id) { 376 for (var i = 0; i < scenes.length; i++) { 377 if (scenes[i].data.id === id) { 378 return scenes[i]; 379 } 380 } 381 return null; 382 } 383 384 function findSceneDataById(id) { 385 for (var i = 0; i < data.scenes.length; i++) { 386 if (data.scenes[i].id === id) { 387 return data.scenes[i]; 388 } 389 } 390 return null; 391 } 392 393 // Display the initial scene. 394 switchScene(scenes[0]); 395 396})();
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.