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.
This commit is contained in:
Piotr Sarnacki 2014-03-04 14:02:22 +01:00
parent b4f3eac0bf
commit 535a873dc0
6 changed files with 228 additions and 72 deletions

View File

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

View File

@ -1,12 +1,51 @@
@Travis.Tailing = ->
@position = $(window).scrollTop()
$(window).scroll( $.throttle( 200, @onScroll.bind(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')

View File

@ -1,7 +1,14 @@
<div id="log-container">
<a href="#" id="tail" {{action "toggleTailing" target="view"}}>
<span class="status"></span>
<label>Follow logs</label>
<label>
{{#if view.job.isFinished}}
Scroll to end of logs
{{else}}
Follow logs
{{/if}}
</label>
</a>
<pre id="log" class="ansi"></pre>

View File

@ -0,0 +1,89 @@
fakeWindow =
scroll: sinon.spy()
scrollTop: sinon.stub().returns(0)
height: sinon.stub().returns(40)
element = jQuery('<div id="specTail"></div>')
log = jQuery('<div id="specLog"></div>')
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)

View File

@ -88,7 +88,8 @@ pre#log
#log-container
position: relative
#log-container #tail
#log-container
#tail
z-index: 99
position: absolute
display: block
@ -118,6 +119,14 @@ pre#log
label
display: inline
&.scrolling
position: fixed
right: 32px
&.bottom
bottom: 45px
top: inherit
.status
display: inline-block
margin-right: 1px
@ -129,3 +138,17 @@ pre#log
&.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

View File

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