Merge remote-tracking branch 'origin/settings-pane'

Conflicts:
	assets/scripts/app/controllers.coffee
	assets/scripts/app/routes.coffee
	assets/scripts/app/views/repo/show.coffee
This commit is contained in:
Piotr Sarnacki 2014-02-12 11:51:05 +01:00
commit 8d19410264
21 changed files with 461 additions and 20 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View File

@ -71,7 +71,7 @@
try
router.send('afterSignIn')
catch e
throw e unless e =~ /There are no active handlers/
throw e unless e =~ /There are no active handlers/ || e =~ /Can't trigger action "afterSignIn/
@refreshUserData(data.user)
refreshUserData: (user) ->

View File

@ -1,9 +1,19 @@
Travis.TravisSwitchComponent = Ember.Component.extend
tagName: 'a'
classNames: ['travis-switch']
classNameBindings: ['active']
classNameBindings: ['_active:active']
activeBinding: 'target.active'
# TODO: how to handle overriding properties to
# avoid naming it _action?
_active: (->
@get('target.active') || @get('active')
).property('target.active', 'active')
click: ->
@sendAction('action', @get('target'))
if target = @get('target')
@set('target.active', !@get('target.active'))
else
@set('active', !@get('active'))
# allow for bindings to propagate
Ember.run.next this, ->
@sendAction('action', target)

View File

@ -50,6 +50,22 @@ Travis.FirstSyncController = Em.Controller.extend
Travis.IndexErrorController = Em.Controller.extend()
Travis.RepoSettingsTabController = Em.ObjectController.extend()
Travis.RepoSettingsController = Em.ObjectController.extend
needs: ['repoSettingsTab']
tab: Ember.computed.alias('controllers.repoSettingsTab.model.tab')
settings: Ember.computed.alias('model.settings')
save: ->
@set('saving', true)
self = this
@get('model').saveSettings(@get('settings')).then ->
self.set('saving', false)
Travis.flash(success: 'Settings were saved successfully')
, ->
self.set('saving', false)
Travis.flash(error: 'There was an error while saving settings. Please try again.')
require 'controllers/accounts'
require 'controllers/build'
require 'controllers/builds'

View File

