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

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

Issue 2988993003: [logdog-view] Update UX, fix bugs. (Closed)
Patch Set: unfollow only on up scroll 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(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
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
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
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
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 }
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