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() @slider = new Travis.Slider()
@pusher = new Travis.Pusher(Travis.config.pusher_key) if Travis.config.pusher_key @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)) @set('auth', Travis.Auth.create(app: this, endpoint: Travis.config.api_endpoint))

View File

@ -1,12 +1,51 @@
@Travis.Tailing = -> class Travis.ToTop
@position = $(window).scrollTop() # NOTE: I could have probably extract fixed positioning from
$(window).scroll( $.throttle( 200, @onScroll.bind(this) ) ) # 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 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: options:
timeout: 200 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: -> run: ->
@autoScroll() @autoScroll()
@positionButton() @positionButton()
@ -16,37 +55,43 @@ $.extend Travis.Tailing.prototype,
if @active() then @stop() else @start() if @active() then @stop() else @start()
active: -> active: ->
$('#tail').hasClass('active') @tail().hasClass('active')
start: -> start: ->
$('#tail').addClass('active') @tail().addClass('active')
@run() @run()
stop: -> stop: ->
$('#tail').removeClass('active') @tail().removeClass('active')
autoScroll: -> autoScroll: ->
return unless @active() return false unless @active()
win = $(window) logBottom = @log().offset().top + @log().outerHeight() + 40
log = $('#log') winBottom = @window.scrollTop() + @window.height()
logBottom = log.offset().top + log.outerHeight() + 40
winBottom = win.scrollTop() + win.height() if logBottom - winBottom > 0
win.scrollTop(logBottom - win.height()) if logBottom - winBottom > 0 @window.scrollTop(logBottom - @window.height())
true
else
false
onScroll: -> onScroll: ->
@positionButton() @positionButton()
position = $(window).scrollTop() position = @window.scrollTop()
@stop() if position < @position @stop() if position < @position
@position = position @position = position
positionButton: -> positionButton: ->
tail = $('#tail') return if @tail().length is 0
return if tail.length is 0 offset = @window.scrollTop() - @log().offset().top
offset = $(window).scrollTop() - $('#log').offset().top max = @log().height() - @tail().height() + 5
max = $('#log').height() - $('#tail').height() + 5
offset = max if offset > max
if offset > 0
tail.css(top: offset - 2)
else
tail.css(top: 0)
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"> <div id="log-container">
<a href="#" id="tail" {{action "toggleTailing" target="view"}}> <a href="#" id="tail" {{action "toggleTailing" target="view"}}>
<span class="status"></span> <span class="status"></span>
<label>Follow logs</label>
<label>
{{#if view.job.isFinished}}
Scroll to end of logs
{{else}}
Follow logs
{{/if}}
</label>
</a> </a>
<pre id="log" class="ansi"></pre> <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 #log-container
position: relative position: relative
#log-container #tail #log-container
#tail
z-index: 99 z-index: 99
position: absolute position: absolute
display: block display: block
@ -118,6 +119,14 @@ pre#log
label label
display: inline display: inline
&.scrolling
position: fixed
right: 32px
&.bottom
bottom: 45px
top: inherit
.status .status
display: inline-block display: inline-block
margin-right: 1px margin-right: 1px
@ -129,3 +138,17 @@ pre#log
&.active .status &.active .status
background-color: #6b0 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 // float: left
margin-top: 0 margin-top: 0
color: #999 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