@ -3,6 +3,293 @@ require 'ext/ember/bound_helper'
safe = (string) ->
new Handlebars.SafeString(string)
Travis.Tab = Ember.Object.extend
show: ->
@get('tabs').forEach( (t) -> t.hide() )
@set('visible', true)
hide: ->
@set('visible', false)
Travis.TabsView = Ember.View.extend
tabBinding: 'controller.tab'
tabsBinding: 'controller.tabs'
tabDidChange: (->
@activateTab(@get('tab'))
).observes('tab')
tabsDidChange: (->
tab = @get('tab')
if tab
@activateTab(tab)
else if @get('tabs.length')
@activateTab(@get('tabs.firstObject.id'))
).observes('tabs.length', 'tabs')
activateTab: (tabId) ->
tab = @get('tabs').findBy('id', tabId)
return unless tab
tab.show() unless tab.get('visible')
# TODO: remove hardcoded link
layout: Ember.Handlebars.compile(
'<ul class="tabs">' +
' {{#each tab in tabs}}' +
' <li {{bindAttr class="tab.visible:active"}}>' +
' <h5>{{#linkTo "repo.settings.tab" tab.id}}{{tab.name}}{{/linkTo}}</h5>' +
' </li>' +
' {{/each}}' +
'</ul>' +
'{{yield}}')
Travis.TabView = Ember.View.extend
attributeBindings: ['style']
style: (->
if !@get('tab.visible')
'display: none'
).property('tab.visible')
Ember.Handlebars.registerHelper('travis-tab', (id, name, options) ->
controller = this
controller.set('tabs', []) unless controller.get('tabs')
tab = Travis.Tab.create(id: id, name: name, tabs: controller.get('tabs'))
view = Travis.TabView.create(
controller: this
tab: tab
)
controller = this
Ember.run.schedule('afterRender', ->
if controller.get('tabs.length') == 0
tab.show()
controller.get('tabs').pushObject(tab)
)
Ember.Handlebars.helpers.view.call(this, view, options)
)
Ember.Handlebars.registerHelper('travis-tabs', (options) ->
template = options.fn
delete options.fn
@set('tabs', [])
view = Travis.TabsView.create(
controller: this
template: template
)
Ember.Handlebars.helpers.view.call(this, view, options)
)
Travis.SettingsMultiplierView = Ember.CollectionView.extend()
createObjects = (path, offset) ->
segments = path.split('.')
if segments.length > offset
for i in [1..(segments.length - offset)]
path = segments.slice(0, i).join('.')
if Ember.isNone(Ember.get(this, path))
Ember.set(this, path, {})
return segments
Ember.Handlebars.registerHelper('settings-multiplier', (path, options) ->
template = options.fn
delete options.fn
parentsPath = getSettingsPath(options.data.view)
if parentsPath && parentsPath != ''
path = parentsPath + '.' + path
createObjects.call(this, path, 1)
if Ember.isNone(@get(path))
collection = [{}]
@set(path, collection)
itemViewClass = Ember.View.extend(
template: template,
controller: this,
tagName: 'li',
multiplier: true
)
view = Travis.SettingsMultiplierView.create(
contentBinding: 'controller.' + path
controller: this
tagName: 'ul'
itemViewClass: itemViewClass
fields: []
settingsPath: path
)
view.addObserver('content.length', ->
if @get('content.length') == 0
@get('content').pushObject({})
)
Ember.Handlebars.helpers.view.call(this, view, options)
)
Travis.FormSettingsView = Ember.View.extend Ember.TargetActionSupport,
target: Ember.computed.alias('controller')
actionContext: Ember.computed.alias('context'),
action: 'submit'
tagName: 'form'
submit: (event) ->
event.preventDefault()
@triggerAction()
Ember.Handlebars.registerHelper('settings-form', (path, options) ->
if arguments.length == 1
options = path
path = 'settings'
view = Travis.FormSettingsView.create(
template: options.fn
controller: this
settingsPath: path
)
delete options.fn
Ember.Handlebars.helpers.view.call(this, view, options)
)
Ember.Handlebars.helper('settings-select', (options) ->
view = options.data.view
optionValues = options.hash.options
delete options.hash.options
originalPath = options.hash.value
parentsPath = getSettingsPath(view)
#TODO: such checks should also check parents, not only current context view
if !view.get('multiplier') && parentsPath && parentsPath != ''
originalPath = parentsPath + '.' + originalPath
fullPath = originalPath
if view.get('multiplier')
fullPath = 'view.content.' + fullPath
createObjects.call(this, fullPath, 1)
# TODO: setting a value here does not work and we still need
# a valueBinding in the view, I'm not sure why
options.hash.value = fullPath
selectView = Ember.Select.create(
content: [''].pushObjects(optionValues.split(','))
controller: this
valueBinding: 'controller.' + fullPath
)
Ember.Handlebars.helpers.view.call(this, selectView, options)
)
Ember.Handlebars.helper('settings-remove-link', (options) ->
view = Ember.View.extend(
tagName: 'a'
attributeBindings: ['href', 'style']
href: '#'
style: (->
# TODO: do not assume that we're direct child
if @get('parentView.parentView.content.length') == 1
'display: none'
).property('parentView.parentView.content.length')
template: Ember.Handlebars.compile('remove')
controller: this
click: (event) ->
event.preventDefault()
if content = @get('parentView.content')
@get('parentView.parentView.content').removeObject(content)
).create()
Ember.Handlebars.helpers.view.call(this, view, options)
)
Ember.Handlebars.registerHelper('settings-add-link', (path, options) ->
parentsPath = getSettingsPath(options.data.view)
if parentsPath && parentsPath != ''
path = parentsPath + '.' + path
view = Ember.View.create(
tagName: 'a'
attributeBindings: ['href']
href: '#'
template: options.fn
controller: this
click: (event) ->
event.preventDefault()
if collection = @get('controller.' + path)
collection.pushObject({})
)
Ember.Handlebars.helpers.view.call(this, view, options)
)
getSettingsPath = (view) ->
settingsPaths = []
if settingsPath = view.get('settingsPath')
settingsPaths.pushObject settingsPath
parent = view
while parent = parent.get('parentView')
if settingsPath = parent.get('settingsPath')
settingsPaths.pushObject settingsPath
return settingsPaths.reverse().join('.')
Ember.Handlebars.helper('settings-input', (options) ->
view = options.data.view
if options.hash.type == 'checkbox'
originalPath = options.hash.checked
else
originalPath = options.hash.value
parentsPath = getSettingsPath(view)
#TODO: such checks should also check parents, not only current context view
if !view.get('multiplier') && parentsPath && parentsPath != ''
originalPath = parentsPath + '.' + originalPath
fullPath = originalPath
if view.get('multiplier')
fullPath = 'view.content.' + fullPath
if options.hash.type != 'password'
createObjects.call(this, fullPath, 1)
else
createObjects.call(view, fullPath, 2)
content = view.get('content')
fullPath += ".value"
if Ember.isNone(Ember.get(content, originalPath))
Ember.set(content, originalPath, {})
Ember.set(content, originalPath + ".type", 'password')
if options.hash.type == 'checkbox'
options.hash.checked = fullPath
else
options.hash.value = fullPath
Ember.Handlebars.helpers.input.call(this, options)
)
Handlebars.registerHelper 'tipsy', (text, tip) ->
safe '<span class="tool-tip" original-title="' + tip + '">' + text + '</span>'

