Chromium Code Reviews| 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() { |
|
Ryan Tseng
2017/08/01 18:39:07
I'd probably only handle this if it is a scroll up
dnj
2017/08/01 20:16:15
Done.
| |
| 160 this.comp.follow = false; | 184 // Once someone scrolls, stop following. |
| 185 this.following = false; | |
| 161 } | 186 } |
| 162 | 187 |
| 163 /** Called when the split "Down" button is clicked. */ | 188 /** Called when the split "Down" button is clicked. */ |
| 164 handleDownClick() { | 189 handleDownClick() { |
| 165 if (this.model) { | 190 if (this.model) { |
| 166 this.model.fetchLocation(Location.HEAD, true); | 191 this.model.fetchLocation(Location.HEAD, true); |
| 167 } | 192 } |
| 168 } | 193 } |
| 169 | 194 |
| 170 /** Called when the split "Up" button is clicked. */ | 195 /** Called when the split "Up" button is clicked. */ |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 187 return; | 212 return; |
| 188 } | 213 } |
| 189 | 214 |
| 190 await this.model.resolve(this.comp.streams); | 215 await this.model.resolve(this.comp.streams); |
| 191 | 216 |
| 192 // If we're not on mobile, start with playing state. | 217 // If we're not on mobile, start with playing state. |
| 193 this.comp.playing = (!this.comp.mobile); | 218 this.comp.playing = (!this.comp.mobile); |
| 194 | 219 |
| 195 // Perform the initial fetch after resolution. | 220 // Perform the initial fetch after resolution. |
| 196 if (this.model) { | 221 if (this.model) { |
| 197 this.model.setAutomatic(this.comp.playing); | 222 this.model.automatic = this.comp.playing; |
| 198 this.model.setFetchFromTail(!this.comp.backfill); | 223 this.model.setFetchFromTail(!this.comp.backfill); |
| 199 this.model.fetch(false); | 224 this.model.fetch(false); |
| 200 } | 225 } |
| 201 } | 226 } |
| 202 | 227 |
| 203 /** Called when the "playing" property value changes. */ | 228 stop() { |
| 204 handlePlayingChanged(v: boolean) { | |
| 205 if (this.model) { | 229 if (this.model) { |
| 206 // If we're playing, begin log fetching. | 230 this.model.automatic = false; |
| 207 this.model.setAutomatic(v); | 231 this.model.clearCurrentOperation(); |
| 208 } | 232 } |
| 209 } | 233 } |
| 210 | 234 |
| 235 /** Called when the "playing" property value changes. */ | |
| 236 handlePlayPauseChanged(v: boolean) { | |
| 237 if (this.model) { | |
| 238 // If we're playing, begin log fetching. | |
| 239 this.model.automatic = v; | |
| 240 | |
| 241 // Once someone manually uses this control, stop following. | |
| 242 // | |
| 243 // Only apply this after we've started rendering logs, since before that | |
| 244 // this may toggle during setup. | |
| 245 if (this.renderedLogs) { | |
| 246 this.following = false; | |
| 247 } | |
| 248 | |
| 249 if (!v) { | |
| 250 this.model.clearCurrentOperation(); | |
| 251 } | |
| 252 } | |
| 253 } | |
| 254 | |
| 211 /** Called when the "backfill" property value changes. */ | 255 /** Called when the "backfill" property value changes. */ |
| 212 handleBackfillChanged(v: boolean) { | 256 handleBackfillChanged(v: boolean) { |
| 213 if (this.model) { | 257 if (this.model) { |
| 214 // If we're backfilling, then we're not tailing. | 258 // If we're backfilling, then we're not tailing. |
| 215 this.model.setFetchFromTail(!v); | 259 this.model.setFetchFromTail(!v); |
| 216 } | 260 } |
| 217 } | 261 } |
| 218 | 262 |
| 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. */ | 263 /** Called when the "split" button is clicked. */ |
| 231 handleSplitClicked() { | 264 handleSplitClicked() { |
| 232 if (!this.model) { | 265 if (!this.model) { |
| 233 return; | 266 return; |
| 234 } | 267 } |
| 235 | 268 |
| 236 // After a split, toggle off playing. | 269 // After a split, toggle off playing. |
| 270 this.model.setFetchFromTail(true); | |
| 237 this.model.split(); | 271 this.model.split(); |
| 238 this.model.setFetchFromTail(true); | |
| 239 this.comp.playing = false; | 272 this.comp.playing = false; |
| 240 } | 273 } |
| 241 | 274 |
| 242 /** Called when the "scroll to split" button is clicked. */ | 275 /** Called when the "scroll to split" button is clicked. */ |
| 243 handleScrollToSplitClicked() { | 276 handleScrollToSplitClicked() { |
| 244 this.maybeScrollToElement(this.comp.$.logSplit, true, true); | 277 this.maybeScrollToElement(this.comp.$.logSplit, true); |
| 245 } | 278 } |
| 246 | 279 |
| 247 /** Called when a sign-in event is fired from "google-signin-aware". */ | 280 /** Called when a sign-in event is fired from "google-signin-aware". */ |
| 248 handleSignin() { | 281 handleSignin() { |
| 249 if (this.model) { | 282 if (this.model) { |
| 250 this.model.notifyAuthenticationChanged(); | 283 this.model.notifyAuthenticationChanged(); |
| 251 } | 284 } |
| 252 } | 285 } |
| 253 | 286 |
| 287 /** Returns true if our Model is currently automatically loading logs. */ | |
| 288 private get isPlaying() { | |
| 289 return (this.model && this.model.automatic); | |
| 290 } | |
| 291 | |
| 254 /** Clears asynchornous scroll event status. */ | 292 /** Clears asynchornous scroll event status. */ |
| 255 private resetScroll() { | 293 private resetScroll() { |
| 256 if (this.scrollTimeoutId !== null) { | 294 if (this.scrollTimeoutId !== null) { |
| 257 window.clearTimeout(this.scrollTimeoutId); | 295 window.clearTimeout(this.scrollTimeoutId); |
| 258 this.scrollTimeoutId = null; | 296 this.scrollTimeoutId = null; |
| 259 } | 297 } |
| 260 } | 298 } |
| 261 | 299 |
| 262 /** | 300 /** |
| 263 * Called each time a scroll event is fired. Since this can be really | 301 * 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; | 418 lastLogEntry = logEntryChunk; |
| 381 } | 419 } |
| 382 | 420 |
| 383 // To have styles apply correctly, we need to add it twice, see | 421 // To have styles apply correctly, we need to add it twice, see |
| 384 // https://github.com/Polymer/polymer/issues/3100. | 422 // https://github.com/Polymer/polymer/issues/3100. |
| 385 this.comp._polymerAppendChild(logEntryChunk); | 423 this.comp._polymerAppendChild(logEntryChunk); |
| 386 | 424 |
| 387 // Add the log entry to the appropriate place. | 425 // Add the log entry to the appropriate place. |
| 388 let anchor: Element|null; | 426 let anchor: Element|null; |
| 389 let scrollToTop = false; | 427 let scrollToTop = false; |
| 390 let forceScroll = false; | |
| 391 switch (insertion) { | 428 switch (insertion) { |
| 392 case Location.HEAD: | 429 case Location.HEAD: |
| 393 // PREPEND to "logSplit". | 430 // PREPEND to "logSplit". |
| 394 this.comp.$.logs.insertBefore(logEntryChunk, this.comp.$.logSplit); | 431 this.comp.$.logs.insertBefore(logEntryChunk, this.comp.$.logSplit); |
| 395 | 432 |
| 396 // If we're not split, scroll to the log bottom. Otherwise, scroll to | 433 // If we're not split, scroll to the log bottom. Otherwise, scroll to |
| 397 // the split. | 434 // the split. |
| 398 anchor = lastLogEntry; | 435 anchor = lastLogEntry; |
| 436 if (this.following) { | |
| 437 this.maybeScrollToElement(anchor, scrollToTop); | |
| 438 } | |
| 399 break; | 439 break; |
| 400 | 440 |
| 401 case Location.TAIL: | 441 case Location.TAIL: |
| 402 // APPEND to "logSplit". | 442 // APPEND to "logSplit". |
| 403 anchor = this.comp.$.logSplit; | 443 anchor = this.comp.$.logSplit; |
| 404 | 444 |
| 405 // Identify the element *after* our insertion point and scroll to it. | 445 // Identify the element *after* our insertion point and scroll to it. |
| 406 // This provides a semblance of stability as we top-insert. | 446 // This provides a semblance of stability as we top-insert. |
| 407 // | 447 // |
| 408 // As a special case, if the next element is the log bottom, just | 448 // 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. | 449 // scroll to the split, since there is no content to stabilize. |
| 410 if (anchor.nextElementSibling !== this.comp.$.logBottom) { | 450 if (anchor.nextElementSibling !== this.comp.$.logBottom) { |
| 411 anchor = anchor.nextElementSibling; | 451 anchor = anchor.nextElementSibling; |
| 412 } | 452 } |
| 413 | 453 |
| 414 // Insert logs by adding them before the sibling following the log | 454 // Insert logs by adding them before the sibling following the log |
| 415 // split (append to this.$.logSplit). | 455 // split (append to this.$.logSplit). |
| 416 this.comp.$.logs.insertBefore( | 456 this.comp.$.logs.insertBefore( |
| 417 logEntryChunk, this.comp.$.logSplit.nextSibling); | 457 logEntryChunk, this.comp.$.logSplit.nextSibling); |
| 418 | 458 |
| 419 // When tailing, always scroll to the anchor point. | 459 // When tailing, always scroll to the anchor point. |
| 420 scrollToTop = true; | 460 scrollToTop = true; |
| 421 forceScroll = true; | |
| 422 break; | 461 break; |
| 423 | 462 |
| 424 case Location.BOTTOM: | 463 case Location.BOTTOM: |
| 425 // PREPEND to "logBottom". | 464 // PREPEND to "logBottom". |
| 426 anchor = this.comp.$.logBottom; | 465 anchor = this.comp.$.logBottom; |
| 427 this.comp.$.logs.insertBefore(logEntryChunk, anchor); | 466 this.comp.$.logs.insertBefore(logEntryChunk, anchor); |
| 467 if (this.following) { | |
| 468 this.maybeScrollToElement(anchor, scrollToTop); | |
| 469 } | |
| 428 break; | 470 break; |
| 429 | 471 |
| 430 default: | 472 default: |
| 431 anchor = null; | 473 anchor = null; |
| 432 break; | 474 break; |
| 433 } | 475 } |
| 434 | |
| 435 if (anchor) { | |
| 436 this.maybeScrollToElement(anchor, scrollToTop, forceScroll); | |
| 437 } | |
| 438 } | 476 } |
| 439 | 477 |
| 440 clearLogEntries() { | 478 clearLogEntries() { |
| 441 // Remove all current log elements. */ | 479 // Remove all current log elements. */ |
| 442 for (let cur: Element|null = <Element>this.comp.$.logs.firstChild; cur;) { | 480 for (let cur: Element|null = <Element>this.comp.$.logs.firstChild; cur;) { |
| 443 let del = cur; | 481 let del = cur; |
| 444 cur = cur.nextElementSibling; | 482 cur = cur.nextElementSibling; |
| 445 if (del.classList && del.classList.contains('log-entry-chunk')) { | 483 if (del.classList && del.classList.contains('log-entry-chunk')) { |
| 446 this.comp.$.logs.removeChild(del); | 484 this.comp.$.logs.removeChild(del); |
| 447 } | 485 } |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 459 case Location.BOTTOM: | 497 case Location.BOTTOM: |
| 460 anchor = this.comp.$.logBottom; | 498 anchor = this.comp.$.logBottom; |
| 461 break; | 499 break; |
| 462 | 500 |
| 463 default: | 501 default: |
| 464 return false; | 502 return false; |
| 465 } | 503 } |
| 466 return this.elementInViewport(anchor); | 504 return this.elementInViewport(anchor); |
| 467 } | 505 } |
| 468 | 506 |
| 507 updateControls(c: Controls) { | |
| 508 let canSplit = false; | |
| 509 let isSplit = false; | |
| 510 if (!c.fullyLoaded) { | |
| 511 switch (c.splitState) { | |
| 512 case SplitState.CAN_SPLIT: | |
| 513 canSplit = true; | |
| 514 break; | |
| 469 | 515 |
| 470 updateControls(c: Controls) { | 516 case SplitState.IS_SPLIT: |
| 471 this.comp._setCanSplit(c.canSplit); | 517 isSplit = true; |
| 472 this.comp._setIsSplit(c.split); | 518 break; |
| 473 this.comp._updateSplitVisible(c.split && this.renderedLogs); | 519 |
| 474 this.comp._updateBottomVisible(c.bottom && this.renderedLogs); | 520 default: |
| 521 break; | |
| 522 } | |
| 523 } | |
| 524 this.comp._setShowPlayPause(!c.fullyLoaded); | |
| 525 this.comp._setShowStreamControls(c.fullyLoaded || !this.isPlaying); | |
| 526 this.comp._setShowSplitButton(canSplit && !this.isPlaying); | |
| 527 this.comp._setShowSplitControls(isSplit); | |
| 528 this.comp._updateSplitVisible(isSplit); | |
| 529 | |
| 475 this.comp.streamLinkUrl = c.logStreamUrl; | 530 this.comp.streamLinkUrl = c.logStreamUrl; |
| 476 | 531 |
| 477 this.comp._setShowStreamingControls(this.renderedLogs && !c.fullyLoaded); | |
| 478 if (c.fullyLoaded) { | |
| 479 this.comp.playing = false; | |
| 480 } | |
| 481 | |
| 482 switch (c.loadingState) { | 532 switch (c.loadingState) { |
| 483 case LogDog.LoadingState.RESOLVING: | 533 case LogDog.LoadingState.RESOLVING: |
| 484 this.loadStatusBar('Resolving stream names...'); | 534 this.loadStatusBar('Resolving stream names...'); |
| 485 break; | 535 break; |
| 486 case LogDog.LoadingState.LOADING: | 536 case LogDog.LoadingState.LOADING: |
| 487 this.loadStatusBar('Loading streams...'); | 537 this.loadStatusBar('Loading streams...'); |
| 488 break; | 538 break; |
| 489 case LogDog.LoadingState.LOADING_BEEN_A_WHILE: | 539 case LogDog.LoadingState.LOADING_BEEN_A_WHILE: |
| 490 this.loadStatusBar('Loading streams (has the build crashed?)...'); | 540 this.loadStatusBar('Loading streams (has the build crashed?)...'); |
| 491 break; | 541 break; |
| 492 case LogDog.LoadingState.RENDERING: | 542 case LogDog.LoadingState.RENDERING: |
| 493 this.loadStatusBar('Rendering logs.'); | 543 this.loadStatusBar('Rendering logs.'); |
| 494 break; | 544 break; |
| 545 case LogDog.LoadingState.PAUSED: | |
| 546 this.loadStatusBar('Paused.'); | |
| 547 break; | |
| 495 case LogDog.LoadingState.NEEDS_AUTH: | 548 case LogDog.LoadingState.NEEDS_AUTH: |
| 496 this.loadStatusBar('Not authenticated. Please log in.'); | 549 this.loadStatusBar('Not authenticated. Please log in.'); |
| 497 break; | 550 break; |
| 498 case LogDog.LoadingState.ERROR: | 551 case LogDog.LoadingState.ERROR: |
| 499 this.loadStatusBar('Error loading streams (see console).'); | 552 this.loadStatusBar('Error loading streams (see console).'); |
| 500 break; | 553 break; |
| 501 | 554 |
| 502 case LogDog.LoadingState.NONE: | 555 case LogDog.LoadingState.NONE: |
| 503 default: | 556 default: |
| 504 this.loadStatusBar(null); | 557 this.loadStatusBar(null); |
| 505 break; | 558 break; |
| 506 } | 559 } |
| 507 | 560 |
| 508 this.comp._setStreamStatus(c.streamStatus); | 561 this.comp._setStreamStatus(c.streamStatus); |
| 509 } | 562 } |
| 510 | 563 |
| 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 /** | 564 /** |
| 527 * Scrolls to the specified element, centering it at the top or bottom of | 565 * 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; | 566 * the view. By default,t his will only happen if "follow" is enabled; |
| 529 * however, it can be forced via "force". | 567 * however, it can be forced via "force". |
| 530 */ | 568 */ |
| 531 private maybeScrollToElement( | 569 private maybeScrollToElement(element: Element, topOfView: boolean) { |
| 532 element: Element, topOfView: boolean, force: boolean) { | 570 if (topOfView) { |
| 533 if (this.comp.follow || force) { | 571 element.scrollIntoView({ |
| 534 if (topOfView) { | 572 behavior: 'auto', |
| 535 element.scrollIntoView({ | 573 block: 'end', |
| 536 behavior: 'auto', | 574 }); |
| 537 block: 'end', | 575 } else { |
| 538 }); | 576 // Bug? "block: start" doesn't seem to work the same as false. |
| 539 } else { | 577 element.scrollIntoView(false); |
| 540 // Bug? "block: start" doesn't seem to work the same as false. | |
| 541 element.scrollIntoView(false); | |
| 542 } | |
| 543 } | 578 } |
| 544 } | 579 } |
| 545 | 580 |
| 546 /** | 581 /** |
| 547 * Loads text content into the status bar. | 582 * Loads text content into the status bar. |
| 548 * | 583 * |
| 549 * If null is passed, the status bar will be cleared. If text is passed, the | 584 * 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. | 585 * status bar will become visible with the supplied content. |
| 551 */ | 586 */ |
| 552 private loadStatusBar(v: string|null) { | 587 private loadStatusBar(v: string|null) { |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 572 } | 607 } |
| 573 | 608 |
| 574 return ( | 609 return ( |
| 575 top < (window.pageYOffset + window.innerHeight) && | 610 top < (window.pageYOffset + window.innerHeight) && |
| 576 left < (window.pageXOffset + window.innerWidth) && | 611 left < (window.pageXOffset + window.innerWidth) && |
| 577 (top + height) > window.pageYOffset && | 612 (top + height) > window.pageYOffset && |
| 578 (left + width) > window.pageXOffset); | 613 (left + width) > window.pageXOffset); |
| 579 } | 614 } |
| 580 } | 615 } |
| 581 } | 616 } |
| OLD | NEW |