github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/public/elm-setup.js (about) 1 const renderingModulePromise = import('./index.mjs'); 2 3 const node = document.getElementById("elm-app-embed"); 4 if (node === null) { 5 throw "missing #elm-app-embed"; 6 } 7 8 const app = Elm.Main.init({ 9 node: node, 10 flags: window.elmFlags, 11 }); 12 13 var resizeTimer; 14 15 app.ports.pinTeamNames.subscribe(function(config) { 16 const sections = () => Array.from(document.querySelectorAll("." + config.sectionClass)); 17 const header = (section) => Array.from(section.childNodes).find(n => n.classList && n.classList.contains(config.sectionHeaderClass)); 18 const body = (section) => Array.from(section.childNodes).find(n => n.classList && n.classList.contains(config.sectionBodyClass)); 19 const lowestHeaderTop = (section) => body(section).offsetTop + body(section).scrollHeight - header(section).scrollHeight; 20 21 const pageHeaderHeight = () => config.pageHeaderHeight; 22 const viewportTop = () => window.pageYOffset + pageHeaderHeight(); 23 24 const updateHeader = (section) => { 25 var scrolledFarEnough = section.offsetTop < viewportTop(); 26 var scrolledTooFar = lowestHeaderTop(section) < viewportTop(); 27 if (!scrolledFarEnough && !scrolledTooFar) { 28 header(section).style.top = ""; 29 header(section).style.position = ""; 30 body(section).style.paddingTop = ""; 31 return 'static'; 32 } else if (scrolledFarEnough && !scrolledTooFar) { 33 header(section).style.position = 'fixed'; 34 header(section).style.top = pageHeaderHeight() + "px"; 35 body(section).style.paddingTop = header(section).scrollHeight + "px"; 36 return 'fixed'; 37 } else if (scrolledFarEnough && scrolledTooFar) { 38 header(section).style.position = 'absolute'; 39 header(section).style.top = lowestHeaderTop(section) + "px"; 40 return 'absolute'; 41 } else if (!scrolledFarEnough && scrolledTooFar) { 42 return 'impossible'; 43 } 44 } 45 46 const updateSticky = () => { 47 document.querySelector("." + config.pageBodyClass).style.marginTop = pageHeaderHeight(); 48 sections().forEach(updateHeader); 49 } 50 51 clearTimeout(resizeTimer); 52 resizeTimer = setTimeout(updateSticky, 250); 53 window.onscroll = updateSticky; 54 }); 55 56 app.ports.resetPipelineFocus.subscribe(function() { 57 renderingModulePromise.then(({resetPipelineFocus}) => resetPipelineFocus()); 58 }); 59 60 app.ports.renderPipeline.subscribe(function (values) { 61 const jobs = values[0]; 62 const resources = values[1]; 63 renderingModulePromise.then(({renderPipeline}) => 64 // elm 0.17 bug, see https://github.com/elm-lang/core/issues/595 65 setTimeout(() => renderPipeline(jobs, resources, app.ports.newUrl), 0) 66 ); 67 }); 68 69 app.ports.requestLoginRedirect.subscribe(function (message) { 70 var path = document.location.pathname; 71 var query = document.location.search; 72 var redirect = encodeURIComponent(path + query); 73 var loginUrl = "/login?redirect_uri="+ redirect; 74 document.location.href = loginUrl; 75 }); 76 77 78 app.ports.tooltip.subscribe(function (pipelineInfo) { 79 const pipelineName = pipelineInfo[0]; 80 const pipelineTeamName = pipelineInfo[1]; 81 82 const team = document.getElementById(pipelineTeamName); 83 if (team === null) { 84 return; 85 } 86 const card = team.querySelector(`.card[data-pipeline-name="${pipelineName}"]`); 87 if (card === null) { 88 return; 89 } 90 const title = card.querySelector('.dashboard-pipeline-name'); 91 if(title === null || title.offsetWidth >= title.scrollWidth) { 92 return; 93 } 94 title.parentNode.setAttribute('data-tooltip', pipelineName); 95 }); 96 97 app.ports.tooltipHd.subscribe(function (pipelineInfo) { 98 var pipelineName = pipelineInfo[0]; 99 var pipelineTeamName = pipelineInfo[1]; 100 101 const card = document.querySelector(`.card[data-pipeline-name="${pipelineName}"][data-team-name="${pipelineTeamName}"]`); 102 if (card === null) { 103 return; 104 } 105 const title = card.querySelector('.dashboardhd-pipeline-name'); 106 107 if(title === null || title.offsetWidth >= title.scrollWidth){ 108 return; 109 } 110 title.parentNode.setAttribute('data-tooltip', pipelineName); 111 }); 112 113 app.ports.saveToLocalStorage.subscribe(function(params) { 114 if (!params || params.length !== 2) { 115 return; 116 } 117 const [key, value] = params; 118 try { 119 localStorage.setItem(key, JSON.stringify(value)); 120 } catch(err) { 121 console.error(err); 122 } 123 }); 124 125 app.ports.saveToSessionStorage.subscribe(function(params) { 126 if (!params || params.length !== 2) { 127 return; 128 } 129 const [key, value] = params; 130 try { 131 sessionStorage.setItem(key, JSON.stringify(value)); 132 } catch(err) { 133 console.error(err); 134 } 135 }); 136 137 app.ports.loadFromLocalStorage.subscribe(function(key) { 138 const value = localStorage.getItem(key); 139 if (value === null) { 140 return; 141 } 142 setTimeout(function() { 143 app.ports.receivedFromLocalStorage.send([key, value]); 144 }, 0); 145 }); 146 147 app.ports.loadFromSessionStorage.subscribe(function(key) { 148 const value = sessionStorage.getItem(key); 149 if (value === null) { 150 return; 151 } 152 setTimeout(function() { 153 app.ports.receivedFromSessionStorage.send([key, value]); 154 }, 0); 155 }); 156 157 app.ports.deleteFromLocalStorage.subscribe(function(key) { 158 localStorage.removeItem(key); 159 }); 160 161 162 const csrfTokenKey = "csrf_token"; 163 const favoritedPipelinesKey = "favorited_pipelines"; 164 window.addEventListener('storage', function(event) { 165 if (event.key === csrfTokenKey || event.key === favoritedPipelinesKey) { 166 const value = localStorage.getItem(event.key); 167 setTimeout(function() { 168 app.ports.receivedFromLocalStorage.send([event.key, value]); 169 }, 0); 170 } 171 }, false); 172 173 app.ports.syncTextareaHeight.subscribe(function(id) { 174 const attemptToSyncHeight = () => { 175 const elem = document.getElementById(id); 176 if (elem === null) { 177 return false; 178 } 179 elem.style.height = "auto"; 180 elem.style.height = elem.scrollHeight + "px"; 181 return true; 182 }; 183 setTimeout(() => { 184 const success = attemptToSyncHeight(); 185 if (!success) { 186 // The element does not always exist by the time we attempt to sync 187 // Try one more time after a small delay 188 setTimeout(attemptToSyncHeight, 50); 189 } 190 }, 0); 191 }); 192 193 let syncStickyBuildLogHeadersInterval; 194 195 app.ports.syncStickyBuildLogHeaders.subscribe(function() { 196 if (!CSS || !CSS.supports || !CSS.supports('position', 'sticky')) { 197 return; 198 } 199 if (syncStickyBuildLogHeadersInterval != null) { 200 return; 201 } 202 const attemptToSync = () => { 203 const padding = 5; 204 const headers = document.querySelectorAll('.build-step .header:not(.loading-header)'); 205 if (headers.length === 0) { 206 return false; 207 } 208 headers.forEach(header => { 209 const parentHeader = findParentHeader(header); 210 let curHeight = 0; 211 if (parentHeader != null) { 212 const parentHeight = parsePixels(parentHeader.style.top || '') || 0; 213 curHeight = parentHeight + parentHeader.offsetHeight + padding; 214 } 215 header.style.top = curHeight + 'px'; 216 }); 217 return true; 218 } 219 220 setTimeout(() => { 221 const success = attemptToSync(); 222 if (!success) { 223 // The headers do not always exist by the time we attempt to sync. 224 // Keep trying on an interval 225 syncStickyBuildLogHeadersInterval = setInterval(() => { 226 const success = attemptToSync(); 227 if (success) { 228 clearInterval(syncStickyBuildLogHeadersInterval); 229 syncStickyBuildLogHeadersInterval = null; 230 } 231 }, 250); 232 } 233 }, 50); 234 }); 235 236 function findParentHeader(el) { 237 const closestStepBody = el.closest('.step-body'); 238 if (closestStepBody == null || closestStepBody.parentElement == null) { 239 return; 240 } 241 return closestStepBody.parentElement.querySelector('.header') 242 } 243 244 function parsePixels(raw) { 245 raw = raw.trim(); 246 if(!raw.endsWith('px')) { 247 return 0; 248 } 249 return parseFloat(raw); 250 } 251 252 app.ports.scrollToId.subscribe(function(params) { 253 if (!params || params.length !== 2) { 254 return; 255 } 256 const [parentId, toId] = params; 257 const padding = 150; 258 const interval = setInterval(function() { 259 const parentElem = document.getElementById(parentId); 260 if (parentElem === null) { 261 return; 262 } 263 const elem = document.getElementById(toId); 264 if (elem === null) { 265 return; 266 } 267 parentElem.scrollTop = offsetTop(elem, parentElem) - padding; 268 setTimeout(() => app.ports.scrolledToId.send([parentId, toId]), 50) 269 clearInterval(interval); 270 }, 20); 271 }); 272 273 function offsetTop(element, untilElement) { 274 let offsetTop = 0; 275 while(element && element != untilElement) { 276 offsetTop += element.offsetTop; 277 element = element.offsetParent; 278 } 279 return offsetTop; 280 } 281 282 app.ports.openEventStream.subscribe(function(config) { 283 var buffer = []; 284 var es = new EventSource(config.url); 285 function flush() { 286 if (buffer.length > 0) { 287 app.ports.eventSource.send(buffer); 288 buffer = []; 289 } 290 } 291 function dispatchEvent(event) { 292 buffer.push(event); 293 if (buffer.length > 1000) { 294 flush(); 295 } 296 } 297 es.onopen = dispatchEvent; 298 es.onerror = dispatchEvent; 299 config.eventTypes.forEach(function(eventType) { 300 es.addEventListener(eventType, dispatchEvent); 301 }); 302 app.ports.closeEventStream.subscribe(function() { 303 es.close(); 304 }); 305 setInterval(flush, 200); 306 }); 307 308 app.ports.checkIsVisible.subscribe(function(id) { 309 var interval = setInterval(function() { 310 var element = document.getElementById(id); 311 if (element) { 312 clearInterval(interval); 313 var isVisible = 314 element.getBoundingClientRect().left < window.innerWidth; 315 app.ports.reportIsVisible.send([id, isVisible]); 316 } 317 }, 20); 318 }); 319 320 app.ports.setFavicon.subscribe(function(url) { 321 var oldIcon = document.getElementById("favicon"); 322 var newIcon = document.createElement("link"); 323 newIcon.id = "favicon"; 324 newIcon.rel = "shortcut icon"; 325 newIcon.href = url; 326 if (oldIcon) { 327 document.head.removeChild(oldIcon); 328 } 329 330 document.head.appendChild(newIcon); 331 }); 332 333 app.ports.rawHttpRequest.subscribe(function(url) { 334 var xhr = new XMLHttpRequest(); 335 336 xhr.addEventListener('error', function(error) { 337 app.ports.rawHttpResponse.send('networkError'); 338 }); 339 xhr.addEventListener('timeout', function() { 340 app.ports.rawHttpResponse.send('timeout'); 341 }); 342 xhr.addEventListener('load', function() { 343 app.ports.rawHttpResponse.send('success'); 344 }); 345 346 xhr.open('GET', url, false); 347 348 try { 349 xhr.send(); 350 if (xhr.readyState === 1) { 351 app.ports.rawHttpResponse.send('browserError'); 352 } 353 } catch (error) { 354 app.ports.rawHttpResponse.send('networkError'); 355 } 356 }); 357 358 app.ports.renderSvgIcon.subscribe(function(icon, id) { 359 renderingModulePromise.then(({addIcon}) => addIcon(icon, (typeof id !== 'undefined') ? id : icon)); 360 }); 361 362 var clipboard = new ClipboardJS('#copy-token');