OLD | NEW |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of swarmlib; | 5 part of swarmlib; |
6 | 6 |
7 // TODO(jacobr): there is a lot of dead code in this class. Checking is as is | 7 // TODO(jacobr): there is a lot of dead code in this class. Checking is as is |
8 // and then doing a large pass to remove functionality that doesn't make sense | 8 // and then doing a large pass to remove functionality that doesn't make sense |
9 // given the UI layout. | 9 // given the UI layout. |
10 | 10 |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
63 openKeyPresses = new Set.from([13 /*enter*/, 79 /*o*/]), | 63 openKeyPresses = new Set.from([13 /*enter*/, 79 /*o*/]), |
64 backKeyPresses = new Set.from([8 /*delete*/, 27 /*escape*/]), | 64 backKeyPresses = new Set.from([8 /*delete*/, 27 /*escape*/]), |
65 nextPageKeyPresses = new Set.from([78 /*n*/]), | 65 nextPageKeyPresses = new Set.from([78 /*n*/]), |
66 previousPageKeyPresses = new Set.from([80 /*p*/]), | 66 previousPageKeyPresses = new Set.from([80 /*p*/]), |
67 nextPrevShown = false { | 67 nextPrevShown = false { |
68 topView = new CompositeView('top-view', false, false, false); | 68 topView = new CompositeView('top-view', false, false, false); |
69 | 69 |
70 headerView = new HeaderView(swarm); | 70 headerView = new HeaderView(swarm); |
71 topView.addChild(headerView); | 71 topView.addChild(headerView); |
72 | 72 |
73 sliderMenu = new SliderMenu(swarm.sections.sectionTitles, | 73 sliderMenu = new SliderMenu(swarm.sections.sectionTitles, (sectionTitle) { |
74 (sectionTitle) { | 74 swarm.state.moveToNewSection(sectionTitle); |
75 swarm.state.moveToNewSection(sectionTitle); | 75 _onSectionSelected(sectionTitle); |
76 _onSectionSelected(sectionTitle); | 76 // Start with no articles selected. |
77 // Start with no articles selected. | 77 swarm.state.selectedArticle.value = null; |
78 swarm.state.selectedArticle.value = null; | 78 }); |
79 }); | |
80 topView.addChild(sliderMenu); | 79 topView.addChild(sliderMenu); |
81 addChild(topView); | 80 addChild(topView); |
82 | 81 |
83 bottomView = new CompositeView('bottom-view', false, false, false); | 82 bottomView = new CompositeView('bottom-view', false, false, false); |
84 addChild(bottomView); | 83 addChild(bottomView); |
85 | 84 |
86 sections = new ConveyorView(); | 85 sections = new ConveyorView(); |
87 sections.viewSelected = _onSectionTransitionEnded; | 86 sections.viewSelected = _onSectionTransitionEnded; |
88 } | 87 } |
89 | 88 |
90 SectionView get currentSection { | 89 SectionView get currentSection { |
91 var view = sections.selectedView; | 90 var view = sections.selectedView; |
92 // TODO(jmesserly): this code works around a bug in the DartC --optimize | 91 // TODO(jmesserly): this code works around a bug in the DartC --optimize |
93 if (view == null) { | 92 if (view == null) { |
94 view = sections.childViews[0]; | 93 view = sections.childViews[0]; |
95 sections.selectView(view); | 94 sections.selectView(view); |
96 } | 95 } |
97 return view; | 96 return view; |
98 } | 97 } |
99 | 98 |
100 void afterRender(Element node) { | 99 void afterRender(Element node) { |
101 _createSectionViews(); | 100 _createSectionViews(); |
102 attachWatch(swarm.state.currentArticle, (e) { _refreshCurrentArticle(); }); | 101 attachWatch(swarm.state.currentArticle, (e) { |
103 attachWatch(swarm.state.storyMaximized, (e) { _refreshMaximized(); }); | 102 _refreshCurrentArticle(); |
| 103 }); |
| 104 attachWatch(swarm.state.storyMaximized, (e) { |
| 105 _refreshMaximized(); |
| 106 }); |
104 } | 107 } |
105 | 108 |
106 void _refreshCurrentArticle() { | 109 void _refreshCurrentArticle() { |
107 if (!swarm.state.inMainView) { | 110 if (!swarm.state.inMainView) { |
108 _animateToStory(swarm.state.currentArticle.value); | 111 _animateToStory(swarm.state.currentArticle.value); |
109 } else { | 112 } else { |
110 _animateToMainView(); | 113 _animateToMainView(); |
111 } | 114 } |
112 } | 115 } |
113 | 116 |
114 /** | 117 /** |
115 * Animates back from the story view to the main grid view. | 118 * Animates back from the story view to the main grid view. |
116 */ | 119 */ |
117 void _animateToMainView() { | 120 void _animateToMainView() { |
118 sliderMenu.removeClass('hidden'); | 121 sliderMenu.removeClass('hidden'); |
119 storyView.addClass('hidden-story'); | 122 storyView.addClass('hidden-story'); |
120 currentSection.storyMode = false; | 123 currentSection.storyMode = false; |
121 | 124 |
122 headerView.startTransitionToMainView(); | 125 headerView.startTransitionToMainView(); |
123 | 126 |
124 currentSection.dataSourceView.reattachSubview( | 127 currentSection.dataSourceView |
125 detachedView.source, detachedView, true); | 128 .reattachSubview(detachedView.source, detachedView, true); |
126 | 129 |
127 storyView.node.onTransitionEnd.first.then((e) { | 130 storyView.node.onTransitionEnd.first.then((e) { |
128 currentSection.hidden = false; | 131 currentSection.hidden = false; |
129 // TODO(rnystrom): Should move this "mode" into SwarmState and have | 132 // TODO(rnystrom): Should move this "mode" into SwarmState and have |
130 // header view respond to change events itself. | 133 // header view respond to change events itself. |
131 removeChild(storyView); | 134 removeChild(storyView); |
132 storyView = null; | 135 storyView = null; |
133 detachedView.removeClass('sel'); | 136 detachedView.removeClass('sel'); |
134 detachedView = null; | 137 detachedView = null; |
135 }); | 138 }); |
(...skipping 14 matching lines...) Expand all Loading... |
150 // and the user has clicked to see a new story. | 153 // and the user has clicked to see a new story. |
151 removeChild(storyView); | 154 removeChild(storyView); |
152 | 155 |
153 // Create the new story view and place in the frame. | 156 // Create the new story view and place in the frame. |
154 storyView = addChild(new StoryContentView(swarm, item)); | 157 storyView = addChild(new StoryContentView(swarm, item)); |
155 } else { | 158 } else { |
156 // We are animating from the main view to the story view. | 159 // We are animating from the main view to the story view. |
157 // TODO(jmesserly): make this code better | 160 // TODO(jmesserly): make this code better |
158 final view = currentSection.findView(source); | 161 final view = currentSection.findView(source); |
159 | 162 |
160 final newPosition = FxUtil.computeRelativePosition( | 163 final newPosition = |
161 view.node, bottomView.node); | 164 FxUtil.computeRelativePosition(view.node, bottomView.node); |
162 currentSection.dataSourceView.detachSubview(view.source); | 165 currentSection.dataSourceView.detachSubview(view.source); |
163 detachedView = view; | 166 detachedView = view; |
164 | 167 |
165 FxUtil.setPosition(view.node, newPosition); | 168 FxUtil.setPosition(view.node, newPosition); |
166 bottomView.addChild(view); | 169 bottomView.addChild(view); |
167 view.addClass('sel'); | 170 view.addClass('sel'); |
168 currentSection.storyMode = true; | 171 currentSection.storyMode = true; |
169 | 172 |
170 // Create the new story view. | 173 // Create the new story view. |
171 storyView = new StoryContentView(swarm, item); | 174 storyView = new StoryContentView(swarm, item); |
(...skipping 18 matching lines...) Expand all Loading... |
190 if (swarm.state.storyMaximized.value) { | 193 if (swarm.state.storyMaximized.value) { |
191 _animateDataSourceToMaximized(); | 194 _animateDataSourceToMaximized(); |
192 } else { | 195 } else { |
193 _animateDataSourceToMinimized(); | 196 _animateDataSourceToMinimized(); |
194 } | 197 } |
195 } | 198 } |
196 | 199 |
197 void _animateDataSourceToMaximized() { | 200 void _animateDataSourceToMaximized() { |
198 FxUtil.setWebkitTransform(topView.node, 0, -HeaderView.HEIGHT); | 201 FxUtil.setWebkitTransform(topView.node, 0, -HeaderView.HEIGHT); |
199 if (detachedView != null) { | 202 if (detachedView != null) { |
200 FxUtil.setWebkitTransform(detachedView.node, 0, | 203 FxUtil.setWebkitTransform( |
201 -DataSourceView.TAB_ONLY_HEIGHT); | 204 detachedView.node, 0, -DataSourceView.TAB_ONLY_HEIGHT); |
202 } | 205 } |
203 } | 206 } |
204 | 207 |
205 void _animateDataSourceToMinimized() { | 208 void _animateDataSourceToMinimized() { |
206 if (detachedView != null) { | 209 if (detachedView != null) { |
207 FxUtil.setWebkitTransform(detachedView.node, 0, 0); | 210 FxUtil.setWebkitTransform(detachedView.node, 0, 0); |
208 FxUtil.setWebkitTransform(topView.node, 0, 0); | 211 FxUtil.setWebkitTransform(topView.node, 0, 0); |
209 } | 212 } |
210 } | 213 } |
211 | 214 |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
319 | 322 |
320 /** A back button that sends the user back to the front page. */ | 323 /** A back button that sends the user back to the front page. */ |
321 class SwarmBackButton extends View { | 324 class SwarmBackButton extends View { |
322 Swarm swarm; | 325 Swarm swarm; |
323 | 326 |
324 SwarmBackButton(this.swarm) : super(); | 327 SwarmBackButton(this.swarm) : super(); |
325 | 328 |
326 Element render() => new Element.html('<div class="back-arrow button"></div>'); | 329 Element render() => new Element.html('<div class="back-arrow button"></div>'); |
327 | 330 |
328 void afterRender(Element node) { | 331 void afterRender(Element node) { |
329 addOnClick((e) { _backToMain(swarm.state); }); | 332 addOnClick((e) { |
| 333 _backToMain(swarm.state); |
| 334 }); |
330 } | 335 } |
331 } | 336 } |
332 | 337 |
333 /** Top view constaining the title and standard buttons. */ | 338 /** Top view constaining the title and standard buttons. */ |
334 class HeaderView extends CompositeView { | 339 class HeaderView extends CompositeView { |
335 // TODO(jacobr): make this value be coupled with the CSS file. | 340 // TODO(jacobr): make this value be coupled with the CSS file. |
336 static const HEIGHT = 80; | 341 static const HEIGHT = 80; |
337 Swarm swarm; | 342 Swarm swarm; |
338 | 343 |
339 View _title; | 344 View _title; |
(...skipping 18 matching lines...) Expand all Loading... |
358 | 363 |
359 // TODO(rnystrom): No more web/text mode (it's just text) so get rid of | 364 // TODO(rnystrom): No more web/text mode (it's just text) so get rid of |
360 // these. | 365 // these. |
361 _webBackButton = addChild(new WebBackButton()); | 366 _webBackButton = addChild(new WebBackButton()); |
362 _webForwardButton = addChild(new WebForwardButton()); | 367 _webForwardButton = addChild(new WebForwardButton()); |
363 _newWindowButton = addChild(View.div('new-window-button button')); | 368 _newWindowButton = addChild(View.div('new-window-button button')); |
364 } | 369 } |
365 | 370 |
366 void afterRender(Element node) { | 371 void afterRender(Element node) { |
367 // Respond to changes to whether the story is being shown as text or web. | 372 // Respond to changes to whether the story is being shown as text or web. |
368 attachWatch(swarm.state.storyTextMode, (e) { refreshWebStoryButtons(); }); | 373 attachWatch(swarm.state.storyTextMode, (e) { |
| 374 refreshWebStoryButtons(); |
| 375 }); |
369 | 376 |
370 _title.addOnClick((e) { _backToMain(swarm.state); }); | 377 _title.addOnClick((e) { |
| 378 _backToMain(swarm.state); |
| 379 }); |
371 | 380 |
372 // Wire up the events. | 381 // Wire up the events. |
373 _configButton.addOnClick((e) { | 382 _configButton.addOnClick((e) { |
374 // Bring up the config dialog. | 383 // Bring up the config dialog. |
375 if (this._configDialog == null) { | 384 if (this._configDialog == null) { |
376 // TODO(terry): Cleanup, HeaderView shouldn't be tangled with main view. | 385 // TODO(terry): Cleanup, HeaderView shouldn't be tangled with main view. |
377 this._configDialog = new ConfigHintDialog(swarm.frontView, () { | 386 this._configDialog = new ConfigHintDialog(swarm.frontView, () { |
378 swarm.frontView.removeChild(this._configDialog); | 387 swarm.frontView.removeChild(this._configDialog); |
379 this._configDialog = null; | 388 this._configDialog = null; |
380 | 389 |
(...skipping 30 matching lines...) Expand all Loading... |
411 | 420 |
412 // On click of the new window button, show web article in new window/tab. | 421 // On click of the new window button, show web article in new window/tab. |
413 _newWindowButton.addOnClick((e) { | 422 _newWindowButton.addOnClick((e) { |
414 String currentArticleSrcUrl = swarm.state.currentArticle.value.srcUrl; | 423 String currentArticleSrcUrl = swarm.state.currentArticle.value.srcUrl; |
415 window.open(currentArticleSrcUrl, '_blank'); | 424 window.open(currentArticleSrcUrl, '_blank'); |
416 }); | 425 }); |
417 | 426 |
418 startTransitionToMainView(); | 427 startTransitionToMainView(); |
419 } | 428 } |
420 | 429 |
421 | |
422 /** | 430 /** |
423 * Refreshes whether or not the buttons specific to the display of a story in | 431 * Refreshes whether or not the buttons specific to the display of a story in |
424 * the web perspective are visible. | 432 * the web perspective are visible. |
425 */ | 433 */ |
426 void refreshWebStoryButtons() { | 434 void refreshWebStoryButtons() { |
427 bool webButtonsHidden = true; | 435 bool webButtonsHidden = true; |
428 | 436 |
429 if (swarm.state.currentArticle.value != null) { | 437 if (swarm.state.currentArticle.value != null) { |
430 // Set if web buttons are hidden | 438 // Set if web buttons are hidden |
431 webButtonsHidden = swarm.state.storyTextMode.value; | 439 webButtonsHidden = swarm.state.storyTextMode.value; |
432 } | 440 } |
433 | 441 |
434 _webBackButton.hidden = webButtonsHidden; | 442 _webBackButton.hidden = webButtonsHidden; |
435 _webForwardButton.hidden = webButtonsHidden; | 443 _webForwardButton.hidden = webButtonsHidden; |
436 _newWindowButton.hidden = webButtonsHidden; | 444 _newWindowButton.hidden = webButtonsHidden; |
437 } | 445 } |
438 | 446 |
439 void startTransitionToMainView() { | 447 void startTransitionToMainView() { |
440 _title.removeClass('in-story'); | 448 _title.removeClass('in-story'); |
441 _backButton.removeClass('in-story'); | 449 _backButton.removeClass('in-story'); |
442 | 450 |
443 _configButton.removeClass('in-story'); | 451 _configButton.removeClass('in-story'); |
444 _refreshButton.removeClass('in-story'); | 452 _refreshButton.removeClass('in-story'); |
445 _infoButton.removeClass('in-story'); | 453 _infoButton.removeClass('in-story'); |
446 | 454 |
447 refreshWebStoryButtons(); | 455 refreshWebStoryButtons(); |
448 } | 456 } |
449 | 457 |
450 void endTransitionToStoryView() { | 458 void endTransitionToStoryView() { |
451 _title.addClass('in-story'); | 459 _title.addClass('in-story'); |
452 _backButton.addClass('in-story'); | 460 _backButton.addClass('in-story'); |
453 | 461 |
454 _configButton.addClass('in-story'); | 462 _configButton.addClass('in-story'); |
455 _refreshButton.addClass('in-story'); | 463 _refreshButton.addClass('in-story'); |
456 _infoButton.addClass('in-story'); | 464 _infoButton.addClass('in-story'); |
457 } | 465 } |
458 } | 466 } |
459 | 467 |
460 | |
461 /** A back button for the web view of a story that is equivalent to clicking | 468 /** A back button for the web view of a story that is equivalent to clicking |
462 * "back" in the browser. */ | 469 * "back" in the browser. */ |
463 // TODO(rnystrom): We have nearly identical versions of this littered through | 470 // TODO(rnystrom): We have nearly identical versions of this littered through |
464 // the sample apps. Should consolidate into one. | 471 // the sample apps. Should consolidate into one. |
465 class WebBackButton extends View { | 472 class WebBackButton extends View { |
466 WebBackButton() : super(); | 473 WebBackButton() : super(); |
467 | 474 |
468 Element render() { | 475 Element render() { |
469 return new Element.html('<div class="web-back-button button"></div>'); | 476 return new Element.html('<div class="web-back-button button"></div>'); |
470 } | 477 } |
471 | 478 |
472 void afterRender(Element node) { | 479 void afterRender(Element node) { |
473 addOnClick((e) { back(); }); | 480 addOnClick((e) { |
| 481 back(); |
| 482 }); |
474 } | 483 } |
475 | 484 |
476 /** Equivalent to [window.history.back] */ | 485 /** Equivalent to [window.history.back] */ |
477 static void back() { | 486 static void back() { |
478 window.history.back(); | 487 window.history.back(); |
479 } | 488 } |
480 } | 489 } |
481 | 490 |
482 /** A back button for the web view of a story that is equivalent to clicking | 491 /** A back button for the web view of a story that is equivalent to clicking |
483 * "forward" in the browser. */ | 492 * "forward" in the browser. */ |
484 // TODO(rnystrom): We have nearly identical versions of this littered through | 493 // TODO(rnystrom): We have nearly identical versions of this littered through |
485 // the sample apps. Should consolidate into one. | 494 // the sample apps. Should consolidate into one. |
486 class WebForwardButton extends View { | 495 class WebForwardButton extends View { |
487 WebForwardButton() : super(); | 496 WebForwardButton() : super(); |
488 | 497 |
489 Element render() { | 498 Element render() { |
490 return new Element.html('<div class="web-forward-button button"></div>'); | 499 return new Element.html('<div class="web-forward-button button"></div>'); |
491 } | 500 } |
492 | 501 |
493 void afterRender(Element node) { | 502 void afterRender(Element node) { |
494 addOnClick((e) { forward(); }); | 503 addOnClick((e) { |
| 504 forward(); |
| 505 }); |
495 } | 506 } |
496 | 507 |
497 /** Equivalent to [window.history.forward] */ | 508 /** Equivalent to [window.history.forward] */ |
498 static void forward() { | 509 static void forward() { |
499 window.history.forward(); | 510 window.history.forward(); |
500 } | 511 } |
501 } | 512 } |
502 | 513 |
503 /** | 514 /** |
504 * A factory that creates a view for data sources. | 515 * A factory that creates a view for data sources. |
505 */ | 516 */ |
506 class DataSourceViewFactory implements ViewFactory<Feed> { | 517 class DataSourceViewFactory implements ViewFactory<Feed> { |
507 Swarm swarm; | 518 Swarm swarm; |
508 | 519 |
509 DataSourceViewFactory(this.swarm) {} | 520 DataSourceViewFactory(this.swarm) {} |
510 | 521 |
511 View newView(Feed data) => new DataSourceView(data, swarm); | 522 View newView(Feed data) => new DataSourceView(data, swarm); |
512 | 523 |
513 int get width => ArticleViewLayout.getSingleton().width; | 524 int get width => ArticleViewLayout.getSingleton().width; |
514 int get height => null; // Width for this view isn't known. | 525 int get height => null; // Width for this view isn't known. |
515 } | 526 } |
516 | 527 |
517 | |
518 /** | 528 /** |
519 * A view for the items from a single data source. | 529 * A view for the items from a single data source. |
520 * Shows a title and a list of items. | 530 * Shows a title and a list of items. |
521 */ | 531 */ |
522 class DataSourceView extends CompositeView { | 532 class DataSourceView extends CompositeView { |
523 // TODO(jacobr): make this value be coupled with the CSS file. | 533 // TODO(jacobr): make this value be coupled with the CSS file. |
524 static const TAB_ONLY_HEIGHT = 34; | 534 static const TAB_ONLY_HEIGHT = 34; |
525 | 535 |
526 final Feed source; | 536 final Feed source; |
527 VariableSizeListView<Article> itemsView; | 537 VariableSizeListView<Article> itemsView; |
528 | 538 |
529 DataSourceView(this.source, Swarm swarm) : super('query') { | 539 DataSourceView(this.source, Swarm swarm) : super('query') { |
530 | |
531 // TODO(jacobr): make the title a view or decide it is sane for a subclass | 540 // TODO(jacobr): make the title a view or decide it is sane for a subclass |
532 // of component view to manually add some DOM cruft. | 541 // of component view to manually add some DOM cruft. |
533 node.nodes.add(new Element.html( | 542 node.nodes.add(new Element.html('<h2>${source.title}</h2>')); |
534 '<h2>${source.title}</h2>')); | |
535 | 543 |
536 // TODO(jacobr): use named arguments when available. | 544 // TODO(jacobr): use named arguments when available. |
537 itemsView = addChild(new VariableSizeListView<Article>( | 545 itemsView = addChild(new VariableSizeListView<Article>( |
538 source.articles, | 546 source.articles, |
539 new ArticleViewFactory(swarm), | 547 new ArticleViewFactory(swarm), |
540 true, /* scrollable */ | 548 true, |
541 true, /* vertical */ | 549 /* scrollable */ |
542 swarm.state.currentArticle, /* selectedItem */ | 550 true, |
| 551 /* vertical */ |
| 552 swarm.state.currentArticle, |
| 553 /* selectedItem */ |
543 !Device.supportsTouch /* snapToArticles */, | 554 !Device.supportsTouch /* snapToArticles */, |
544 false /* paginate */, | 555 false /* paginate */, |
545 true /* removeClippedViews */, | 556 true /* removeClippedViews */, |
546 !Device.supportsTouch /* showScrollbar */)); | 557 !Device.supportsTouch /* showScrollbar */)); |
547 itemsView.addClass('story-section'); | 558 itemsView.addClass('story-section'); |
548 | 559 |
549 node.nodes.add(new Element.html('<div class="query-name-shadow"></div>')); | 560 node.nodes.add(new Element.html('<div class="query-name-shadow"></div>')); |
550 | 561 |
551 // Clicking the view (i.e. its title area) unmaximizes to show the entire | 562 // Clicking the view (i.e. its title area) unmaximizes to show the entire |
552 // view. | 563 // view. |
553 node.onMouseDown.listen((e) { | 564 node.onMouseDown.listen((e) { |
554 swarm.state.storyMaximized.value = false; | 565 swarm.state.storyMaximized.value = false; |
555 }); | 566 }); |
556 } | 567 } |
557 } | 568 } |
558 | 569 |
559 /** A button that toggles between states. */ | 570 /** A button that toggles between states. */ |
560 class ToggleButton extends View { | 571 class ToggleButton extends View { |
561 EventListeners onChanged; | 572 EventListeners onChanged; |
562 List<String> states; | 573 List<String> states; |
563 | 574 |
564 ToggleButton(this.states) | 575 ToggleButton(this.states) |
565 : super(), | 576 : super(), |
566 onChanged = new EventListeners(); | 577 onChanged = new EventListeners(); |
567 | 578 |
568 Element render() => new Element.tag('button'); | 579 Element render() => new Element.tag('button'); |
569 | 580 |
570 void afterRender(Element node) { | 581 void afterRender(Element node) { |
571 state = states[0]; | 582 state = states[0]; |
572 node.onClick.listen((event) { toggle(); }); | 583 node.onClick.listen((event) { |
| 584 toggle(); |
| 585 }); |
573 } | 586 } |
574 | 587 |
575 String get state { | 588 String get state { |
576 final currentState = node.innerHtml; | 589 final currentState = node.innerHtml; |
577 assert(states.indexOf(currentState, 0) >= 0); | 590 assert(states.indexOf(currentState, 0) >= 0); |
578 return currentState; | 591 return currentState; |
579 } | 592 } |
580 | 593 |
581 void set state(String state) { | 594 void set state(String state) { |
582 assert(states.indexOf(state, 0) >= 0); | 595 assert(states.indexOf(state, 0) >= 0); |
583 node.innerHtml = state; | 596 node.innerHtml = state; |
584 onChanged.fire(null); | 597 onChanged.fire(null); |
585 } | 598 } |
586 | 599 |
587 void toggle() { | 600 void toggle() { |
588 final oldState = state; | 601 final oldState = state; |
589 int index = states.indexOf(oldState, 0); | 602 int index = states.indexOf(oldState, 0); |
590 index = (index + 1) % states.length; | 603 index = (index + 1) % states.length; |
591 state = states[index]; | 604 state = states[index]; |
592 } | 605 } |
593 } | 606 } |
594 | 607 |
595 /** | 608 /** |
596 * A factory that creates a view for generic items. | 609 * A factory that creates a view for generic items. |
597 */ | 610 */ |
598 class ArticleViewFactory implements VariableSizeViewFactory<Article> { | 611 class ArticleViewFactory implements VariableSizeViewFactory<Article> { |
599 Swarm swarm; | 612 Swarm swarm; |
600 | 613 |
601 ArticleViewLayout layout; | 614 ArticleViewLayout layout; |
602 ArticleViewFactory(this.swarm) | 615 ArticleViewFactory(this.swarm) : layout = ArticleViewLayout.getSingleton(); |
603 : layout = ArticleViewLayout.getSingleton(); | |
604 | 616 |
605 View newView(Article item) => new ArticleView(item, swarm, layout); | 617 View newView(Article item) => new ArticleView(item, swarm, layout); |
606 | 618 |
607 int getWidth(Article item) => layout.width; | 619 int getWidth(Article item) => layout.width; |
608 int getHeight(Article item) => layout.computeHeight(item); | 620 int getHeight(Article item) => layout.computeHeight(item); |
609 } | 621 } |
610 | 622 |
611 class ArticleViewMetrics { | 623 class ArticleViewMetrics { |
612 final int height; | 624 final int height; |
613 final int titleLines; | 625 final int titleLines; |
(...skipping 16 matching lines...) Expand all Loading... |
630 static const TOTAL_MARGIN = 16 * 2 + 70; | 642 static const TOTAL_MARGIN = 16 * 2 + 70; |
631 static const MIN_TITLE_HEIGHT = 36; | 643 static const MIN_TITLE_HEIGHT = 36; |
632 static const MAX_TITLE_LINES = 2; | 644 static const MAX_TITLE_LINES = 2; |
633 static const MAX_BODY_LINES = 4; | 645 static const MAX_BODY_LINES = 4; |
634 | 646 |
635 MeasureText measureTitleText; | 647 MeasureText measureTitleText; |
636 MeasureText measureBodyText; | 648 MeasureText measureBodyText; |
637 | 649 |
638 int width; | 650 int width; |
639 static ArticleViewLayout _singleton; | 651 static ArticleViewLayout _singleton; |
640 ArticleViewLayout() : | 652 ArticleViewLayout() |
641 measureBodyText = new MeasureText(BODY_FONT), | 653 : measureBodyText = new MeasureText(BODY_FONT), |
642 measureTitleText = new MeasureText(TITLE_FONT) { | 654 measureTitleText = new MeasureText(TITLE_FONT) { |
643 num screenWidth = window.screen.width; | 655 num screenWidth = window.screen.width; |
644 width = DESKTOP_WIDTH; | 656 width = DESKTOP_WIDTH; |
645 } | 657 } |
646 | 658 |
647 static ArticleViewLayout getSingleton() { | 659 static ArticleViewLayout getSingleton() { |
648 if (_singleton == null) { | 660 if (_singleton == null) { |
649 _singleton = new ArticleViewLayout(); | 661 _singleton = new ArticleViewLayout(); |
650 } | 662 } |
651 return _singleton; | 663 return _singleton; |
652 } | 664 } |
653 | 665 |
654 int computeHeight(Article item) { | 666 int computeHeight(Article item) { |
655 if (item == null) { | 667 if (item == null) { |
656 // TODO(jacobr): find out why this is happening.. | 668 // TODO(jacobr): find out why this is happening.. |
657 print('Null item encountered.'); | 669 print('Null item encountered.'); |
658 return 0; | 670 return 0; |
659 } | 671 } |
660 | 672 |
661 return computeLayout(item, null, null).height; | 673 return computeLayout(item, null, null).height; |
662 } | 674 } |
663 | 675 |
664 /** | 676 /** |
665 * titleContainer and snippetContainer may be null in which case the size is | 677 * titleContainer and snippetContainer may be null in which case the size is |
666 * computed but no actual layout is performed. | 678 * computed but no actual layout is performed. |
667 */ | 679 */ |
668 ArticleViewMetrics computeLayout(Article item, | 680 ArticleViewMetrics computeLayout( |
669 StringBuffer titleBuffer, | 681 Article item, StringBuffer titleBuffer, StringBuffer snippetBuffer) { |
670 StringBuffer snippetBuffer) { | |
671 int titleWidth = width - BODY_MARGIN_LEFT; | 682 int titleWidth = width - BODY_MARGIN_LEFT; |
672 | 683 |
673 if (item.hasThumbnail) { | 684 if (item.hasThumbnail) { |
674 titleWidth = width - TITLE_MARGIN_LEFT; | 685 titleWidth = width - TITLE_MARGIN_LEFT; |
675 } | 686 } |
676 | 687 |
677 final titleLines = measureTitleText.addLineBrokenText(titleBuffer, | 688 final titleLines = measureTitleText.addLineBrokenText( |
678 item.title, titleWidth, MAX_TITLE_LINES); | 689 titleBuffer, item.title, titleWidth, MAX_TITLE_LINES); |
679 final bodyLines = measureBodyText.addLineBrokenText(snippetBuffer, | 690 final bodyLines = measureBodyText.addLineBrokenText( |
680 item.textBody, width - BODY_MARGIN_LEFT, MAX_BODY_LINES); | 691 snippetBuffer, item.textBody, width - BODY_MARGIN_LEFT, MAX_BODY_LINES); |
681 | 692 |
682 int height = bodyLines * LINE_HEIGHT + TOTAL_MARGIN; | 693 int height = bodyLines * LINE_HEIGHT + TOTAL_MARGIN; |
683 | 694 |
684 if (bodyLines == 0) { | 695 if (bodyLines == 0) { |
685 height = 92; | 696 height = 92; |
686 } | 697 } |
687 | 698 |
688 return new ArticleViewMetrics(height, titleLines, bodyLines); | 699 return new ArticleViewMetrics(height, titleLines, bodyLines); |
689 } | 700 } |
690 } | 701 } |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
734 // Remove the snippet entirely if it's empty. This keeps it from taking up | 745 // Remove the snippet entirely if it's empty. This keeps it from taking up |
735 // space and pushing the padding down. | 746 // space and pushing the padding down. |
736 if ((item.textBody == null) || (item.textBody.trim() == '')) { | 747 if ((item.textBody == null) || (item.textBody.trim() == '')) { |
737 node.querySelector('.snippet').remove(); | 748 node.querySelector('.snippet').remove(); |
738 } | 749 } |
739 | 750 |
740 return node; | 751 return node; |
741 } | 752 } |
742 | 753 |
743 void afterRender(Element node) { | 754 void afterRender(Element node) { |
744 | |
745 // Select this view's item. | 755 // Select this view's item. |
746 addOnClick((e) { | 756 addOnClick((e) { |
747 // Mark the item as read, so it shows as read in other views | 757 // Mark the item as read, so it shows as read in other views |
748 item.unread.value = false; | 758 item.unread.value = false; |
749 | 759 |
750 final oldArticle = swarm.state.currentArticle.value; | 760 final oldArticle = swarm.state.currentArticle.value; |
751 swarm.state.currentArticle.value = item; | 761 swarm.state.currentArticle.value = item; |
752 swarm.state.storyTextMode.value = true; | 762 swarm.state.storyTextMode.value = true; |
753 if (oldArticle == null) { | 763 if (oldArticle == null) { |
754 swarm.state.pushToHistory(); | 764 swarm.state.pushToHistory(); |
(...skipping 29 matching lines...) Expand all Loading... |
784 * article that is currently outside of the visible area. | 794 * article that is currently outside of the visible area. |
785 */ | 795 */ |
786 void _updateViewForSelectedArticle() { | 796 void _updateViewForSelectedArticle() { |
787 Article selArticle = swarm.state.selectedArticle.value; | 797 Article selArticle = swarm.state.selectedArticle.value; |
788 if (swarm.state.hasArticleSelected) { | 798 if (swarm.state.hasArticleSelected) { |
789 // Ensure that the selected article is visible in the view. | 799 // Ensure that the selected article is visible in the view. |
790 if (!swarm.state.inMainView) { | 800 if (!swarm.state.inMainView) { |
791 // Story View. | 801 // Story View. |
792 swarm.frontView.detachedView.itemsView.showView(selArticle); | 802 swarm.frontView.detachedView.itemsView.showView(selArticle); |
793 } else { | 803 } else { |
794 if(swarm.frontView.currentSection.inCurrentView(selArticle)) { | 804 if (swarm.frontView.currentSection.inCurrentView(selArticle)) { |
795 // Scroll horizontally if needed. | 805 // Scroll horizontally if needed. |
796 swarm.frontView.currentSection.dataSourceView.showView( | 806 swarm.frontView.currentSection.dataSourceView |
797 selArticle.dataSource); | 807 .showView(selArticle.dataSource); |
798 DataSourceView dataView = swarm.frontView.currentSection | 808 DataSourceView dataView = |
799 .findView(selArticle.dataSource); | 809 swarm.frontView.currentSection.findView(selArticle.dataSource); |
800 if(dataView != null) { | 810 if (dataView != null) { |
801 dataView.itemsView.showView(selArticle); | 811 dataView.itemsView.showView(selArticle); |
802 } | 812 } |
803 } | 813 } |
804 } | 814 } |
805 } | 815 } |
806 } | 816 } |
807 | 817 |
808 String getDataUriForImage(final img) { | 818 String getDataUriForImage(final img) { |
809 // TODO(hiltonc,jimhug) eval perf of this vs. reusing one canvas element | 819 // TODO(hiltonc,jimhug) eval perf of this vs. reusing one canvas element |
810 final CanvasElement canvas = new CanvasElement( | 820 final CanvasElement canvas = |
811 height: img.height, width: img.width); | 821 new CanvasElement(height: img.height, width: img.width); |
812 | 822 |
813 final CanvasRenderingContext2D ctx = canvas.getContext("2d"); | 823 final CanvasRenderingContext2D ctx = canvas.getContext("2d"); |
814 ctx.drawImage(img, 0, 0, img.width, img.height); | 824 ctx.drawImage(img, 0, 0, img.width, img.height); |
815 | 825 |
816 return canvas.toDataUrl("image/png"); | 826 return canvas.toDataUrl("image/png"); |
817 } | 827 } |
818 | 828 |
819 /** | 829 /** |
820 * Update this view's selected appearance based on the currently selected | 830 * Update this view's selected appearance based on the currently selected |
821 * Article. | 831 * Article. |
(...skipping 19 matching lines...) Expand all Loading... |
841 final Swarm swarm; | 851 final Swarm swarm; |
842 final Article item; | 852 final Article item; |
843 | 853 |
844 View _pagedStory; | 854 View _pagedStory; |
845 | 855 |
846 StoryContentView(this.swarm, this.item) : super(); | 856 StoryContentView(this.swarm, this.item) : super(); |
847 | 857 |
848 get childViews => [_pagedStory]; | 858 get childViews => [_pagedStory]; |
849 | 859 |
850 Element render() { | 860 Element render() { |
851 final storyContent = new Element.html( | 861 final storyContent = |
852 '<div class="story-content">${item.htmlBody}</div>'); | 862 new Element.html('<div class="story-content">${item.htmlBody}</div>'); |
853 for (Element element in storyContent.querySelectorAll( | 863 for (Element element in storyContent.querySelectorAll( |
854 "iframe, script, style, object, embed, frameset, frame")) { | 864 "iframe, script, style, object, embed, frameset, frame")) { |
855 element.remove(); | 865 element.remove(); |
856 } | 866 } |
857 _pagedStory = new PagedContentView(new View.fromNode(storyContent)); | 867 _pagedStory = new PagedContentView(new View.fromNode(storyContent)); |
858 | 868 |
859 // Modify all links to open in new windows.... | 869 // Modify all links to open in new windows.... |
860 // TODO(jacobr): would it be better to add an event listener on click that | 870 // TODO(jacobr): would it be better to add an event listener on click that |
861 // intercepts these instead? | 871 // intercepts these instead? |
862 for (AnchorElement anchor in storyContent.querySelectorAll('a')) { | 872 for (AnchorElement anchor in storyContent.querySelectorAll('a')) { |
(...skipping 26 matching lines...) Expand all Loading... |
889 class SectionView extends CompositeView { | 899 class SectionView extends CompositeView { |
890 final Section section; | 900 final Section section; |
891 final Swarm swarm; | 901 final Swarm swarm; |
892 final DataSourceViewFactory _viewFactory; | 902 final DataSourceViewFactory _viewFactory; |
893 final View loadingText; | 903 final View loadingText; |
894 ListView<Feed> dataSourceView; | 904 ListView<Feed> dataSourceView; |
895 PageNumberView pageNumberView; | 905 PageNumberView pageNumberView; |
896 final PageState pageState; | 906 final PageState pageState; |
897 | 907 |
898 SectionView(this.swarm, this.section, this._viewFactory) | 908 SectionView(this.swarm, this.section, this._viewFactory) |
899 : super('section-view'), | 909 : super('section-view'), |
900 loadingText = new View.html('<div class="loading-section"></div>'), | 910 loadingText = new View.html('<div class="loading-section"></div>'), |
901 pageState = new PageState() { | 911 pageState = new PageState() { |
902 addChild(loadingText); | 912 addChild(loadingText); |
903 } | 913 } |
904 | 914 |
905 /** | 915 /** |
906 * Hides the loading text, reloads the data sources, and shows them. | 916 * Hides the loading text, reloads the data sources, and shows them. |
907 */ | 917 */ |
908 void showSources() { | 918 void showSources() { |
909 loadingText.node.style.display = 'none'; | 919 loadingText.node.style.display = 'none'; |
910 | 920 |
911 // Lazy initialize the data source view. | 921 // Lazy initialize the data source view. |
912 if (dataSourceView == null) { | 922 if (dataSourceView == null) { |
913 // TODO(jacobr): use named arguments when available. | 923 // TODO(jacobr): use named arguments when available. |
914 dataSourceView = new ListView<Feed>( | 924 dataSourceView = new ListView<Feed>( |
915 section.feeds, _viewFactory, | 925 section.feeds, |
| 926 _viewFactory, |
916 true /* scrollable */, | 927 true /* scrollable */, |
917 false /* vertical */, | 928 false /* vertical */, |
918 null /* selectedItem */, | 929 null /* selectedItem */, |
919 true /* snapToItems */, | 930 true /* snapToItems */, |
920 true /* paginate */, | 931 true /* paginate */, |
921 true /* removeClippedViews */, | 932 true /* removeClippedViews */, |
922 false, /* showScrollbar */ | 933 false, |
| 934 /* showScrollbar */ |
923 pageState); | 935 pageState); |
924 dataSourceView.addClass("data-source-view"); | 936 dataSourceView.addClass("data-source-view"); |
925 addChild(dataSourceView); | 937 addChild(dataSourceView); |
926 | 938 |
927 pageNumberView = addChild(new PageNumberView(pageState)); | 939 pageNumberView = addChild(new PageNumberView(pageState)); |
928 | 940 |
929 node.style.opacity = '1'; | 941 node.style.opacity = '1'; |
930 } else { | 942 } else { |
931 addChild(dataSourceView); | 943 addChild(dataSourceView); |
932 addChild(pageNumberView); | 944 addChild(pageNumberView); |
(...skipping 30 matching lines...) Expand all Loading... |
963 * [Feed]. | 975 * [Feed]. |
964 */ | 976 */ |
965 DataSourceView findView(Feed dataSource) { | 977 DataSourceView findView(Feed dataSource) { |
966 return dataSourceView.getSubview(dataSourceView.findIndex(dataSource)); | 978 return dataSourceView.getSubview(dataSourceView.findIndex(dataSource)); |
967 } | 979 } |
968 | 980 |
969 bool inCurrentView(Article article) { | 981 bool inCurrentView(Article article) { |
970 return dataSourceView.findIndex(article.dataSource) != null; | 982 return dataSourceView.findIndex(article.dataSource) != null; |
971 } | 983 } |
972 } | 984 } |
OLD | NEW |