diff --git a/Assetfile b/Assetfile index 5e4925bb..edb50985 100644 --- a/Assetfile +++ b/Assetfile @@ -69,7 +69,7 @@ end output 'public/styles' input assets.styles do - match '**/*.{scss,sass}' do + match '**/*.{scss,sass,css}' do sass assets.production? ? { style: :compressed } : {} concat [], ['app.css'] end diff --git a/assets/scripts/app/app.coffee b/assets/scripts/app/app.coffee index 87fe5a2a..f866d741 100644 --- a/assets/scripts/app/app.coffee +++ b/assets/scripts/app/app.coffee @@ -55,7 +55,7 @@ unless window.TravisApplication type = Ember.get(Travis, 'mappings')[name] - if name == 'build' && data.build.commit + if name == 'build' && data.build?.commit # TODO: commit should be a sideload record on build, not mixed with it build = data.build commit = { diff --git a/assets/scripts/app/controllers.coffee b/assets/scripts/app/controllers.coffee index 45ae67d1..ad07c187 100644 --- a/assets/scripts/app/controllers.coffee +++ b/assets/scripts/app/controllers.coffee @@ -56,6 +56,15 @@ Travis.RepoSettingsController = Em.ObjectController.extend tab: Ember.computed.alias('controllers.repoSettingsTab.model.tab') settings: Ember.computed.alias('model.settings') + settingsChanged: (-> + if parseInt(@get('settings.maximum_number_of_builds')) > 0 + @set('settings.maximum_number_of_builds_valid', '') + @get('model').saveSettings(@get('settings')).then null, -> + Travis.flash(error: 'There was an error while saving settings. Please try again.') + else + @set('settings.maximum_number_of_builds_valid', 'invalid') + ).observes('settings.maximum_number_of_builds') + save: -> @get('model').saveSettings(@get('settings')).then null, -> Travis.flash(error: 'There was an error while saving settings. Please try again.') diff --git a/assets/scripts/app/models/build.coffee b/assets/scripts/app/models/build.coffee index 21ba175e..974d5a9a 100644 --- a/assets/scripts/app/models/build.coffee +++ b/assets/scripts/app/models/build.coffee @@ -86,7 +86,7 @@ require 'travis/model' Travis.ajax.post "/builds/#{@get('id')}/restart" isPropertyLoaded: (key) -> - if ['_duration', 'finishedAt'].contains(key) && !@get('isFinished') + if ['_duration', '_finishedAt'].contains(key) && !@get('isFinished') return true else @_super(key) diff --git a/assets/scripts/app/models/job.coffee b/assets/scripts/app/models/job.coffee index 4a90d3b0..23649a37 100644 --- a/assets/scripts/app/models/job.coffee +++ b/assets/scripts/app/models/job.coffee @@ -107,9 +107,9 @@ require 'travis/model' ).observes('state') isPropertyLoaded: (key) -> - if ['finishedAt'].contains(key) && !@get('isFinished') + if ['_finishedAt'].contains(key) && !@get('isFinished') return true - else if key == 'startedAt' && @get('state') == 'created' + else if key == '_startedAt' && @get('state') == 'created' return true else @_super(key) diff --git a/assets/scripts/app/templates/builds/show.hbs b/assets/scripts/app/templates/builds/show.hbs index e39259e4..eadc1042 100644 --- a/assets/scripts/app/templates/builds/show.hbs +++ b/assets/scripts/app/templates/builds/show.hbs @@ -8,14 +8,14 @@
- {{#if build.isFinished}}ran{{else}}running{{/if}} for {{formatDuration build.duration}} + {{#if build.isFinished}}ran{{else}}running{{/if}} for {{formatDuration build.duration}}
{{formatTime build.finishedAt}}
- +
{{build.commit.branch}}
diff --git a/assets/scripts/app/templates/components/travis-switch.hbs b/assets/scripts/app/templates/components/travis-switch.hbs index 18617602..dbed063c 100644 --- a/assets/scripts/app/templates/components/travis-switch.hbs +++ b/assets/scripts/app/templates/components/travis-switch.hbs @@ -1,5 +1,6 @@ -{{#if _active}} + ON -{{else}} + + OFF -{{/if}} + diff --git a/assets/scripts/app/templates/repo/settings.hbs b/assets/scripts/app/templates/repo/settings.hbs index 4c882bf9..3143ec48 100644 --- a/assets/scripts/app/templates/repo/settings.hbs +++ b/assets/scripts/app/templates/repo/settings.hbs @@ -18,6 +18,15 @@ Build pull requests {{travis-switch action="save" active=settings.build_pull_requests}}

+ +

+

+ {{input value=settings.maximum_number_of_builds size="4" pattern='/^[0-9]+$/'}} +

+ +

{{/settings-form}} {{/travis-tab}} {{/travis-tabs}} diff --git a/assets/scripts/spec/integration/my_repos_spec.coffee b/assets/scripts/spec/integration/my_repos_spec.coffee index 6011772b..b43c4a50 100644 --- a/assets/scripts/spec/integration/my_repos_spec.coffee +++ b/assets/scripts/spec/integration/my_repos_spec.coffee @@ -24,7 +24,7 @@ test "my repos is active by default when user is signed in", -> branch: 'master' compare: '0123456..4567890' finishedAt: '-' - duration: '1 min' + duration: '-' message: 'commit message 4' test "my repos is activated when user signs in", -> diff --git a/assets/scripts/spec/unit/build_spec.coffee b/assets/scripts/spec/unit/build_spec.coffee index 1cae1aed..c1b05fcc 100644 --- a/assets/scripts/spec/unit/build_spec.coffee +++ b/assets/scripts/spec/unit/build_spec.coffee @@ -7,7 +7,7 @@ module "Travis.Build", Travis.Job.resetData() test 'it does not load record on duration, finishedAt and result if job is not in finished state', -> - Travis.Build.load [{ id: 1, state: 'started' }] + Travis.Build.load [{ id: 1, state: 'started', started_at: null }] Ember.run -> record = Travis.Build.find 1 @@ -15,7 +15,7 @@ test 'it does not load record on duration, finishedAt and result if job is not i record.loadTheRest = -> ok(false, 'loadTheRest should not be called') - record.get('_duration') + record.get('duration') record.get('finishedAt') record.get('result') @@ -25,7 +25,7 @@ test 'it does not load record on duration, finishedAt and result if job is not i test 'it loads record on duration, finishedAt and result if job is in finished state', -> expect(1) - Travis.Build.load [{ id: 1, state: 'passed' }] + Travis.Build.load [{ id: 1, state: 'passed', started_at: null }] Ember.run -> record = Travis.Build.find 1 diff --git a/assets/scripts/spec/unit/job_spec.coffee b/assets/scripts/spec/unit/job_spec.coffee index 6ade37d3..fdf12984 100644 --- a/assets/scripts/spec/unit/job_spec.coffee +++ b/assets/scripts/spec/unit/job_spec.coffee @@ -37,7 +37,7 @@ test 'configKeys takes into account the keys of other jobs', -> deepEqual( configValues3, [ '1.9.3', undefined, undefined, 'OpenJDK' ] ) test 'it does not load record on duration, finishedAt and result if job is not in finished state', -> - Travis.Job.load [{ id: 1, state: 'started' }] + Travis.Job.load [{ id: 1, state: 'started', started_at: null }] Ember.run -> record = Travis.Job.find 1 @@ -55,7 +55,7 @@ test 'it does not load record on duration, finishedAt and result if job is not i test 'it loads record on duration, finishedAt and result if job is in finished state', -> expect(1) - Travis.Job.load [{ id: 1, state: 'passed' }] + Travis.Job.load [{ id: 1, state: 'passed', started_at: null }] Ember.run -> record = Travis.Job.find 1 diff --git a/assets/scripts/vendor/jquery.tipsy.js b/assets/scripts/vendor/jquery.tipsy.js new file mode 100644 index 00000000..db5db475 --- /dev/null +++ b/assets/scripts/vendor/jquery.tipsy.js @@ -0,0 +1,258 @@ +// tipsy, facebook style tooltips for jquery +// version 1.0.0a +// (c) 2008-2010 jason frame [jason@onehackoranother.com] +// released under the MIT license + +(function($) { + + function maybeCall(thing, ctx) { + return (typeof thing == 'function') ? (thing.call(ctx)) : thing; + }; + + function isElementInDOM(ele) { + while (ele = ele.parentNode) { + if (ele == document) return true; + } + return false; + }; + + function Tipsy(element, options) { + this.$element = $(element); + this.options = options; + this.enabled = true; + this.fixTitle(); + }; + + Tipsy.prototype = { + show: function() { + var title = this.getTitle(); + if (title && this.enabled) { + var $tip = this.tip(); + + $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title); + $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity + $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body); + + var pos = $.extend({}, this.$element.offset(), { + width: this.$element[0].offsetWidth, + height: this.$element[0].offsetHeight + }); + + var actualWidth = $tip[0].offsetWidth, + actualHeight = $tip[0].offsetHeight, + gravity = maybeCall(this.options.gravity, this.$element[0]); + + var tp; + switch (gravity.charAt(0)) { + case 'n': + tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; + break; + case 's': + tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; + break; + case 'e': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}; + break; + case 'w': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}; + break; + } + + if (gravity.length == 2) { + if (gravity.charAt(1) == 'w') { + tp.left = pos.left + pos.width / 2 - 15; + } else { + tp.left = pos.left + pos.width / 2 - actualWidth + 15; + } + } + + $tip.css(tp).addClass('tipsy-' + gravity); + $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0); + if (this.options.className) { + $tip.addClass(maybeCall(this.options.className, this.$element[0])); + } + + if (this.options.fade) { + $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity}); + } else { + $tip.css({visibility: 'visible', opacity: this.options.opacity}); + } + } + }, + + hide: function() { + if (this.options.fade) { + this.tip().stop().fadeOut(function() { $(this).remove(); }); + } else { + this.tip().remove(); + } + }, + + fixTitle: function() { + var $e = this.$element; + if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') { + $e.attr('original-title', $e.attr('title') || '').removeAttr('title'); + } + }, + + getTitle: function() { + var title, $e = this.$element, o = this.options; + this.fixTitle(); + var title, o = this.options; + if (typeof o.title == 'string') { + title = $e.attr(o.title == 'title' ? 'original-title' : o.title); + } else if (typeof o.title == 'function') { + title = o.title.call($e[0]); + } + title = ('' + title).replace(/(^\s*|\s*$)/, ""); + return title || o.fallback; + }, + + tip: function() { + if (!this.$tip) { + this.$tip = $('
').html('
'); + this.$tip.data('tipsy-pointee', this.$element[0]); + } + return this.$tip; + }, + + validate: function() { + if (!this.$element[0].parentNode) { + this.hide(); + this.$element = null; + this.options = null; + } + }, + + enable: function() { this.enabled = true; }, + disable: function() { this.enabled = false; }, + toggleEnabled: function() { this.enabled = !this.enabled; } + }; + + $.fn.tipsy = function(options) { + + if (options === true) { + return this.data('tipsy'); + } else if (typeof options == 'string') { + var tipsy = this.data('tipsy'); + if (tipsy) tipsy[options](); + return this; + } + + options = $.extend({}, $.fn.tipsy.defaults, options); + + function get(ele) { + var tipsy = $.data(ele, 'tipsy'); + if (!tipsy) { + tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options)); + $.data(ele, 'tipsy', tipsy); + } + return tipsy; + } + + function enter() { + var tipsy = get(this); + tipsy.hoverState = 'in'; + if (options.delayIn == 0) { + tipsy.show(); + } else { + tipsy.fixTitle(); + setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn); + } + }; + + function leave() { + var tipsy = get(this); + tipsy.hoverState = 'out'; + if (options.delayOut == 0) { + tipsy.hide(); + } else { + setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut); + } + }; + + if (!options.live) this.each(function() { get(this); }); + + if (options.trigger != 'manual') { + var binder = options.live ? 'live' : 'bind', + eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus', + eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'; + this[binder](eventIn, enter)[binder](eventOut, leave); + } + + return this; + + }; + + $.fn.tipsy.defaults = { + className: null, + delayIn: 0, + delayOut: 0, + fade: false, + fallback: '', + gravity: 'n', + html: false, + live: false, + offset: 0, + opacity: 0.8, + title: 'title', + trigger: 'hover' + }; + + $.fn.tipsy.revalidate = function() { + $('.tipsy').each(function() { + var pointee = $.data(this, 'tipsy-pointee'); + if (!pointee || !isElementInDOM(pointee)) { + $(this).remove(); + } + }); + }; + + // Overwrite this method to provide options on a per-element basis. + // For example, you could store the gravity in a 'tipsy-gravity' attribute: + // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' }); + // (remember - do not modify 'options' in place!) + $.fn.tipsy.elementOptions = function(ele, options) { + return $.metadata ? $.extend({}, options, $(ele).metadata()) : options; + }; + + $.fn.tipsy.autoNS = function() { + return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n'; + }; + + $.fn.tipsy.autoWE = function() { + return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w'; + }; + + /** + * yields a closure of the supplied parameters, producing a function that takes + * no arguments and is suitable for use as an autogravity function like so: + * + * @param margin (int) - distance from the viewable region edge that an + * element should be before setting its tooltip's gravity to be away + * from that edge. + * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer + * if there are no viewable region edges effecting the tooltip's + * gravity. It will try to vary from this minimally, for example, + * if 'sw' is preferred and an element is near the right viewable + * region edge, but not the top edge, it will set the gravity for + * that element's tooltip to be 'se', preserving the southern + * component. + */ + $.fn.tipsy.autoBounds = function(margin, prefer) { + return function() { + var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)}, + boundTop = $(document).scrollTop() + margin, + boundLeft = $(document).scrollLeft() + margin, + $this = $(this); + + if ($this.offset().top < boundTop) dir.ns = 'n'; + if ($this.offset().left < boundLeft) dir.ew = 'w'; + if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e'; + if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's'; + + return dir.ns + (dir.ew ? dir.ew : ''); + } + }; + +})(jQuery); diff --git a/assets/styles/components/travis-switch.sass b/assets/styles/components/travis-switch.sass index 65b3a753..ccf1f16a 100644 --- a/assets/styles/components/travis-switch.sass +++ b/assets/styles/components/travis-switch.sass @@ -1,6 +1,12 @@ .settings-row margin-top: 20px +a.travis-switch + margin: 0 10px 0 0 + +p.settings-row + line-height: 30px + .travis-switch position: relative display: inline-block @@ -9,7 +15,7 @@ height: 18px margin: 20px 0 50px 0 padding: 5px 18px 5px 18px - background-color: #607A83 + background-color: #F5F5F5 border: 1px solid #E3E1E1 border-radius: 4px line-height: 19px @@ -20,23 +26,35 @@ &:before content: "" position: absolute - top: -1px - left: -1px - width: 28px - height: 18px + top: 1px + left: 1px + width: 42px + height: 24px background: #e9e9e7 - border: 1px solid #bdbdbd + border: 1px solid #d7d4d4 + border-radius: 2px .travis-switch.active - width: 56px + width: 60px padding: 5px 18px 5px 18px - background: #f5f5f5 - border: 1px solid #bdbdbd - border-radius: 3px + background-color: #607A83 + border-radius: 4px color: #ffffff font-weight: 600 text-align: left &:before left: auto - right: -1px - width: 30px \ No newline at end of file + right: 1px + width: 42px + height: 24px + border: 1px solid #9cafb5 + +span.on + background-color: #607A83 + color: #fff + font-weight: 800 + border-radius: 3px + float: left + +span.off + float: right diff --git a/assets/styles/settings.sass b/assets/styles/settings.sass new file mode 100644 index 00000000..331af6c9 --- /dev/null +++ b/assets/styles/settings.sass @@ -0,0 +1,34 @@ +form + margin: 20px + display: inline-block + + p.short-settings-element + display: inline-block + vertical-align: middle + margin: 0 10px 0 0 + float: left + + label + line-height: 30px + + input + display: inline-block + float: left + width: 60px + height: 18px + padding: 5px 18px 5px 18px + background-color: #F5F5F5 + border: 1px solid #E3E1E1 + border-radius: 4px + line-height: 19px + font-size: 11px + color: #999999 + text-align: center + + .invalid + input + border-width: 1px + border-color: #ffb6c1 + background: rgb(252, 227, 230) + + diff --git a/assets/styles/tipsy.css b/assets/styles/tipsy.css new file mode 100644 index 00000000..806bc5da --- /dev/null +++ b/assets/styles/tipsy.css @@ -0,0 +1,25 @@ +.tipsy { font-size: 10px; position: absolute; padding: 5px; z-index: 100000; } + .tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; } + + /* Rounded corners */ + .tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; } + + /* Uncomment for shadow */ + /*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/ + + .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; } + + /* Rules to colour arrows */ + .tipsy-arrow-n { border-bottom-color: #000; } + .tipsy-arrow-s { border-top-color: #000; } + .tipsy-arrow-e { border-left-color: #000; } + .tipsy-arrow-w { border-right-color: #000; } + + .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; } + .tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} + .tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} + .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } + .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } + .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } + .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; } + .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }