Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(375)

Side by Side Diff: web/inc/logdog-stream-view/view.ts

Issue 2988993003: [logdog-view] Update UX, fix bugs. (Closed)
Patch Set: last Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « web/inc/logdog-stream-view/model.ts ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « web/inc/logdog-stream-view/model.ts ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698