diff --git a/assets/scripts/app/auth.coffee b/assets/scripts/app/auth.coffee index 565a18c1..b8116df7 100644 --- a/assets/scripts/app/auth.coffee +++ b/assets/scripts/app/auth.coffee @@ -74,7 +74,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) -> diff --git a/assets/scripts/app/controllers.coffee b/assets/scripts/app/controllers.coffee index f1b4f520..bcec80fd 100644 --- a/assets/scripts/app/controllers.coffee +++ b/assets/scripts/app/controllers.coffee @@ -48,6 +48,24 @@ Travis.FirstSyncController = Em.Controller.extend isSyncing: Ember.computed.alias('user.isSyncing') +Travis.ProfileRepoController = Em.ObjectController.extend() +Travis.ProfileRepoSettingsTabController = Em.ObjectController.extend() + +Travis.ProfileRepoSettingsController = Em.Controller.extend + needs: ['profileRepoSettingsTab', 'profileRepo'] + tab: Ember.computed.alias('controllers.profileRepoSettingsTab.model.tab') + repo: Ember.computed.alias('controllers.profileRepo.content') + + submit: -> + @set('saving', true) + self = this + @get('repo').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' diff --git a/assets/scripts/app/helpers/handlebars.coffee b/assets/scripts/app/helpers/handlebars.coffee index 8dfda033..63e6cc15 100644 --- a/assets/scripts/app/helpers/handlebars.coffee +++ b/assets/scripts/app/helpers/handlebars.coffee @@ -3,6 +3,292 @@ 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') + + layout: Ember.Handlebars.compile( + '' + + '{{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 '' + text + '' diff --git a/assets/scripts/app/models/repo.coffee b/assets/scripts/app/models/repo.coffee index f582fb10..ca098348 100644 --- a/assets/scripts/app/models/repo.coffee +++ b/assets/scripts/app/models/repo.coffee @@ -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() diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee index 0f8186c1..ba390fb9 100644 --- a/assets/scripts/app/routes.coffee +++ b/assets/scripts/app/routes.coffee @@ -27,10 +27,10 @@ Ember.Route.reopen authController.set('redirected', true) @transitionTo('auth') else - throw(error) + @_super(error) renderNoOwnedRepos: -> - @render('no_owned_repos', outlet: 'main') + @render('no_owned_repos') renderFirstSync: -> @renderFirstSync() @@ -41,6 +41,7 @@ Ember.Route.reopen afterSignOut: -> @afterSignOut() + afterSignIn: -> if transition = Travis.auth.get('afterSignInTransition') Travis.auth.set('afterSignInTransition', null) @@ -86,6 +87,7 @@ Travis.Router.reopen @_super.apply this, arguments + Travis.Router.map -> @resource 'index', path: '/', -> @route 'current', path: '/' @@ -103,6 +105,10 @@ Travis.Router.map -> @route 'auth', path: '/auth' @route 'notFound', path: '/not-found' + @resource 'profile.repo', path: '/profile/:owner/:name', -> + @resource 'profile.repo.settings', path: 'settings', -> + @route 'tab', path: ':tab' + @resource 'profile', path: '/profile', -> @route 'index', path: '/' @resource 'account', path: '/:login', -> @@ -125,7 +131,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') Travis.GettingStartedRoute = Ember.Route.extend setupController: -> @@ -158,7 +164,7 @@ Travis.FirstSyncRoute = Ember.Route.extend Travis.IndexCurrentRoute = Ember.Route.extend Travis.SetupLastBuild, renderTemplate: -> @render 'repo' - @render 'build', outlet: 'pane', into: 'repo' + @render 'build', into: 'repo' setupController: -> @_super.apply this, arguments @@ -175,7 +181,7 @@ Travis.IndexCurrentRoute = Ember.Route.extend Travis.SetupLastBuild, Travis.AbstractBuildsRoute = Ember.Route.extend renderTemplate: -> - @render 'builds', outlet: 'pane', into: 'repo' + @render 'builds', into: 'repo' setupController: -> @controllerFor('repo').activate(@get('contentType')) @@ -200,7 +206,7 @@ Travis.BranchesRoute = Travis.AbstractBuildsRoute.extend(contentType: 'branches' Travis.BuildRoute = Ember.Route.extend renderTemplate: -> - @render 'build', outlet: 'pane', into: 'repo' + @render 'build', into: 'repo' serialize: (model, params) -> id = if model.get then model.get('id') else model @@ -221,7 +227,7 @@ Travis.BuildRoute = Ember.Route.extend Travis.JobRoute = Ember.Route.extend renderTemplate: -> - @render 'job', outlet: 'pane', into: 'repo' + @render 'job', into: 'repo' serialize: (model, params) -> id = if model.get then model.get('id') else model @@ -248,7 +254,7 @@ Travis.RepoIndexRoute = Ember.Route.extend Travis.SetupLastBuild, @controllerFor('repo').activate('current') renderTemplate: -> - @render 'build', outlet: 'pane', into: 'repo' + @render 'build', into: 'repo' Travis.RepoRoute = Ember.Route.extend renderTemplate: -> @@ -267,7 +273,6 @@ Travis.RepoRoute = Ember.Route.extend model: (params) -> slug = "#{params.owner}/#{params.name}" - Travis.Repo.fetchBySlug(slug) actions: @@ -324,7 +329,7 @@ Travis.ProfileRoute = Ember.Route.extend @render 'top', outlet: 'top' @render 'accounts', outlet: 'left' @render 'flash', outlet: 'flash' - @render 'profile' + @_super.apply(this, arguments) Travis.ProfileIndexRoute = Ember.Route.extend setupController: -> @@ -392,3 +397,29 @@ Travis.AuthRoute = Ember.Route.extend deactivate: -> @controllerFor('auth').set('redirected', false) + +Travis.ProfileRepoRoute = Travis.ProfileRoute.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) + + controller.set('content', 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) + +Travis.ProfileRepoSettingsRoute = Ember.Route.extend + setupController: (controller, model) -> + controller.set('settings', model) + + model: -> + repo = @modelFor('profileRepo') + repo.fetchSettings() diff --git a/assets/scripts/app/templates/layouts/profile.hbs b/assets/scripts/app/templates/layouts/profile.hbs index 6ce4e370..e149cc94 100644 --- a/assets/scripts/app/templates/layouts/profile.hbs +++ b/assets/scripts/app/templates/layouts/profile.hbs @@ -8,7 +8,7 @@
{{outlet flash}} - {{outlet main}} + {{outlet}}