Split Topic Progres widget into its own controller, view, template

This commit is contained in:
Robin Ward 2014-06-12 12:52:15 -04:00
parent e5eccfc70a
commit 33e9bc68fc
9 changed files with 142 additions and 139 deletions

View file

@ -0,0 +1,46 @@
export default Ember.ObjectController.extend({
needs: ['topic'],
progressPosition: null,
streamPercentage: function() {
if (!this.get('postStream.loaded')) { return 0; }
if (this.get('postStream.highest_post_number') === 0) { return 0; }
var perc = this.get('progressPosition') / this.get('postStream.filteredPostsCount');
return (perc > 1.0) ? 1.0 : perc;
}.property('postStream.loaded', 'progressPosition', 'postStream.filteredPostsCount'),
jumpTopDisabled: function() {
return (this.get('progressPosition') < 2);
}.property('progressPosition'),
filteredPostCountChanged: function(){
if(this.get('postStream.filteredPostsCount') < this.get('progressPosition')){
this.set('progressPosition', this.get('postStream.filteredPostsCount'));
}
}.observes('postStream.filteredPostsCount'),
jumpBottomDisabled: function() {
return this.get('progressPosition') >= this.get('postStream.filteredPostsCount') ||
this.get('progressPosition') >= this.get('highest_post_number');
}.property('postStream.filteredPostsCount', 'highest_post_number', 'progressPosition'),
hideProgress: function() {
if (!this.get('postStream.loaded')) return true;
if (!this.get('currentPost')) return true;
if (this.get('postStream.filteredPostsCount') < 2) return true;
return false;
}.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'),
hugeNumberOfPosts: function() {
return (this.get('postStream.filteredPostsCount') >= Discourse.SiteSettings.short_progress_text_threshold);
}.property('highest_post_number'),
jumpToBottomTitle: function() {
if (this.get('hugeNumberOfPosts')) {
return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('highest_post_number')});
} else {
return I18n.t('topic.progress.jump_bottom');
}
}.property('hugeNumberOfPosts', 'highest_post_number')
});

View file

