| 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 |