1// Small Screen Variables 2const compactSize = 960; 3 4// Page Variables 5var activePage; 6var activePageId; 7var numOfPages = 0; 8var pages = {}; 9var pageByIndex = []; 10var pageByPosition = {}; 11var pageContainerEl; 12 13// Scrolling Variables 14var lastScrollPos = 0; 15var pageDelayMS = 750; 16var mouseWheelListener; 17var usingDefaultWheelBehavior = false; 18var isDoneScrolling = true; 19 20// Collections Variables 21var collapsibleObj = {}; 22 23// Misc Variables 24var clickedOnHeaderObj = false; 25 26// Custom Events 27let pageChangeEvent = new CustomEvent("changedPage", {detail: {}}); 28 29function initialize() { 30 pageContainerEl = document.getElementById("page-container"); 31 32 populatePages(); 33 setActivePage(); 34 initializeEventListeners(); 35 getProjectTabs(); 36 37 // Some browsers stay in last position when refreshing the site. Scroll to Home if so. 38 //if (navigator.userAgent.search("Firefox") > 0 && pageContainerEl.scrollTop != 0) 39 // scrollToPage("home"); 40} 41 42// Returns true if visitor is using a device that is not compatible with site. 43// Ex. If using Mac, user is more likely to use Safari or is using a laptop. 44// https://www.learningjquery.com/2017/05/how-to-use-javascript-to-detect-browser 45function isJankyDevice() { 46 return navigator.platform == "MacIntel" || navigator.userAgent.
46search("Firefox") > 0; 47} 48 49function isCompact() { 50 return window.innerWidth <= compactSize; 51} 52 53function makeDeviceFriendly() { 54 if (isCompact() || isJankyDevice()) 55 enableDefaultWheel(); 56 else 57 disableDefaultWheel(); 58} 59 60function populatePages() { 61 var pageElements = document.getElementsByClassName("page"); 62 numOfPages = pageElements.length; 63 pageByPosition = {}; // Clear every time we resize to save space. 64 65 for (var i = 0; i < numOfPages; i++) { 66 // Populate pages array with data. 67 pages[pageElements[i].id] = { 68 id: pageElements[i].id, 69 index: i, 70 position: pageElements[i].offsetTop 71 }; 72 73 // Array so we can look up a page id by referring to its index. 74 pageByIndex[i] = pageElements[i].id; 75 76 // Look up pages by its position. 77 pageByPosition[pageElements[i].offsetTop] = pageElements[i].id; 78 } 79} 80 81function initializeEventListeners() { 82 mouseWheelListener = function(e){mouseWheelToNextPage(e);}; 83 84 // Prevent scrolling from mouse wheel. Replace with "wheel to next page" behavior. 85 disableDefaultWheel(); 86 87 // Navigate through pages using keyboard. 88 window.addEventListener("keydown", function(e){onKeyboardNav(e)}); 89 90 // User should be allowed to scroll as normal while mousing over content container. 91 setScrollableElements("scrolling-content-container"); 92 setScrollableElements("collapsible-body"); 93 94 // Recalculate page positions after resizing window. 95 window.addEventListener("resize", function(e){makeDeviceFriendly(); populatePages();}); 96 97 // Listen for whenever the active page changes. 98 window.addEventListener("changedPage", function(e){updateActivePage(pageContainerEl);}); 99} 100 101function setScrollableElements(className) { 102 scrollableDiv = document.getElementsByClassName(className); 103 104 for (var i = 0; i < scrollableDiv.length; i++) { 105 scrollableDiv[i].addEventListener("mouseenter", function(e){enableDefaultWheel(this);}); 106 scrollableDiv[i].addEventListener("mouseleave", function(e){disableDefaultWheel()}); 107 } 108} 109 110function getProjectTabs() { 111 var tabObj = document.getElementsByClassName("collapsible-tab"); 112 var bodyObj = document.getElementsByClassName("collapsible-body"); 113 114 // Associate each tab with its body by the tab element as the key and its body as its value. 115 for (var i = 0; i < tabObj.length; i++) 116 collapsibleObj[tabObj[i].id] = bodyObj[i]; 117} 118 119function onKeyboardNav(event) { 120 // Navigation keys that have an affect on the page when pressed. 121 // PgUp, PgDown, Left, Up, Right, Down 122 validKeys = [33, 34, 37, 38, 39, 40]; 123 124 // Prevents strange bug that jumps page to top when pressing some keys. 125 if (!(validKeys.includes(event.keyCode))) 126 return; 127 128 nextPage = pages[activePageId].index; 129 130 // Determine if user is pressing (Left/Up/Page Up) or (Right/Down/Page Down) 131 if (event.keyCode === 37 || event.keyCode === 38 || event.keyCode === 33) 132 nextPage--; 133 else if (event.keyCode === 39 || event.keyCode === 40 || event.keyCode === 34) 134 nextPage++; 135 136 // If user attempts to scroll past all accessible pages, do nothing. 137 if (nextPage >= numOfPages || nextPage < 0) 138 return; 139 140 // Otherwise, scroll to next page. 141 scrollToPage(pageByIndex[nextPage]); 142} 143 144function enableDefaultWheel(el = null) { 145 /* Removed this feature due to glitchiness. 146 // If this div is small enough to fit on the screen without scrolling, 147 // then keep snapping to next page as normal when scrolling. 148 if (el && el.scrollHeight == el.offsetHeight) 149 return; 150 */ 151 152 // Otherwise, allow user to scroll through content without snapping to next page. 153 window.removeEventListener("wheel", mouseWheelListener); 154 usingDefaultWheelBehavior = true; 155} 156 157function disableDefaultWheel() { 158 if (isCompact() || isJankyDevice()) 159 return; 160 161 window.addEventListener("wheel", mouseWheelListener, {passive: false}); 162 usingDefaultWheelBehavior = false; 163} 164 165/*============*\ 166|| NAVIGATION || 167\*============*/ 168// What to do when user clicked on navigation. 169function clickedNav(element) { 170 scrollToPage(element.id.replace("nav-", "")); 171} 172 173// Scroll to indicated page id. 174function scrollToPage(pageId) { 175 page = document.getElementById(pageId); 176 page.scrollIntoView(); 177 178 if (!usingDefaultWheelBehavior) 179 switchPage(document.getElementById("nav-" + pageId)); 180 181 // Reenable disabling of default wheel behavior after done scrolling. 182 setTimeout(function(){isDoneScrolling = true; window.dispatchEvent(pageChangeEvent);}, pageDelayMS); 183} 184 185function setActivePage() { 186 // Get new variables. 187 activePage = document.getElementsByClassName("active-tab")[0]; 188 activePageId = activePage.id.replace("nav-", ""); 189} 190 191function updateActivePage(el) { 192 // How many pixels away from element top before we trigger the tab switch. 193 var tolerance = window.screen.height * 0.3; 194 195 // If scrolling by mouse wheel is stalled and active page tab is set without scrolling, 196 // go ahead and scroll to appropriate page. 197 if (Math.abs(el.scrollTop - pages[activePageId].position) >= tolerance) 198 scrollToPage(activePageId); 199} 200 201// Deal with some instances where active page tab doesn't update due to manual scrolling. 202function updateActivePageEdgeCase() { 203 // If current page does not match up with active tab, update the tab to match the page. 204 if (isDoneScrolling && pages[activePageId].position != pageContainerEl.scrollTop) { 205 if (!(pageContainerEl.scrollTop in pageByPosition)) 206 return; 207 208 actualPageId = pageByPosition[pageContainerEl.scrollTop]; 209 switchPage(document.getElementById("nav-" + actualPageId)); 210 } 211} 212 213function switchPage(element) { 214 // Remove active tag from previous active page and set to current page. 215 activePage.classList.remove("active-tab"); 216 element.classList.add("active-tab"); 217 setActivePage(); 218} 219 220function mouseWheelToNextPage(e) { 221 // Prevent scrolling from mouse wheel. 222 e.preventDefault(); 223 224 if (!isDoneScrolling) 225 return; 226 227 // Determine if user is scrolling up or down. 228 nextPage = e.deltaY > 0 ? pages[activePageId].index + 1 : pages[activePageId].index - 1; 229 230 // If user attempts to scroll past all accessible pages, do nothing. 231 if (nextPage >= numOfPages || nextPage < 0) 232 return; 233 234 // Otherwise, scroll to next page. 235 isDoneScrolling = false;
236 scrollToPage(pageByIndex[nextPage]); 237} 238 239/*==================*\ 240|| COLLAPSIBLE TABS || 241\*==================*/ 242// Collapse all tabs besides selected category. 243// Scroll to container if not already in view. 244function onClickProjShortcut(el) { 245 var projId = el.id.replace("-shortcut", ""); 246 247 for (var key in collapsibleObj) { 248 if (key == projId) 249 expandTab(collapsibleObj[projId], projId); 250 else 251 collapseTab(collapsibleObj[key], key); 252 } 253 254 document.getElementById("content-proj").scrollIntoView(); 255} 256 257// Expand or collapse a tab depending on its display status. 258function expandOrCollapseTab(el) { 259 if (collapsibleObj[el.id].classList.contains("collapsed")) 260 expandTab(collapsibleObj[el.id], el.id); 261 else 262 collapseTab(collapsibleObj[el.id], el.id); 263} 264 265function collapseTab(el, id) { 266 el.classList.remove("expanded"); 267 el.classList.add("collapsed"); 268 flipIndicator(id, true); 269} 270 271function expandTab(el, id) { 272 el.classList.remove("collapsed"); 273 el.classList.add("expanded"); 274 flipIndicator(id, false); 275} 276 277function flipIndicator(id, isExpanded) { 278 var ind = document.getElementById(id).getElementsByClassName("tab-indicator")[0]; 279 280 if (isExpanded) { 281 ind.classList.remove("indicator-collapse"); 282 ind.classList.add("indicator-expand"); 283 } 284 else { 285 ind.classList.remove("indicator-expand"); 286 ind.classList.add("indicator-collapse"); 287 } 288} 289 290/*==================*\ 291|| PROJECT EXPLORER || 292\*==================*/ 293// Expand or collapse a project tab depending on its display status. 294function expandOrCollapseProject(el) { 295 // Expand tab but do not collapse it if user pressed on header icon. 296 if (clickedOnHeaderObj) { 297 clickedOnHeaderObj = false; 298 return; 299 } 300 301 el.parentElement.classList.toggle("gc-hidden"); 302} 303 304function preventTabAction() { 305 clickedOnHeaderObj = true; 306} 307 308/*===================*\ 309|| PROJECT SPOTLIGHT || 310\*===================*/ 311function onClickThumbnail(el) { 312 var sector = el.parentElement.parentElement; 313 var thumbnail = el.getElementsByClassName("thumbnail-img")[0]; 314 315 // If image is already focused, do nothing. 316 if (thumbnail.classList.contains("img-focused")) 317 return; 318 319 // Otherwise, make it focused and make the currently focused image lose focus. 320 thumbnailLoseFocus(sector.getElementsByClassName("img-focused")[0]); 321 thumbnailGainFocus(thumbnail); 322 323 // Change the spotlight image to match the thumbnail. 324 var spotlight_active = sector.getElementsByClassName("spotlight-img-active")[0]; 325 var spotlight_hidden = sector.getElementsByClassName("spotlight-img-hidden")[0]; 326 var path = thumbnail.getAttribute("src"); 327 328 spotlight_hidden.setAttribute("src", path); 329 330 flipSpotlightVisibility(spotlight_hidden); 331 flipSpotlightVisibility(spotlight_active); 332 333} 334 335function thumbnailGainFocus(el) { 336 el.classList.remove("img-unfocused") 337 el.classList.add("img-focused") 338} 339 340function thumbnailLoseFocus(el) { 341 el.classList.add("img-unfocused") 342 el.classList.remove("img-focused") 343} 344 345function flipSpotlightVisibility(el) { 346 el.classList.toggle("spotlight-img-hidden"); 347 el.classList.toggle("spotlight-img-active"); 348} 349 350/*=======*\ 351|| AUDIO || 352\*=======*/ 353// Hide anything that is showing and display anything that was hidden. 354function flipAudioStatus(el) { 355 var hidden = el.getElementsByClassName("hidden")[0]; 356 var showing = el.getElementsByClassName("showing")[0]; 357 358 hidden.classList.remove("hidden"); 359 hidden.classList.add("showing"); 360 showing.classList.remove("showing"); 361 showing.classList.add("hidden"); 362} 363 364// Play audio and display the pause button. 365// Stop playing any audio that is currently playing. 366function playAudio(id) { 367 var allPlayers = document.getElementsByClassName("grid-audio-player"); 368 var proj = document.getElementById(id.replace("audio", "proj-music")); 369 var audio = document.getElementById(id); 370 371 // Stop playing other audio if any. 372 for (var i = 0; i < allPlayers.length; i++) { 373 if (allPlayers[i] == id) 374 continue; 375 376 if (!allPlayers[i].paused) 377 stopAudio(allPlayers[i].id); 378 } 379 380 // Reload so it starts from beginning upon pressing on play again. 381 audio.load(); 382 audio.play(); 383 384 flipAudioStatus(proj); 385} 386 387// Stop audio and display the play button. 388function stopAudio(id) { 389 var proj = document.getElementById(id.replace("audio", "proj-music")); 390 document.getElementById(id).pause(); 391 flipAudioStatus(proj); 392} 393
394// Displays and updates audio track time. 395// https://stackoverflow.com/questions/4993097/html5-display-audio-currenttime 396function updateAudioTimeTracker(audioEl, id) { 397 var proj = document.getElementById(id); 398 proj.getElementsByClassName('audio-time-tracker')[0].innerHTML = Math.floor(audioEl.currentTime) + ' / ' + Math.floor(audioEl.duration); 399}
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.