@ -8,7 +8,7 @@
**/
Discourse.TopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, {
multiSelect: false,
needs: ['header', 'modal', 'composer', 'quote-button', 'search'],
needs: ['header', 'modal', 'composer', 'quote-button', 'search', 'topic-progress'],
allPostsSelected: false,
editingTopic: false,
selectedPosts: null,
@ -391,21 +391,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
return post.get('post_number') === 1 && post.get('topic.expandable_first_post');
}.property(),
jumpTopDisabled: function() {
return (this.get('progressPosition') < 2);
}.property('progressPosition'),
filteredPostCountChanged: function(){
if(this.get('postStream.filteredPostsCount') < this.get('progressPosition')){
this.set('progressPosition', this.get('postStream.filteredPostsCount'));
}
}.observes('postStream.filteredPostsCount'),
jumpBottomDisabled: function() {
return this.get('progressPosition') >= this.get('postStream.filteredPostsCount') ||
this.get('progressPosition') >= this.get('highest_post_number');
}.property('postStream.filteredPostsCount', 'highest_post_number', 'progressPosition'),
canMergeTopic: function() {
if (!this.get('details.can_move_posts')) return false;
return (this.get('selectedPostsCount') > 0);
@ -451,13 +436,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
hasError: Ember.computed.or('errorBodyHtml', 'message'),
streamPercentage: function() {
if (!this.get('postStream.loaded')) { return 0; }
if (this.get('postStream.highest_post_number') === 0) { return 0; }
var perc = this.get('progressPosition') / this.get('postStream.filteredPostsCount');
return (perc > 1.0) ? 1.0 : perc;
}.property('postStream.loaded', 'progressPosition', 'postStream.filteredPostsCount'),
multiSelectChanged: function() {
// Deselect all posts when multi select is turned off
if (!this.get('multiSelect')) {
@ -465,25 +443,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
}
}.observes('multiSelect'),
hideProgress: function() {
if (!this.get('postStream.loaded')) return true;
if (!this.get('currentPost')) return true;
if (this.get('postStream.filteredPostsCount') < 2) return true;
return false;
}.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'),
hugeNumberOfPosts: function() {
return (this.get('postStream.filteredPostsCount') >= Discourse.SiteSettings.short_progress_text_threshold);
}.property('highest_post_number'),
jumpToBottomTitle: function() {
if (this.get('hugeNumberOfPosts')) {
return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('highest_post_number')});
} else {
return I18n.t('topic.progress.jump_bottom');
}
}.property('hugeNumberOfPosts', 'highest_post_number'),
deselectPost: function(post) {
this.get('selectedPosts').removeObject(post);
@ -663,7 +622,7 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
lastLoadedPost = postStream.get('lastLoadedPost'),
index = postStream.get('stream').indexOf(post.get('id'))+1;
this.set('progressPosition', index);
this.set('controllers.topic-progress.progressPosition', index);
if (lastLoadedPost && lastLoadedPost === post) {
postStream.appendMore();

View file

@ -162,7 +162,9 @@ Discourse.URL = Em.Object.createWithMixins({
if (oldTopicId === newTopicId) {
Discourse.URL.replaceState(path);
var topicController = Discourse.__container__.lookup('controller:topic'),
var container = Discourse.__container__,
topicController = container.lookup('controller:topic'),
topicProgressController = container.lookup('controller:topic-progress'),
opts = {},
postStream = topicController.get('postStream');
@ -173,10 +175,10 @@ Discourse.URL = Em.Object.createWithMixins({
postStream.refresh(opts).then(function() {
topicController.setProperties({
currentPost: closest,
progressPosition: closest,
highlightOnInsert: closest,
enteredAt: new Date().getTime().toString()
});
topicProgressController.set('progressPosition', closest);
}).then(function() {
Discourse.TopicView.jumpToPost(closest);
});

View file

@ -15,6 +15,7 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({
postStream = topic.get('postStream');
var topicController = this.controllerFor('topic'),
topicProgressController = this.controllerFor('topic-progress'),
composerController = this.controllerFor('composer');
// I sincerely hope no topic gets this many posts
@ -26,11 +27,11 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({
topicController.setProperties({
currentPost: closest,
progressPosition: closest,
enteredAt: new Date().getTime().toString(),
highlightOnInsert: closest
});
topicProgressController.set('progressPosition', closest);
Discourse.TopicView.jumpToPost(closest);
if (topic.present('draft')) {

View file

@ -182,6 +182,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
Discourse.TopicTrackingState.current().trackIncoming('all');
controller.subscribe();
this.controllerFor('topic-progress').set('model', model);
// We reset screen tracking every time a topic is entered
Discourse.ScreenTrack.current().start(model.get('id'), controller);
}

View file

@ -0,0 +1,8 @@
<nav id='topic-progress' title="{{i18n topic.progress.title}}" {{bind-attr class="hideProgress:hidden"}}>
<button id='jump-top' title="{{i18n topic.progress.jump_top}}" {{bind-attr disabled="jumpTopDisabled"}} {{action jumpTop}}><i class="fa fa-arrow-circle-up"></i></button>
<div class='nums'>
<h4>{{progressPosition}}</h4><span {{bind-attr class="hugeNumberOfPosts:hidden"}}> <span>{{i18n of_value}}</span> <h4>{{postStream.filteredPostsCount}}</h4></span>
</div>
<button id='jump-bottom' {{bind-attr title="jumpToBottomTitle"}} {{bind-attr disabled="jumpBottomDisabled"}} {{action jumpBottom}}><i class="fa fa-arrow-circle-down"></i></button>
<div class='bg'>&nbsp;</div>
</nav>

View file

@ -62,16 +62,7 @@
<section class="topic-area" id='topic' data-topic-id='{{unbound id}}'>
<div class='posts-wrapper'>
<div id='topic-progress-wrapper' {{bind-attr class="dockedCounter:docked"}}>
<nav id='topic-progress' title="{{i18n topic.progress.title}}" {{bind-attr class="hideProgress:hidden"}}>
<button id='jump-top' title="{{i18n topic.progress.jump_top}}" {{bind-attr disabled="jumpTopDisabled"}} {{action jumpTop}}><i class="fa fa-arrow-circle-up"></i></button>
<div class='nums' {{bind-attr title="progressPositionTitle"}}>
<h4>{{progressPosition}}</h4><span {{bind-attr class="hugeNumberOfPosts:hidden"}}> <span>{{i18n of_value}}</span> <h4>{{postStream.filteredPostsCount}}</h4></span>
</div>
<button id='jump-bottom' {{bind-attr title="jumpToBottomTitle"}} {{bind-attr disabled="jumpBottomDisabled"}} {{action jumpBottom}}><i class="fa fa-arrow-circle-down"></i></button>
<div class='bg'>&nbsp;</div>
</nav>
</div>
{{render 'topic-progress'}}
{{#if postStream.loadingAbove}}
<div class='spinner'>{{i18n loading}}</div>

View file

@ -0,0 +1,76 @@
export default Ember.View.extend({
elementId: 'topic-progress-wrapper',
_inserted: function() {
// This get seems counter intuitive, but it's to trigger the observer on
// the streamPercentage for this view. Otherwise the process bar does not
// update.
this.get('controller.streamPercentage');
this.appEvents.on("composer:opened", this, '_dock')
.on("composer:resized", this, '_dock')
.on("composer:closed", this, '_dock')
.on("topic:scrolled", this, '_dock');
// Reflows are expensive. Cache the jQuery selector
// and the width when inserted into the DOM
this._$topicProgress = this.$('#topic-progress');
this._progressWidth = this._$topicProgress[0].offsetWidth;
}.on('didInsertElement'),
_unbindEvents: function() {
this.appEvents.off("composer:opened", this, '_dock')
.off("composer:resized", this, '_dock')
.off("composer:closed", this, '_dock')
.off('topic:scrolled', this, '_dock');
}.on('willDestroyElement'),
_updateBar: function() {
Em.run.scheduleOnce('afterRender', this, '_updateProgressBar');
}.observes('controller.streamPercentage', 'postStream.stream.@each'),
_updateProgressBar: function() {
// speeds up stuff, bypass jquery slowness and extra checks
var totalWidth = this._progressWidth,
progressWidth = this.get('controller.streamPercentage') * totalWidth;
this._$topicProgress.find('.bg')
.css("border-right-width", (progressWidth === totalWidth) ? "0px" : "1px")
.width(progressWidth);
},
_dock: function () {
var maximumOffset = $('#topic-footer-buttons').offset(),
composerHeight = $('#reply-control').height() || 0,
$topicProgressWrapper = this.$(),
style = $topicProgressWrapper.attr('style') || '',
isDocked = false,
offset = window.pageYOffset || $('html').scrollTop();
if (maximumOffset) {
var threshold = maximumOffset.top,
windowHeight = $(window).height(),
topicProgressHeight = $('#topic-progress').height();
isDocked = offset >= threshold - windowHeight + topicProgressHeight + composerHeight;
}
if (composerHeight > 0) {
if (isDocked) {
if (style.indexOf('bottom') >= 0) {
$topicProgressWrapper.css('bottom', '');
}
} else {
var height = composerHeight + "px";
if ($topicProgressWrapper.css('bottom') !== height) {
$topicProgressWrapper.css('bottom', height);
}
}
} else {
if (style.indexOf('bottom') >= 0) {
$topicProgressWrapper.css('bottom', '');
}
}
}
});

View file

@ -23,35 +23,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
postStream: Em.computed.alias('controller.postStream'),
updateBar: function() {
Em.run.scheduleOnce('afterRender', this, '_updateProgressBar');
}.observes('controller.streamPercentage', 'postStream.stream.@each'),
_updateProgressBar: function() {
var $topicProgress = this._topicProgress;
// cache lookup
if (!$topicProgress) {
$topicProgress = $('#topic-progress');
if (!$topicProgress.length) {
return;
}
this._topicProgress = $topicProgress;
// CAREFUL WITH THIS AXE
// offsetWidth will cause a reflow, this ensures it only happens once
// in future it may make sense to move this offscreen to do the measurement
Discourse.TopicView._progressWidth = Discourse.TopicView._progressWidth || $topicProgress[0].offsetWidth;
}
// speeds up stuff, bypass jquery slowness and extra checks
var totalWidth = Discourse.TopicView._progressWidth,
progressWidth = this.get('controller.streamPercentage') * totalWidth;
$topicProgress.find('.bg')
.css("border-right-width", (progressWidth === totalWidth) ? "0px" : "1px")
.width(progressWidth);
},
_updateTitle: function() {
var title = this.get('topic.title');
if (title) return Discourse.set('title', _.unescape(title));
@ -64,8 +35,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
}.observes('composer'),
_enteredTopic: function() {
this._topicProgress = undefined;
// Ember is supposed to only call observers when values change but something
// in our view set up is firing this observer with the same value. This check
// prevents scrolled from being called twice.
@ -84,21 +53,12 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
self.scrolled();
});
// This get seems counter intuitive, but it's to trigger the observer on
// the streamPercentage for this view. Otherwise the process bar does not
// update.
this.get('controller.streamPercentage');
this.$().on('mouseup.discourse-redirect', '.cooked a, a.track-link', function(e) {
var $target = $(e.target);
if ($target.hasClass('mention') || $target.parents('.expanded-embed').length) { return false; }
return Discourse.ClickTrack.trackClick(e);
});
var dockProgressBar = function () { self._dockProgressBar(); };
this.appEvents.on("composer:opened", dockProgressBar)
.on("composer:resized", dockProgressBar)
.on("composer:closed", dockProgressBar);
}.on('didInsertElement'),
// This view is being removed. Shut down operations
@ -114,10 +74,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
// this happens after route exit, stuff could have trickled in
this.set('controller.controllers.header.showExtraInfo', false);
// unbind events
this.appEvents.off("composer:opened")
.off("composer:resized")
.off("composer:closed");
}.on('willDestroyElement'),
debounceLoadSuggested: Discourse.debounce(function(){
@ -181,45 +137,8 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
headerController.set('showExtraInfo', topic.get('postStream.firstPostNotLoaded'));
}
// dock the counter if necessary
this._dockProgressBar(offset);
},
_dockProgressBar: function (offset) {
var maximumOffset = $('#topic-footer-buttons').offset(),
composerHeight = $('#reply-control').height() || 0,
$topicProgressWrapper = $('#topic-progress-wrapper'),
style = $topicProgressWrapper.attr('style') || '',
isDocked = false;
offset = offset || window.pageYOffset || $('html').scrollTop();
if (maximumOffset) {
var threshold = maximumOffset.top,
windowHeight = $(window).height(),
topicProgressHeight = $('#topic-progress').height();
isDocked = offset >= threshold - windowHeight + topicProgressHeight + composerHeight;
}
if (composerHeight > 0) {
if (isDocked) {
if (style.indexOf('bottom') >= 0) {
$topicProgressWrapper.css('bottom', '');
}
} else {
var height = composerHeight + "px";
if ($topicProgressWrapper.css('bottom') !== height) {
$topicProgressWrapper.css('bottom', height);
}
}
} else {
if (style.indexOf('bottom') >= 0) {
$topicProgressWrapper.css('bottom', '');
}
}
this.set("controller.dockedCounter", isDocked);
// Trigger a scrolled event
this.appEvents.trigger('topic:scrolled');
},
topicTrackingState: function() {