| /** | 
 |  * @module modules/ct-scaffold-sk | 
 |  * @description <h2><code>ct-scaffold-sk</code></h2> | 
 |  * | 
 |  * Contains the title bar, side bar, and error-toast for all the CT pages. The rest of | 
 |  * every CT page should be a child of this element. | 
 |  * | 
 |  * Has a spinner-sk that can be activated when it hears "begin-task" events and keeps | 
 |  * spinning until it hears an equal number of "end-task" events. | 
 |  * | 
 |  * The error-toast element responds to fetch-error events and normal error-sk events. | 
 |  * | 
 |  * @attr {string} app_title - The title to show in the page banner. | 
 |  * | 
 |  * @attr {boolean} testing_offline - If we should operate entirely in offline mode. | 
 |  */ | 
 | import { html } from 'lit-html'; | 
 | import { define } from '../../../elements-sk/modules/define'; | 
 | import { fromObject } from '../../../infra-sk/modules/query'; | 
 | import { jsonOrThrow } from '../../../infra-sk/modules/jsonOrThrow'; | 
 | import { errorMessage } from '../../../elements-sk/modules/errorMessage'; | 
 | import { SpinnerSk } from '../../../elements-sk/modules/spinner-sk/spinner-sk'; | 
 | import { ElementSk } from '../../../infra-sk/modules/ElementSk'; | 
 | import * as ctfe_utils from '../ctfe_utils'; | 
 |  | 
 | import '../../../infra-sk/modules/app-sk'; | 
 | import '../../../infra-sk/modules/alogin-sk'; | 
 | import '../../../infra-sk/modules/theme-chooser-sk'; | 
 |  | 
 | import '../../../elements-sk/modules/error-toast-sk'; | 
 | import '../../../elements-sk/modules/icons/assessment-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/find-in-page-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/folder-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/help-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/home-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/label-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/photo-camera-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/list-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/search-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/sync-problem-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/view-day-icon-sk'; | 
 | import '../../../elements-sk/modules/spinner-sk'; | 
 |  | 
 | import '../../../elements-sk/modules/icons/trending-up-icon-sk'; | 
 |  | 
 | import '../../../elements-sk/modules/icons/assessment-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/cloud-icon-sk'; | 
 |  | 
 | import '../../../elements-sk/modules/icons/build-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/person-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/reorder-icon-sk'; | 
 | import '../../../elements-sk/modules/icons/history-icon-sk'; | 
 |  | 
 | /** | 
 |  * Moves the elements from one NodeList to another NodeList. | 
 |  * | 
 |  * @param {NodeList} from - The list we are moving from. | 
 |  * @param {NodeList} to - The list we are moving to. | 
 |  */ | 
 | function move(from: HTMLCollection | NodeList, to: HTMLElement) { | 
 |   Array.prototype.slice.call(from).forEach((ele) => to.appendChild(ele)); | 
 | } | 
 |  | 
 | export class CtScaffoldSk extends ElementSk { | 
 |   private _main: HTMLElement | null = null; | 
 |  | 
 |   private _busyTaskCount: number = 0; | 
 |  | 
 |   private _spinner: SpinnerSk | null = null; | 
 |  | 
 |   private _task_queue_length: number = 0; | 
 |  | 
 |   constructor() { | 
 |     super(CtScaffoldSk.template); | 
 |   } | 
 |  | 
 |   private static template = (ele: CtScaffoldSk) => html` | 
 | <app-sk> | 
 |   <header> | 
 |     <h1>${ele.appTitle}</h1> | 
 |     <div class=spinner-spacer> | 
 |       <spinner-sk></spinner-sk> | 
 |     </div> | 
 |     <div class=spacer></div> | 
 |     <alogin-sk ?testing_offline=${ele.testingOffline}></alogin-sk> | 
 |     <theme-chooser-sk></theme-chooser-sk> | 
 |   </header> | 
 |  | 
 |   <aside> | 
 |     <nav class=surface-themes-sk> | 
 |       <a href="/chromium_perf/" tab-index=0> | 
 |         <trending-up-icon-sk></trending-up-icon-sk><span>Performance</span> | 
 |       </a> | 
 |       <a href="/chromium_analysis/" tab-index=0> | 
 |         <search-icon-sk></search-icon-sk><span>Analysis<span> | 
 |       </a> | 
 |       <a href="/metrics_analysis/" tab-index=0> | 
 |         <assessment-icon-sk></assessment-icon-sk><span>Metrics Analysis<span> | 
 |       </a> | 
 |       <a href="/admin_tasks/" tab-index=0> | 
 |         <person-icon-sk></person-icon-sk><span>Admin Tasks</span> | 
 |       </a> | 
 |       <a href="/queue/" tab-index=0> | 
 |         <reorder-icon-sk></reorder-icon-sk><span>View Queue (<b>${ele._task_queue_length}</b>)</span> | 
 |       </a> | 
 |       <a href="/history/" tab-index=0> | 
 |         <history-icon-sk></history-icon-sk><span>Runs History</span> | 
 |       </a> | 
 |       <a href="https://github.com/google/skia-buildbot/tree/main/ct" tab-index=0> | 
 |         <folder-icon-sk></folder-icon-sk><span>Code</span> | 
 |       </a> | 
 |       <a href="https://sites.google.com/corp/google.com/cluster-telemetry/home" tab-index=0> | 
 |         <help-icon-sk></help-icon-sk><span>Docs</span> | 
 |       </a> | 
 |     </nav> | 
 |   </aside> | 
 |  | 
 |   <main></main> | 
 |  | 
 |   <footer><error-toast-sk></error-toast-sk></footer> | 
 | </app-sk> | 
 | `; | 
 |  | 
 |   connectedCallback(): void { | 
 |     super.connectedCallback(); | 
 |     // Don't call more than once. | 
 |     if (this._main) { | 
 |       return; | 
 |     } | 
 |     this.addEventListener('begin-task', this._addBusyTask); | 
 |     this.addEventListener('end-task', this._finishedTask); | 
 |     this.addEventListener('fetch-error', this._fetchError); | 
 |  | 
 |     const allFetches: Promise<void>[] = []; | 
 |     ctfe_utils.taskDescriptors.forEach((obj) => { | 
 |       const queryParams = { | 
 |         size: 1, | 
 |         not_completed: true, | 
 |       }; | 
 |       const queryStr = `?${fromObject(queryParams)}`; | 
 |       allFetches.push( | 
 |         fetch(obj.get_url + queryStr, { method: 'POST' }) | 
 |           .then(jsonOrThrow) | 
 |           .then((json) => { | 
 |             this._task_queue_length += json.pagination.total; | 
 |           }) | 
 |           .catch(errorMessage) | 
 |       ); | 
 |     }); | 
 |     // We aren't using shadow dom so we need to manually move the children of | 
 |     // ct-scaffold-sk to be children of 'main'. We have to do this for the | 
 |     // existing elements and for all future mutations. | 
 |  | 
 |     // Create a temporary holding spot for elements we're moving. | 
 |     const div = document.createElement('div'); | 
 |     move(this.children, div); | 
 |  | 
 |     // Now that we've moved all the old children out of the way we can render | 
 |     // the template. | 
 |     this._render(); | 
 |  | 
 |     this._spinner = this.querySelector('header spinner-sk'); | 
 |  | 
 |     // Move the old children back under main. | 
 |     this._main = this.querySelector('main'); | 
 |     if (this._main) { | 
 |       move(div.children, this._main); | 
 |     } | 
 |  | 
 |     // Move all future children under main also. | 
 |     const observer = new MutationObserver((mutList) => { | 
 |       mutList.forEach((mut) => { | 
 |         if (this._main) { | 
 |           move(mut.addedNodes, this._main); | 
 |         } | 
 |       }); | 
 |     }); | 
 |     observer.observe(this, { childList: true }); | 
 |     // Once we've loaded the queue length, re-render. | 
 |     Promise.all(allFetches).then(() => this._render()); | 
 |   } | 
 |  | 
 |   disconnectedCallback(): void { | 
 |     super.disconnectedCallback(); | 
 |     this.removeEventListener('begin-task', this._addBusyTask); | 
 |     this.removeEventListener('end-task', this._finishedTask); | 
 |     this.removeEventListener('fetch-error', this._fetchError); | 
 |   } | 
 |  | 
 |   /** @prop appTitle {string} Reflects the app_title attribute for ease of use. */ | 
 |   get appTitle(): string { | 
 |     return this.getAttribute('app_title') || ''; | 
 |   } | 
 |  | 
 |   set appTitle(val: string) { | 
 |     this.setAttribute('app_title', val); | 
 |   } | 
 |  | 
 |   /** @prop {boolean} busy Indicates if there any on-going tasks (e.g. RPCs). | 
 |    *                  This also mirrors the status of the embedded spinner-sk. | 
 |    *                  Read-only. */ | 
 |   get busy(): boolean { | 
 |     return !!this._busyTaskCount; | 
 |   } | 
 |  | 
 |   /** @prop testingOffline {boolean} Reflects the testing_offline attribute for ease of use. | 
 |    */ | 
 |   get testingOffline(): boolean { | 
 |     return this.hasAttribute('testing_offline'); | 
 |   } | 
 |  | 
 |   set testingOffline(val: boolean) { | 
 |     if (val) { | 
 |       this.setAttribute('testing_offline', ''); | 
 |     } else { | 
 |       this.removeAttribute('testing_offline'); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * Indicate there are some number of tasks (e.g. RPCs) the app is waiting on | 
 |    * and should be in the "busy" state, if it isn't already. | 
 |    * | 
 |    */ | 
 |   _addBusyTask(): void { | 
 |     this._busyTaskCount++; | 
 |     if (this._spinner && this._busyTaskCount > 0) { | 
 |       this._spinner.active = true; | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * Removes one task from the busy count. If there are no more tasks to wait | 
 |    * for, the app will leave the "busy" state and emit the "busy-end" event. | 
 |    * | 
 |    */ | 
 |   _finishedTask(): void { | 
 |     this._busyTaskCount--; | 
 |     if (this._busyTaskCount <= 0) { | 
 |       this._busyTaskCount = 0; | 
 |       if (this._spinner) { | 
 |         this._spinner.active = false; | 
 |       } | 
 |       this.dispatchEvent(new CustomEvent('busy-end', { bubbles: true })); | 
 |     } | 
 |   } | 
 |  | 
 |   /** Handles a fetch error. Expects the detail of error to contain: | 
 |    *  error: the error given to fetch. | 
 |    *  loading: A string explaining what was being fetched. | 
 |    */ | 
 |   _fetchError(e: any): void { | 
 |     const loadingWhat = e.detail.loading; | 
 |     e = e.detail.error; | 
 |     if (e.name !== 'AbortError') { | 
 |       // We can ignore AbortError since they fire anytime an AbortController was canceled. | 
 |       // Chrome and Firefox report a DOMException in this case: | 
 |       // https://developer.mozilla.org/en-US/docs/Web/API/DOMException | 
 |       errorMessage( | 
 |         `Unexpected error loading ${loadingWhat}: ${e.message}`, | 
 |         5000 | 
 |       ); | 
 |     } | 
 |     this._finishedTask(); | 
 |   } | 
 | } | 
 |  | 
 | define('ct-scaffold-sk', CtScaffoldSk); |