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
this # 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: 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,44 +88,67 @@ pre#log
#log-container #log-container
position: relative position: relative
#log-container #tail #log-container
z-index: 99 #tail
position: absolute z-index: 99
display: block position: absolute
top: 0 display: block
right: 2px top: 0
margin: 13px 10px 0 0 right: 2px
padding: 0 2px 0 3px margin: 13px 10px 0 0
color: #666 padding: 0 2px 0 3px
text-shadow: 0px 1px 0px #fff color: #666
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif text-shadow: 0px 1px 0px #fff
font-size: $font-size-tiny font-family: "Helvetica Neue", Helvetica, Arial, sans-serif
line-height: 14px font-size: $font-size-tiny
text-decoration: none line-height: 14px
white-space: nowrap text-decoration: none
border: 1px solid #bbb white-space: nowrap
border-top-color: #ddd border: 1px solid #bbb
border-bottom-color: #bbb border-top-color: #ddd
@include border-radius(8px) border-bottom-color: #bbb
@include background(linear-gradient(#fff, #e0e0e0)) @include border-radius(8px)
@include background(linear-gradient(#fff, #e0e0e0))
label
display: none
cursor: pointer
&:hover
padding: 1px 4px 1px 6px
label label
display: inline display: none
cursor: pointer
.status &:hover
display: inline-block padding: 1px 4px 1px 6px
margin-right: 1px label
width: 8px display: inline
height: 8px
background-color: #aaa
@include border-radius(4px)
@include box-shadow(white 1px 1px 2px)
&.active .status &.scrolling
background-color: #6b0 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

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