View File

@ -27,3 +27,13 @@ require 'travis/model'
return if @get('isSaving')
@set 'active', !@get('active')
@save()
repo: (->
# I don't want to make an ajax request for each repository showed in profile,
# especially, because most of them does not have any builds anyway. That's why
# I add an info which we have here to the store - this will allow to display
# a link to the repo and if more info is needed, it will be requested when the
# link is used
Travis.loadOrMerge(Travis.Repo, @getProperties('id', 'slug', 'name', 'ownerName'), skipIfExists: true)
Travis.Repo.find(@get('id'))
).property('id')

View File

@ -105,6 +105,13 @@ require 'travis/model'
regenerateKey: (options) ->
Travis.ajax.ajax '/repos/' + @get('id') + '/key', 'post', options
fetchSettings: ->
Travis.ajax.ajax('/repos/' + @get('id') + '/settings', 'get', forceAuth: true).then (data) ->
data['settings']
saveSettings: (settings) ->
Travis.ajax.ajax('/repos/' + @get('id') + '/settings', 'patch', data: { settings: settings })
@Travis.Repo.reopenClass
recent: ->
@find()

View File

@ -71,6 +71,12 @@ Travis.ApplicationRoute = Travis.Route.extend
afterSignOut: ->
@afterSignOut()
Travis.Router.reopen
transitionTo: ->
this.container.lookup('controller:repo').set('lineNumber', null)
@_super.apply this, arguments
Travis.Router.map ->
@resource 'index', path: '/', ->
@route 'current', path: '/'
@ -82,6 +88,11 @@ Travis.Router.map ->
@resource 'pullRequests', path: '/pull_requests'
@resource 'branches', path: '/branches'
# this can't be nested in repo, because we want a set of different
# templates rendered for settings (for example no "current", "builds", ... tabs)
@resource 'repo.settings', path: '/:owner/:name/settings', ->
@route 'tab', path: ':tab'
@route 'getting_started'
@route 'first_sync'
@route 'stats', path: '/stats'
@ -105,7 +116,7 @@ Travis.SetupLastBuild = Ember.Mixin.create
repo = @controllerFor('repo').get('repo')
if repo && repo.get('isLoaded') && !repo.get('lastBuildId')
Ember.run.next =>
@render('builds/not_found', outlet: 'pane', into: 'repo')
@render('builds/not_found', into: 'repo', outlet: 'pane')
Travis.GettingStartedRoute = Travis.Route.extend
setupController: ->
@ -138,7 +149,7 @@ Travis.FirstSyncRoute = Travis.Route.extend
Travis.IndexCurrentRoute = Travis.Route.extend Travis.SetupLastBuild,
renderTemplate: ->
@render 'repo'
@render 'build', outlet: 'pane', into: 'repo'
@render 'build', into: 'repo', outlet: 'pane'
setupController: ->
@_super.apply this, arguments
@ -155,7 +166,7 @@ Travis.IndexCurrentRoute = Travis.Route.extend Travis.SetupLastBuild,
Travis.AbstractBuildsRoute = Travis.Route.extend
renderTemplate: ->
@render 'builds', outlet: 'pane', into: 'repo'
@render 'builds', into: 'repo', outlet: 'pane'
setupController: ->
@controllerFor('repo').activate(@get('contentType'))
@ -180,7 +191,7 @@ Travis.BranchesRoute = Travis.AbstractBuildsRoute.extend(contentType: 'branches'
Travis.BuildRoute = Travis.Route.extend
renderTemplate: ->
@render 'build', outlet: 'pane', into: 'repo'
@render 'build', into: 'repo', outlet: 'pane'
serialize: (model, params) ->
id = if model.get then model.get('id') else model
@ -201,7 +212,7 @@ Travis.BuildRoute = Travis.Route.extend
Travis.JobRoute = Travis.Route.extend
renderTemplate: ->
@render 'job', outlet: 'pane', into: 'repo'
@render 'job', into: 'repo', outlet: 'pane'
serialize: (model, params) ->
id = if model.get then model.get('id') else model
@ -228,7 +239,7 @@ Travis.RepoIndexRoute = Travis.Route.extend Travis.SetupLastBuild,
@controllerFor('repo').activate('current')
renderTemplate: ->
@render 'build', outlet: 'pane', into: 'repo'
@render 'build', into: 'repo', outlet: 'pane'
Travis.RepoRoute = Travis.Route.extend
renderTemplate: ->
@ -247,7 +258,6 @@ Travis.RepoRoute = Travis.Route.extend
model: (params) ->
slug = "#{params.owner}/#{params.name}"
Travis.Repo.fetchBySlug(slug)
actions:
@ -306,7 +316,7 @@ Travis.ProfileRoute = Travis.Route.extend
@render 'top', outlet: 'top'
@render 'accounts', outlet: 'left'
@render 'flash', outlet: 'flash'
@render 'profile'
@_super.apply(this, arguments)
Travis.ProfileIndexRoute = Travis.Route.extend
setupController: ->
@ -374,3 +384,25 @@ Travis.AuthRoute = Travis.Route.extend
deactivate: ->
@controllerFor('auth').set('redirected', false)
Travis.RepoSettingsRoute = Travis.Route.extend
setupController: (controller, model) ->
# TODO: if repo is just a data hash with id and slug load it
# as incomplete record
model = Travis.Repo.find(model.id) if model && !model.get
@_super(controller, model)
serialize: (repo) ->
slug = if repo.get then repo.get('slug') else repo.slug
[owner, name] = slug.split('/')
{ owner: owner, name: name }
model: (params) ->
slug = "#{params.owner}/#{params.name}"
Travis.Repo.fetchBySlug(slug)
afterModel: (repo) ->
# I'm using afterModel to fetch settings, because model is not always called.
# If link-to already provides a model, it will be just set as a route context.
repo.fetchSettings().then (settings) ->
repo.set('settings', settings)

View File

@ -1,4 +1,4 @@
{{#if active}}
{{#if _active}}
ON
{{else}}
OFF

View File

@ -0,0 +1,3 @@
<h1>Settings: {{slug}}</h1>
{{outlet}}

View File

@ -23,7 +23,7 @@
<p class="description">{{hook.description}}</p>
<div class="controls">
<a {{bind-attr href="hook.urlGithubAdmin"}} class="github-admin tool-tip" title="Github service hooks admin page"></a>
{{#link-to "repo.settings" hook.repo class="repo-settings-icon tool-tip" title="Repository settings"}}{{/link-to}}
{{travis-switch action="toggle" target=hook}}
</div>
</li>

View File

@ -0,0 +1,24 @@
<div id="repo">
<h3>Settings: {{#linkTo "repo" this}}{{slug}}{{/linkTo}}</h3>
{{#travis-tabs}}
{{#travis-tab "general" "General Settings"}}
{{#settings-form}}
<p class="settings-row">
Build only commits with .travis.yml file
{{travis-switch action="save" active=settings.builds_only_with_travis_yml}}
</p>
<p class="settings-row">
Build pushes
{{travis-switch action="save" active=settings.build_pushes}}
</p>
<p class="settings-row">
Build pull requests
{{travis-switch action="save" active=settings.build_pull_requests}}
</p>
{{/settings-form}}
{{/travis-tab}}
{{/travis-tabs}}
</div>

View File

@ -24,4 +24,3 @@
{{/if}}
{{/if}}
</div>

View File

@ -11,6 +11,12 @@
</a>
</li>
{{/if}}
{{#if view.displaySettingsLink}}
<li>
{{#linkTo "repo.settings" view.repo}}Settings{{/linkTo}}
</li>
{{/if}}
</ul>
<a href="#" id="status-image-popup" name="status-images" class="open-popup" {{action "statusImages" target="view"}}>
<img {{bind-attr src="view.statusImageUrl"}} title="Build Status Images"/>

View File

@ -45,6 +45,7 @@ Travis.FirstSyncView = Travis.View.extend
)
, Travis.config.syncingPageRedirectionTime
require 'views/accounts'
require 'views/annotation'
require 'views/application'

View File

@ -115,6 +115,11 @@ Travis.reopen
permissions.contains parseInt(@get('repo.id'))
).property('currentUser.permissions.length', 'repo.id')
hasPushPermission: (->
if permissions = @get('currentUser.pushPermissions')
permissions.contains parseInt(@get('repo.id'))
).property('currentUser.pushPermissions.length', 'repo.id')
hasAdminPermission: (->
if permissions = @get('currentUser.adminPermissions')
permissions.contains parseInt(@get('repo.id'))
@ -124,6 +129,14 @@ Travis.reopen
Travis.Urls.statusImage(@get('slug'))
).property('slug')
displaySettingsLink: (->
@get('hasPushPermission')
).property('hasPushPermission')
displayStatusImages: (->
@get('hasPermission')
).property('hasPermission')
statusImages: ->
@popupCloseAll()
view = Travis.StatusImagesView.create(toolsView: this)

View File

@ -23,13 +23,14 @@ Travis.ajax = Em.Object.create
!result
ajax: (url, method, options) ->
method = method || "GET"
method = method.toUpperCase()
endpoint = Travis.config.api_endpoint || ''
options = options || {}
token = Travis.sessionStorage.getItem('travis.token')
if token && Travis.ajax.needsAuth(method, url)
if token && (Travis.ajax.needsAuth(method, url) || options.forceAuth)
options.headers ||= {}
options.headers['Authorization'] ||= "token #{token}"

View File

@ -0,0 +1,28 @@
view = null
module "Handlebars helpers - settings-input",
setup: ->
Ember.run -> Travis.advanceReadiness()
test "settings input allows to bind to nested objects", ->
controller = Ember.Object.create()
view = Ember.View.create(
controller: controller
template: Ember.Handlebars.compile("{{settings-input value=foo.bar.baz}}")
)
Ember.run ->
view.appendTo($("#ember-testing")[0])
input = view.$('input')
Ember.run ->
input.val('a value').change()
equal(controller.get('foo.bar.baz'), 'a value')
Ember.run ->
controller.set('foo.bar.baz', 'a new value')
equal(input.val(), 'a new value')

View File

@ -11,7 +11,7 @@
.loading
display: none
span.loading
span.loading, span.saving
padding: 0 25px 0 0
font-size: $font-size-small
color: $color-text-lighter

View File

@ -1,6 +1,10 @@
.settings-row
margin-top: 20px
.travis-switch
position: relative
display: block
display: inline-block
float: left
width: 60px
height: 18px
margin: 0 10px 0 15px

View File

@ -57,14 +57,14 @@
float: left
display: block
.github-admin
.repo-settings-icon
// Overriding an earlier definition above, which is probably
// obsolete. TODO: Remove if so.
position: relative
height: 20px
width: 20px
padding-right: 0
background: inline-image('ui/github-admin.png') no-repeat 3px 4px
background: inline-image('ui/repo-settings.png') no-repeat 3px 4px
&:hover
> a