| OLD | NEW |
| 1 /* | 1 /* |
| 2 Copyright 2016 The LUCI Authors. All rights reserved. | 2 Copyright 2016 The LUCI Authors. All rights reserved. |
| 3 Use of this source code is governed under the Apache License, Version 2.0 | 3 Use of this source code is governed under the Apache License, Version 2.0 |
| 4 that can be found in the LICENSE file. | 4 that can be found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 | 6 |
| 7 ///<reference path="../logdog-stream/logdog.ts" /> | 7 ///<reference path="../logdog-stream/logdog.ts" /> |
| 8 ///<reference path="../luci-operation/operation.ts" /> | 8 ///<reference path="../luci-operation/operation.ts" /> |
| 9 ///<reference path="../luci-sleep-promise/promise.ts" /> | 9 ///<reference path="../luci-sleep-promise/promise.ts" /> |
| 10 ///<reference path="../rpc/client.ts" /> | 10 ///<reference path="../rpc/client.ts" /> |
| (...skipping 10 matching lines...) Expand all Loading... |
| 21 /** No loading state (no status, loaded). */ | 21 /** No loading state (no status, loaded). */ |
| 22 NONE, | 22 NONE, |
| 23 /** Resolving a glob into the set of streams via query. */ | 23 /** Resolving a glob into the set of streams via query. */ |
| 24 RESOLVING, | 24 RESOLVING, |
| 25 /** Loading stream content, alternates with RENDERING. */ | 25 /** Loading stream content, alternates with RENDERING. */ |
| 26 LOADING, | 26 LOADING, |
| 27 /** Version of LOADING when the stream has been loading for a long time. */ | 27 /** Version of LOADING when the stream has been loading for a long time. */ |
| 28 LOADING_BEEN_A_WHILE, | 28 LOADING_BEEN_A_WHILE, |
| 29 /** Rendering loaded stream content. */ | 29 /** Rendering loaded stream content. */ |
| 30 RENDERING, | 30 RENDERING, |
| 31 /** No operations in progress, but the log isn't fully loaded.. */ |
| 32 PAUSED, |
| 31 /** Error: Attempt to load failed w/ "Unauthenticated". */ | 33 /** Error: Attempt to load failed w/ "Unauthenticated". */ |
| 32 NEEDS_AUTH, | 34 NEEDS_AUTH, |
| 33 /** Error: generic loading failure. */ | 35 /** Error: generic loading failure. */ |
| 34 ERROR, | 36 ERROR, |
| 35 } | 37 } |
| 36 | 38 |
| 37 /** Registered callbacks used by Model. Implemented by View. */ | 39 /** Registered callbacks used by Model. Implemented by View. */ |
| 38 export interface ViewBinding { | 40 export interface ViewBinding { |
| 39 pushLogEntries(entries: LogDog.LogEntry[], l: Location): void; | 41 pushLogEntries(entries: LogDog.LogEntry[], l: Location): void; |
| 40 clearLogEntries(): void; | 42 clearLogEntries(): void; |
| 41 | 43 |
| 42 updateControls(c: Controls): void; | 44 updateControls(c: Controls): void; |
| 43 locationIsVisible(l: Location): boolean; | 45 locationIsVisible(l: Location): boolean; |
| 44 } | 46 } |
| 45 | 47 |
| 46 /** Represents control visibility in the View. */ | 48 /** State of the split UI component. */ |
| 49 export enum SplitState { |
| 50 /** The UI cannot be split. */ |
| 51 CANNOT_SPLIT, |
| 52 /** The UI can be split, and isn't split. */ |
| 53 CAN_SPLIT, |
| 54 /** The UI cannot be split, and is split. */ |
| 55 IS_SPLIT, |
| 56 } |
| 57 |
| 58 /** |
| 59 * Represents state in the View. |
| 60 * |
| 61 * Modifying this will propagate to the view via "updateControls". |
| 62 */ |
| 47 type Controls = { | 63 type Controls = { |
| 48 /** Are we completely finished loading stream data? */ | |
| 49 canSplit: boolean; | |
| 50 /** Are we currently split? */ | |
| 51 split: boolean; | |
| 52 /** Show the bottom bar? */ | |
| 53 bottom: boolean; | |
| 54 /** Is the content fully loaded? */ | 64 /** Is the content fully loaded? */ |
| 55 fullyLoaded: boolean; | 65 fullyLoaded: boolean; |
| 66 /** True if we should show the split option to the user. */ |
| 67 splitState: SplitState; |
| 56 /** If not undefined, link to this URL for the log stream. */ | 68 /** If not undefined, link to this URL for the log stream. */ |
| 57 logStreamUrl: string | undefined; | 69 logStreamUrl: string | undefined; |
| 58 | 70 |
| 59 /** Text in the status bar. */ | 71 /** Text in the status bar. */ |
| 60 loadingState: LoadingState; | 72 loadingState: LoadingState; |
| 61 /** Stream status entries, or null for no status window. */ | 73 /** Stream status entries, or null for no status window. */ |
| 62 streamStatus: StreamStatusEntry[]; | 74 streamStatus: StreamStatusEntry[]; |
| 63 }; | 75 }; |
| 64 | 76 |
| 65 /** A value for the "status-bar" element. */ | 77 /** A value for the "status-bar" element. */ |
| (...skipping 15 matching lines...) Expand all Loading... |
| 81 logEnd: HTMLElement; | 93 logEnd: HTMLElement; |
| 82 logs: HTMLElement; | 94 logs: HTMLElement; |
| 83 }; | 95 }; |
| 84 | 96 |
| 85 // Polymer properties. | 97 // Polymer properties. |
| 86 streams: string[]; | 98 streams: string[]; |
| 87 streamLinkUrl: string | undefined; | 99 streamLinkUrl: string | undefined; |
| 88 mobile: boolean; | 100 mobile: boolean; |
| 89 isSplit: boolean; | 101 isSplit: boolean; |
| 90 metadata: boolean; | 102 metadata: boolean; |
| 91 follow: boolean; | |
| 92 playing: boolean; | 103 playing: boolean; |
| 93 backfill: boolean; | 104 backfill: boolean; |
| 94 | 105 |
| 95 /** Polymer read-only setter functions. */ | 106 /** Polymer read-only setter functions. */ |
| 96 _setStatusBar(v: StatusBarValue|null): void; | 107 _setStatusBar(v: StatusBarValue|null): void; |
| 108 |
| 109 _setShowPlayPause(v: boolean): void; |
| 110 _setShowStreamControls(v: boolean): void; |
| 111 _setShowSplitButton(v: boolean): void; |
| 112 _setShowSplitControls(v: boolean): void; |
| 113 |
| 97 _setCanSplit(v: boolean): void; | 114 _setCanSplit(v: boolean): void; |
| 98 _setIsSplit(v: boolean): void; | 115 _setIsSplit(v: boolean): void; |
| 99 _setShowStreamingControls(v: boolean): void; | 116 _setShowStreamingControls(v: boolean): void; |
| 100 _setStreamStatus(v: StreamStatusEntry[]): void; | 117 _setStreamStatus(v: StreamStatusEntry[]): void; |
| 101 | 118 |
| 102 /** Update functions. */ | 119 /** Update functions. */ |
| 103 _updateSplitVisible(v: boolean): void; | 120 _updateSplitVisible(v: boolean): void; |
| 104 _updateBottomVisible(v: boolean): void; | 121 _updateBottomVisible(v: boolean): void; |
| 105 | 122 |
| 106 /** Special Polymer callback to apply child styles. */ | 123 /** Special Polymer callback to apply child styles. */ |
| (...skipping 13 matching lines...) Expand all Loading... |
| 120 private onScrollHandler = | 137 private onScrollHandler = |
| 121 () => { | 138 () => { |
| 122 this.onScroll(); | 139 this.onScroll(); |
| 123 } | 140 } |
| 124 | 141 |
| 125 private scrollTimeoutId: number | | 142 private scrollTimeoutId: number | |
| 126 null = null; | 143 null = null; |
| 127 private model: Model|null = null; | 144 private model: Model|null = null; |
| 128 private renderedLogs = false; | 145 private renderedLogs = false; |
| 129 | 146 |
| 147 /** |
| 148 * We start out following by default. Every time we add new logs to the |
| 149 * bottom, we will scroll to them. If users scroll or pause, we will stop |
| 150 * following permanently. |
| 151 */ |
| 152 private following = true; |
| 153 |
| 130 constructor(readonly comp: Component) {} | 154 constructor(readonly comp: Component) {} |
| 131 | 155 |
| 132 /** Resets and reloads current viewer state. */ | 156 /** Resets and reloads current viewer state. */ |
| 133 reset() { | 157 reset() { |
| 134 this.detach(); | 158 this.detach(); |
| 135 | 159 |
| 136 // Create "onScrollHandler", which just invokes "_onScroll" while bound | 160 // Create "onScrollHandler", which just invokes "_onScroll" while bound |
| 137 // to "this". We create it here so we can unregister it later, since | 161 // to "this". We create it here so we can unregister it later, since |
| 138 // "bind" returns a modified value. | 162 // "bind" returns a modified value. |
| 139 window.addEventListener('scroll', this.onScrollHandler); | 163 window.addEventListener('scroll', this.onScrollHandler); |
| 140 | 164 |
| 141 this.resetScroll(); | 165 this.resetScroll(); |
| 142 this.renderedLogs = false; | 166 this.renderedLogs = false; |
| 143 | 167 |
| 144 // Instantiate our view, and install callbacks. | 168 // Instantiate our view, and install callbacks. |
| 145 let profile = | 169 let profile = |
| 146 ((this.comp.mobile) ? Model.MOBILE_PROFILE : Model.DEFAULT_PROFILE); | 170 ((this.comp.mobile) ? Model.MOBILE_PROFILE : Model.DEFAULT_PROFILE); |
| 147 this.model = | 171 this.model = |
| 148 new LogDog.Model(new luci.Client(this.comp.$.client), profile, this); | 172 new LogDog.Model(new luci.Client(this.comp.$.client), profile, this); |
| 149 this.handleStreamsChanged(); | 173 this.handleStreamsChanged(); |
| 150 } | 174 } |
| 151 | 175 |
| 152 /** Called to detach any resources used by View (Polymer "detach()"). */ | 176 /** Called to detach any resources used by View (Polymer "detach()"). */ |
| 153 detach() { | 177 detach() { |
| 154 window.removeEventListener('scroll', this.onScrollHandler); | 178 window.removeEventListener('scroll', this.onScrollHandler); |
| 155 this.model = null; | 179 this.model = null; |
| 156 } | 180 } |
| 157 | 181 |
| 158 /** Called when a mouse wheel event occurs. */ | 182 /** Called when a mouse wheel event occurs. */ |
| 159 handleMouseWheel() { | 183 handleMouseWheel(down: boolean) { |
| 160 this.comp.follow = false; | 184 // Once someone scrolls, stop following. |
| 185 if (!down) { |
| 186 this.following = false; |
| 187 } |
| 161 } | 188 } |
| 162 | 189 |
| 163 /** Called when the split "Down" button is clicked. */ | 190 /** Called when the split "Down" button is clicked. */ |
| 164 handleDownClick() { | 191 handleDownClick() { |
| 165 if (this.model) { | 192 if (this.model) { |
| 166 this.model.fetchLocation(Location.HEAD, true); | 193 this.model.fetchLocation(Location.HEAD, true); |
| 167 } | 194 } |
| 168 } | 195 } |
| 169 | 196 |
| 170 /** Called when the split "Up" button is clicked. */ | 197 /** Called when the split "Up" button is clicked. */ |
| (...skipping 16 matching lines...) Expand all Loading... |
| 187 return; | 214 return; |
| 188 } | 215 } |
| 189 | 216 |
| 190 await this.model.resolve(this.comp.streams); | 217 await this.model.resolve(this.comp.streams); |
| 191 | 218 |
| 192 // If we're not on mobile, start with playing state. | 219 // If we're not on mobile, start with playing state. |
| 193 this.comp.playing = (!this.comp.mobile); | 220 this.comp.playing = (!this.comp.mobile); |
| 194 | 221 |
| 195 // Perform the initial fetch after resolution. | 222 // Perform the initial fetch after resolution. |
| 196 if (this.model) { | 223 if (this.model) { |
| 197 this.model.setAutomatic(this.comp.playing); | 224 this.model.automatic = this.comp.playing; |
| 198 this.model.setFetchFromTail(!this.comp.backfill); | 225 this.model.setFetchFromTail(!this.comp.backfill); |
| 199 this.model.fetch(false); | 226 this.model.fetch(false); |
| 200 } | 227 } |
| 201 } | 228 } |
| 202 | 229 |
| 203 /** Called when the "playing" property value changes. */ | 230 stop() { |
| 204 handlePlayingChanged(v: boolean) { | |
| 205 if (this.model) { | 231 if (this.model) { |
| 206 // If we're playing, begin log fetching. | 232 this.model.automatic = false; |
| 207 this.model.setAutomatic(v); | 233 this.model.clearCurrentOperation(); |
| 208 } | 234 } |
| 209 } | 235 } |
| 210 | 236 |
| 237 /** Called when the "playing" property value changes. */ |
| 238 handlePlayPauseChanged(v: boolean) { |
| 239 if (this.model) { |
| 240 // If we're playing, begin log fetching. |
| 241 this.model.automatic = v; |
| 242 |
| 243 // Once someone manually uses this control, stop following. |
| 244 // |
| 245 // Only apply this after we've started rendering logs, since before that |
| 246 // this may toggle during setup. |
| 247 if (this.renderedLogs) { |
| 248 this.following = false; |
| 249 } |
| 250 |
| 251 if (!v) { |
| 252 this.model.clearCurrentOperation(); |
| 253 } |
| 254 } |
| 255 } |
| 256 |
| 211 /** Called when the "backfill" property value changes. */ | 257 /** Called when the "backfill" property value changes. */ |
| 212 handleBackfillChanged(v: boolean) { | 258 handleBackfillChanged(v: boolean) { |
| 213 if (this.model) { | 259 if (this.model) { |
| 214 // If we're backfilling, then we're not tailing. | 260 // If we're backfilling, then we're not tailing. |
| 215 this.model.setFetchFromTail(!v); | 261 this.model.setFetchFromTail(!v); |
| 216 } | 262 } |
| 217 } | 263 } |
| 218 | 264 |
| 219 /** Called when the "follow" property value changes. */ | |
| 220 handleFollowChanged(v: boolean) { | |
| 221 if (this.model) { | |
| 222 if (v) { | |
| 223 // If follow is toggled on, automatically begin playing. | |
| 224 this.comp.playing = true; | |
| 225 this.maybeScrollToFollow(); | |
| 226 } | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 /** Called when the "split" button is clicked. */ | 265 /** Called when the "split" button is clicked. */ |
| 231 handleSplitClicked() { | 266 handleSplitClicked() { |
| 232 if (!this.model) { | 267 if (!this.model) { |
| 233 return; | 268 return; |
| 234 } | 269 } |
| 235 | 270 |
| 236 // After a split, toggle off playing. | 271 // After a split, toggle off playing. |
| 272 this.model.setFetchFromTail(true); |
| 237 this.model.split(); | 273 this.model.split(); |
| 238 this.model.setFetchFromTail(true); | |
| 239 this.comp.playing = false; | 274 this.comp.playing = false; |
| 240 } | 275 } |
| 241 | 276 |
| 242 /** Called when the "scroll to split" button is clicked. */ | 277 /** Called when the "scroll to split" button is clicked. */ |
| 243 handleScrollToSplitClicked() { | 278 handleScrollToSplitClicked() { |
| 244 this.maybeScrollToElement(this.comp.$.logSplit, true, true); | 279 this.maybeScrollToElement(this.comp.$.logSplit, true); |
| 245 } | 280 } |
| 246 | 281 |
| 247 /** Called when a sign-in event is fired from "google-signin-aware". */ | 282 /** Called when a sign-in event is fired from "google-signin-aware". */ |
| 248 handleSignin() { | 283 handleSignin() { |
| 249 if (this.model) { | 284 if (this.model) { |
| 250 this.model.notifyAuthenticationChanged(); | 285 this.model.notifyAuthenticationChanged(); |
| 251 } | 286 } |
| 252 } | 287 } |
| 253 | 288 |
| 289 /** Returns true if our Model is currently automatically loading logs. */ |
| 290 private get isPlaying() { |
| 291 return (this.model && this.model.automatic); |
| 292 } |
| 293 |
| 254 /** Clears asynchornous scroll event status. */ | 294 /** Clears asynchornous scroll event status. */ |
| 255 private resetScroll() { | 295 private resetScroll() { |
| 256 if (this.scrollTimeoutId !== null) { | 296 if (this.scrollTimeoutId !== null) { |
| 257 window.clearTimeout(this.scrollTimeoutId); | 297 window.clearTimeout(this.scrollTimeoutId); |
| 258 this.scrollTimeoutId = null; | 298 this.scrollTimeoutId = null; |
| 259 } | 299 } |
| 260 } | 300 } |
| 261 | 301 |
| 262 /** | 302 /** |
| 263 * Called each time a scroll event is fired. Since this can be really | 303 * Called each time a scroll event is fired. Since this can be really |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 380 lastLogEntry = logEntryChunk; | 420 lastLogEntry = logEntryChunk; |
| 381 } | 421 } |
| 382 | 422 |
| 383 // To have styles apply correctly, we need to add it twice, see | 423 // To have styles apply correctly, we need to add it twice, see |
| 384 // https://github.com/Polymer/polymer/issues/3100. | 424 // https://github.com/Polymer/polymer/issues/3100. |
| 385 this.comp._polymerAppendChild(logEntryChunk); | 425 this.comp._polymerAppendChild(logEntryChunk); |
| 386 | 426 |
| 387 // Add the log entry to the appropriate place. | 427 // Add the log entry to the appropriate place. |
| 388 let anchor: Element|null; | 428 let anchor: Element|null; |
| 389 let scrollToTop = false; | 429 let scrollToTop = false; |
| 390 let forceScroll = false; | |
| 391 switch (insertion) { | 430 switch (insertion) { |
| 392 case Location.HEAD: | 431 case Location.HEAD: |
| 393 // PREPEND to "logSplit". | 432 // PREPEND to "logSplit". |
| 394 this.comp.$.logs.insertBefore(logEntryChunk, this.comp.$.logSplit); | 433 this.comp.$.logs.insertBefore(logEntryChunk, this.comp.$.logSplit); |
| 395 | 434 |
| 396 // If we're not split, scroll to the log bottom. Otherwise, scroll to | 435 // If we're not split, scroll to the log bottom. Otherwise, scroll to |
| 397 // the split. | 436 // the split. |
| 398 anchor = lastLogEntry; | 437 anchor = lastLogEntry; |
| 438 if (this.following) { |
| 439 this.maybeScrollToElement(anchor, scrollToTop); |
| 440 } |
| 399 break; | 441 break; |
| 400 | 442 |
| 401 case Location.TAIL: | 443 case Location.TAIL: |
| 402 // APPEND to "logSplit". | 444 // APPEND to "logSplit". |
| 403 anchor = this.comp.$.logSplit; | 445 anchor = this.comp.$.logSplit; |
| 404 | 446 |
| 405 // Identify the element *after* our insertion point and scroll to it. | 447 // Identify the element *after* our insertion point and scroll to it. |
| 406 // This provides a semblance of stability as we top-insert. | 448 // This provides a semblance of stability as we top-insert. |
| 407 // | 449 // |
| 408 // As a special case, if the next element is the log bottom, just | 450 // As a special case, if the next element is the log bottom, just |
| 409 // scroll to the split, since there is no content to stabilize. | 451 // scroll to the split, since there is no content to stabilize. |
| 410 if (anchor.nextElementSibling !== this.comp.$.logBottom) { | 452 if (anchor.nextElementSibling !== this.comp.$.logBottom) { |
| 411 anchor = anchor.nextElementSibling; | 453 anchor = anchor.nextElementSibling; |
| 412 } | 454 } |
| 413 | 455 |
| 414 // Insert logs by adding them before the sibling following the log | 456 // Insert logs by adding them before the sibling following the log |
| 415 // split (append to this.$.logSplit). | 457 // split (append to this.$.logSplit). |
| 416 this.comp.$.logs.insertBefore( | 458 this.comp.$.logs.insertBefore( |
| 417 logEntryChunk, this.comp.$.logSplit.nextSibling); | 459 logEntryChunk, this.comp.$.logSplit.nextSibling); |
| 418 | 460 |
| 419 // When tailing, always scroll to the anchor point. | 461 // When tailing, always scroll to the anchor point. |
| 420 scrollToTop = true; | 462 scrollToTop = true; |
| 421 forceScroll = true; | |
| 422 break; | 463 break; |
| 423 | 464 |
| 424 case Location.BOTTOM: | 465 case Location.BOTTOM: |
| 425 // PREPEND to "logBottom". | 466 // PREPEND to "logBottom". |
| 426 anchor = this.comp.$.logBottom; | 467 anchor = this.comp.$.logBottom; |
| 427 this.comp.$.logs.insertBefore(logEntryChunk, anchor); | 468 this.comp.$.logs.insertBefore(logEntryChunk, anchor); |
| 469 if (this.following) { |
| 470 this.maybeScrollToElement(anchor, scrollToTop); |
| 471 } |
| 428 break; | 472 break; |
| 429 | 473 |
| 430 default: | 474 default: |
| 431 anchor = null; | 475 anchor = null; |
| 432 break; | 476 break; |
| 433 } | 477 } |
| 434 | |
| 435 if (anchor) { | |
| 436 this.maybeScrollToElement(anchor, scrollToTop, forceScroll); | |
| 437 } | |
| 438 } | 478 } |
| 439 | 479 |
| 440 clearLogEntries() { | 480 clearLogEntries() { |
| 441 // Remove all current log elements. */ | 481 // Remove all current log elements. */ |
| 442 for (let cur: Element|null = <Element>this.comp.$.logs.firstChild; cur;) { | 482 for (let cur: Element|null = <Element>this.comp.$.logs.firstChild; cur;) { |
| 443 let del = cur; | 483 let del = cur; |
| 444 cur = cur.nextElementSibling; | 484 cur = cur.nextElementSibling; |
| 445 if (del.classList && del.classList.contains('log-entry-chunk')) { | 485 if (del.classList && del.classList.contains('log-entry-chunk')) { |
| 446 this.comp.$.logs.removeChild(del); | 486 this.comp.$.logs.removeChild(del); |
| 447 } | 487 } |
| (...skipping 11 matching lines...) Expand all Loading... |
| 459 case Location.BOTTOM: | 499 case Location.BOTTOM: |
| 460 anchor = this.comp.$.logBottom; | 500 anchor = this.comp.$.logBottom; |
| 461 break; | 501 break; |
| 462 | 502 |
| 463 default: | 503 default: |
| 464 return false; | 504 return false; |
| 465 } | 505 } |
| 466 return this.elementInViewport(anchor); | 506 return this.elementInViewport(anchor); |
| 467 } | 507 } |
| 468 | 508 |
| 509 updateControls(c: Controls) { |
| 510 let canSplit = false; |
| 511 let isSplit = false; |
| 512 if (!c.fullyLoaded) { |
| 513 switch (c.splitState) { |
| 514 case SplitState.CAN_SPLIT: |
| 515 canSplit = true; |
| 516 break; |
| 469 | 517 |
| 470 updateControls(c: Controls) { | 518 case SplitState.IS_SPLIT: |
| 471 this.comp._setCanSplit(c.canSplit); | 519 isSplit = true; |
| 472 this.comp._setIsSplit(c.split); | 520 break; |
| 473 this.comp._updateSplitVisible(c.split && this.renderedLogs); | 521 |
| 474 this.comp._updateBottomVisible(c.bottom && this.renderedLogs); | 522 default: |
| 523 break; |
| 524 } |
| 525 } |
| 526 this.comp._setShowPlayPause(!c.fullyLoaded); |
| 527 this.comp._setShowStreamControls(c.fullyLoaded || !this.isPlaying); |
| 528 this.comp._setShowSplitButton(canSplit && !this.isPlaying); |
| 529 this.comp._setShowSplitControls(isSplit); |
| 530 this.comp._updateSplitVisible(isSplit); |
| 531 |
| 475 this.comp.streamLinkUrl = c.logStreamUrl; | 532 this.comp.streamLinkUrl = c.logStreamUrl; |
| 476 | 533 |
| 477 this.comp._setShowStreamingControls(this.renderedLogs && !c.fullyLoaded); | |
| 478 if (c.fullyLoaded) { | |
| 479 this.comp.playing = false; | |
| 480 } | |
| 481 | |
| 482 switch (c.loadingState) { | 534 switch (c.loadingState) { |
| 483 case LogDog.LoadingState.RESOLVING: | 535 case LogDog.LoadingState.RESOLVING: |
| 484 this.loadStatusBar('Resolving stream names...'); | 536 this.loadStatusBar('Resolving stream names...'); |
| 485 break; | 537 break; |
| 486 case LogDog.LoadingState.LOADING: | 538 case LogDog.LoadingState.LOADING: |
| 487 this.loadStatusBar('Loading streams...'); | 539 this.loadStatusBar('Loading streams...'); |
| 488 break; | 540 break; |
| 489 case LogDog.LoadingState.LOADING_BEEN_A_WHILE: | 541 case LogDog.LoadingState.LOADING_BEEN_A_WHILE: |
| 490 this.loadStatusBar('Loading streams (has the build crashed?)...'); | 542 this.loadStatusBar('Loading streams (has the build crashed?)...'); |
| 491 break; | 543 break; |
| 492 case LogDog.LoadingState.RENDERING: | 544 case LogDog.LoadingState.RENDERING: |
| 493 this.loadStatusBar('Rendering logs.'); | 545 this.loadStatusBar('Rendering logs.'); |
| 494 break; | 546 break; |
| 547 case LogDog.LoadingState.PAUSED: |
| 548 this.loadStatusBar('Paused.'); |
| 549 break; |
| 495 case LogDog.LoadingState.NEEDS_AUTH: | 550 case LogDog.LoadingState.NEEDS_AUTH: |
| 496 this.loadStatusBar('Not authenticated. Please log in.'); | 551 this.loadStatusBar('Not authenticated. Please log in.'); |
| 497 break; | 552 break; |
| 498 case LogDog.LoadingState.ERROR: | 553 case LogDog.LoadingState.ERROR: |
| 499 this.loadStatusBar('Error loading streams (see console).'); | 554 this.loadStatusBar('Error loading streams (see console).'); |
| 500 break; | 555 break; |
| 501 | 556 |
| 502 case LogDog.LoadingState.NONE: | 557 case LogDog.LoadingState.NONE: |
| 503 default: | 558 default: |
| 504 this.loadStatusBar(null); | 559 this.loadStatusBar(null); |
| 505 break; | 560 break; |
| 506 } | 561 } |
| 507 | 562 |
| 508 this.comp._setStreamStatus(c.streamStatus); | 563 this.comp._setStreamStatus(c.streamStatus); |
| 509 } | 564 } |
| 510 | 565 |
| 511 /** Scrolls to the follow anchor point. */ | |
| 512 private maybeScrollToFollow() { | |
| 513 // Determine our anchor element. | |
| 514 let e: HTMLElement; | |
| 515 if (this.comp.isSplit && this.comp.backfill) { | |
| 516 // Centering on the split element, at the bottom of the page. | |
| 517 e = this.comp.$.logSplit; | |
| 518 } else { | |
| 519 // Scroll to the bottom of the page. | |
| 520 e = this.comp.$.logEnd; | |
| 521 } | |
| 522 | |
| 523 this.maybeScrollToElement(e, false, false); | |
| 524 } | |
| 525 | |
| 526 /** | 566 /** |
| 527 * Scrolls to the specified element, centering it at the top or bottom of | 567 * Scrolls to the specified element, centering it at the top or bottom of |
| 528 * the view. By default,t his will only happen if "follow" is enabled; | 568 * the view. By default,t his will only happen if "follow" is enabled; |
| 529 * however, it can be forced via "force". | 569 * however, it can be forced via "force". |
| 530 */ | 570 */ |
| 531 private maybeScrollToElement( | 571 private maybeScrollToElement(element: Element, topOfView: boolean) { |
| 532 element: Element, topOfView: boolean, force: boolean) { | 572 if (topOfView) { |
| 533 if (this.comp.follow || force) { | 573 element.scrollIntoView({ |
| 534 if (topOfView) { | 574 behavior: 'auto', |
| 535 element.scrollIntoView({ | 575 block: 'end', |
| 536 behavior: 'auto', | 576 }); |
| 537 block: 'end', | 577 } else { |
| 538 }); | 578 // Bug? "block: start" doesn't seem to work the same as false. |
| 539 } else { | 579 element.scrollIntoView(false); |
| 540 // Bug? "block: start" doesn't seem to work the same as false. | |
| 541 element.scrollIntoView(false); | |
| 542 } | |
| 543 } | 580 } |
| 544 } | 581 } |
| 545 | 582 |
| 546 /** | 583 /** |
| 547 * Loads text content into the status bar. | 584 * Loads text content into the status bar. |
| 548 * | 585 * |
| 549 * If null is passed, the status bar will be cleared. If text is passed, the | 586 * If null is passed, the status bar will be cleared. If text is passed, the |
| 550 * status bar will become visible with the supplied content. | 587 * status bar will become visible with the supplied content. |
| 551 */ | 588 */ |
| 552 private loadStatusBar(v: string|null) { | 589 private loadStatusBar(v: string|null) { |
| (...skipping 19 matching lines...) Expand all Loading... |
| 572 } | 609 } |
| 573 | 610 |
| 574 return ( | 611 return ( |
| 575 top < (window.pageYOffset + window.innerHeight) && | 612 top < (window.pageYOffset + window.innerHeight) && |
| 576 left < (window.pageXOffset + window.innerWidth) && | 613 left < (window.pageXOffset + window.innerWidth) && |
| 577 (top + height) > window.pageYOffset && | 614 (top + height) > window.pageYOffset && |
| 578 (left + width) > window.pageXOffset); | 615 (left + width) > window.pageXOffset); |
| 579 } | 616 } |
| 580 } | 617 } |
| 581 } | 618 } |
| OLD | NEW |