From 535a873dc065bc72747b1d3fb2e9db69b3b4ff16 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 4 Mar 2014 14:02:22 +0100 Subject: [PATCH] Make "to top" link visible when scrolling through the log These are the changes by @dmathieu (reverted at d7bef2b) slightly changed to allow us to use them also on Travis Pro. The change is to still use onScroll calculations in order to position elements instead of using "position: fixed". The latter method is harder to use when element needs to be positioned relatively to other element - on Pro we would have to still calculate the position because of the right sidebar. --- assets/scripts/app/app.coffee | 3 +- assets/scripts/app/tailing.coffee | 93 ++++++++++++++----- assets/scripts/app/templates/jobs/pre.hbs | 9 +- assets/scripts/spec/unit/tailing_spec.coffee | 89 ++++++++++++++++++ assets/styles/main/log.sass | 97 ++++++++++++-------- assets/styles/main/sponsors.sass | 9 -- 6 files changed, 228 insertions(+), 72 deletions(-) create mode 100644 assets/scripts/spec/unit/tailing_spec.coffee diff --git a/assets/scripts/app/app.coffee b/assets/scripts/app/app.coffee index 5c454d2a..ef4b2682 100644 --- a/assets/scripts/app/app.coffee +++ b/assets/scripts/app/app.coffee @@ -35,7 +35,8 @@ unless window.TravisApplication @slider = new Travis.Slider() @pusher = new Travis.Pusher(Travis.config.pusher_key) if Travis.config.pusher_key - @tailing = new Travis.Tailing() + @tailing = new Travis.Tailing($(window), '#tail', '#log') + @toTop = new Travis.ToTop($(window), '.to-top', '#log-container') @set('auth', Travis.Auth.create(app: this, endpoint: Travis.config.api_endpoint)) diff --git a/assets/scripts/app/tailing.coffee b/assets/scripts/app/tailing.coffee index fab3fec8..666c2c51 100644 --- a/assets/scripts/app/tailing.coffee +++ b/assets/scripts/app/tailing.coffee @@ -1,12 +1,51 @@ -@Travis.Tailing = -> - @position = $(window).scrollTop() - $(window).scroll( $.throttle( 200, @onScroll.bind(this) ) ) - this +class Travis.ToTop + # NOTE: I could have probably extract fixed positioning from + # Tailing, but then I would need to parametrize positionElement + # function to make it flexible to handle both cases. In that + # situation I prefer a bit less DRY code over simplicity of + # the calculations. + constructor: (@window, @element_selector, @container_selector) -> + @position = @window.scrollTop() + @window.scroll( $.throttle( 200, @onScroll.bind(this) ) ) + this -$.extend Travis.Tailing.prototype, + element: -> + $(@element_selector) + container: -> + $(@container_selector) + + onScroll: -> + @positionElement() + + positionElement: -> + element = @element() + container = @container() + return if element.length is 0 + containerHeight = container.height() + windowHeight = @window.height() + offset = container.offset().top + containerHeight - (@window.scrollTop() + windowHeight) + max = containerHeight - windowHeight + offset = max if offset > max + console.log(offset, max) + if offset > 0 + element.css(bottom: offset) + else + element.css(bottom: 2) + +class @Travis.Tailing options: timeout: 200 + tail: -> + $(@tail_selector) + log: -> + $(@log_selector) + + constructor: (@window, @tail_selector, @log_selector) -> + @position = @window.scrollTop() + @window.scroll( $.throttle( 200, @onScroll.bind(this) ) ) + this + run: -> @autoScroll() @positionButton() @@ -16,37 +55,43 @@ $.extend Travis.Tailing.prototype, if @active() then @stop() else @start() active: -> - $('#tail').hasClass('active') + @tail().hasClass('active') start: -> - $('#tail').addClass('active') + @tail().addClass('active') @run() stop: -> - $('#tail').removeClass('active') + @tail().removeClass('active') autoScroll: -> - return unless @active() - win = $(window) - log = $('#log') - logBottom = log.offset().top + log.outerHeight() + 40 - winBottom = win.scrollTop() + win.height() - win.scrollTop(logBottom - win.height()) if logBottom - winBottom > 0 + return false unless @active() + logBottom = @log().offset().top + @log().outerHeight() + 40 + winBottom = @window.scrollTop() + @window.height() + + if logBottom - winBottom > 0 + @window.scrollTop(logBottom - @window.height()) + true + else + false onScroll: -> @positionButton() - position = $(window).scrollTop() + position = @window.scrollTop() @stop() if position < @position @position = position positionButton: -> - tail = $('#tail') - return if tail.length is 0 - offset = $(window).scrollTop() - $('#log').offset().top - max = $('#log').height() - $('#tail').height() + 5 - offset = max if offset > max - if offset > 0 - tail.css(top: offset - 2) - else - tail.css(top: 0) + return if @tail().length is 0 + offset = @window.scrollTop() - @log().offset().top + max = @log().height() - @tail().height() + 5 + if offset > 0 && offset <= max + @tail().removeClass('bottom') + @tail().addClass('scrolling') + else + if offset > max + @tail().addClass('bottom') + else + @tail().removeClass('bottom') + @tail().removeClass('scrolling') diff --git a/assets/scripts/app/templates/jobs/pre.hbs b/assets/scripts/app/templates/jobs/pre.hbs index e2a33456..0184bee8 100644 --- a/assets/scripts/app/templates/jobs/pre.hbs +++ b/assets/scripts/app/templates/jobs/pre.hbs @@ -1,7 +1,14 @@
- + +

 
diff --git a/assets/scripts/spec/unit/tailing_spec.coffee b/assets/scripts/spec/unit/tailing_spec.coffee
new file mode 100644
index 00000000..18a836f0
--- /dev/null
+++ b/assets/scripts/spec/unit/tailing_spec.coffee
@@ -0,0 +1,89 @@
+fakeWindow =
+  scroll: sinon.spy()
+  scrollTop: sinon.stub().returns(0)
+  height: sinon.stub().returns(40)
+element = jQuery('
') +log = jQuery('
') +tail = new Travis.Tailing(fakeWindow, '#specTail', '#specLog') +tail.tail = -> element +tail.log = -> log + +module "Travis.Tailing", + setup: -> + jQuery('body').append(element) + jQuery('body').append(log) + + teardown: -> + element.remove() + log.remove() + tail.stop() + +test "toggle", -> + equal(element.hasClass('active'), false) + tail.toggle() + equal(element.hasClass('active'), true) + tail.toggle() + stop() + + Ember.run.later -> + start() + equal(element.hasClass('active'), false) + , 300 + +test "active", -> + equal(tail.active(), false) + element.addClass('active') + equal(tail.active(), true) + +test "autoscroll when inactive", -> + tail.scrollTo = sinon.spy() + + equal(tail.active(), false) + equal(tail.autoScroll(), false) + equal(tail.scrollTo.called, false) + +test "autoscroll", -> + element.addClass('active') + log.offset = -> {top: 1} + log.outerHeight = -> 1 + + equal(tail.active(), true) + equal(tail.autoScroll(), true) + equal(fakeWindow.scrollTop.calledWith(2), true) + +test "autoscroll when we're at the bottom", -> + element.addClass('active') + log.offset = -> {top: 0} + log.outerHeight = -> 0 + + equal(tail.active(), true) + equal(tail.autoScroll(), false) + equal(fakeWindow.scrollTop.calledWith(0), false) + +test 'should stop scrolling if the position changed', -> + element.addClass('active') + tail.position = 100 + tail.onScroll() + equal(element.hasClass('active'), false) + +test 'positionButton adds the scrolling class', -> + log.offset = -> {top: -1} + + tail.positionButton() + equal(element.hasClass('scrolling'), true) + equal(element.hasClass('bottom'), false) + +test 'positionButton removes the scrolling class', -> + log.offset = -> {top: 1} + tail.positionButton() + equal(element.hasClass('scrolling'), false) + equal(element.hasClass('bottom'), false) + +test 'positionButton sets the button as bottom', -> + log.offset = -> {top: -100} + log.height = -> 50 + tail.height = -> 1 + + tail.positionButton() + equal(element.hasClass('scrolling'), false) + equal(element.hasClass('bottom'), true) diff --git a/assets/styles/main/log.sass b/assets/styles/main/log.sass index a6083550..cb9afcd8 100644 --- a/assets/styles/main/log.sass +++ b/assets/styles/main/log.sass @@ -88,44 +88,67 @@ pre#log #log-container position: relative -#log-container #tail - z-index: 99 - position: absolute - display: block - top: 0 - right: 2px - margin: 13px 10px 0 0 - padding: 0 2px 0 3px - color: #666 - text-shadow: 0px 1px 0px #fff - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif - font-size: $font-size-tiny - line-height: 14px - text-decoration: none - white-space: nowrap - border: 1px solid #bbb - border-top-color: #ddd - border-bottom-color: #bbb - @include border-radius(8px) - @include background(linear-gradient(#fff, #e0e0e0)) +#log-container + #tail + z-index: 99 + position: absolute + display: block + top: 0 + right: 2px + margin: 13px 10px 0 0 + padding: 0 2px 0 3px + color: #666 + text-shadow: 0px 1px 0px #fff + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif + font-size: $font-size-tiny + line-height: 14px + text-decoration: none + white-space: nowrap + border: 1px solid #bbb + border-top-color: #ddd + border-bottom-color: #bbb + @include border-radius(8px) + @include background(linear-gradient(#fff, #e0e0e0)) - label - display: none - cursor: pointer - - &:hover - padding: 1px 4px 1px 6px label - display: inline + display: none + cursor: pointer - .status - display: inline-block - margin-right: 1px - width: 8px - height: 8px - background-color: #aaa - @include border-radius(4px) - @include box-shadow(white 1px 1px 2px) + &:hover + padding: 1px 4px 1px 6px + label + display: inline - &.active .status - background-color: #6b0 + &.scrolling + position: fixed + right: 32px + + &.bottom + bottom: 45px + top: inherit + + .status + display: inline-block + margin-right: 1px + width: 8px + height: 8px + background-color: #aaa + @include border-radius(4px) + @include box-shadow(white 1px 1px 2px) + + &.active .status + background-color: #6b0 + + .to-top + z-index: 99 + position: absolute + display: block + bottom: 2px + right: 2px + + width: 50px + margin-right: 2px + padding-right: 16px + text-align: right + color: #999 + background: inline-image('ui/to-top.png') no-repeat right 6px diff --git a/assets/styles/main/sponsors.sass b/assets/styles/main/sponsors.sass index cfbbdc16..e5c79998 100644 --- a/assets/styles/main/sponsors.sass +++ b/assets/styles/main/sponsors.sass @@ -3,12 +3,3 @@ // float: left margin-top: 0 color: #999 - .to-top - display: inline-block - width: 50px - float: right - margin-right: 2px - padding-right: 16px - text-align: right - color: #999 - background: inline-image('ui/to-top.png') no-repeat right 6px