From c9f58718cfb308ef31ffe79241e3be43cbfbcf6d Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 1 Aug 2014 02:50:20 +0200 Subject: [PATCH] Final touches on env vars --- assets/images/ui/bubbles-spinner.svg | 29 +++ assets/images/ui/round-spinner.svg | 26 +++ assets/scripts/app/controllers/env_var.coffee | 34 ++- .../app/controllers/env_var_new.coffee | 47 ++-- assets/scripts/app/controllers/ssh_key.coffee | 19 +- assets/scripts/app/helpers/handlebars.coffee | 37 +++ assets/scripts/app/routes.coffee | 2 +- assets/scripts/app/templates/env_vars.hbs | 51 ++--- .../scripts/app/templates/env_vars/_form.hbs | 22 ++ .../scripts/app/templates/env_vars/index.hbs | 1 + assets/scripts/app/templates/env_vars/new.hbs | 10 +- assets/scripts/app/templates/settings.hbs | 2 +- assets/scripts/app/templates/ssh_key.hbs | 2 +- .../lib/travis/expandable_record_array.coffee | 17 ++ assets/scripts/lib/travis/validations.coffee | 87 +++++++ assets/styles/settings.sass | 214 ++++++++++-------- 16 files changed, 446 insertions(+), 154 deletions(-) create mode 100644 assets/images/ui/bubbles-spinner.svg create mode 100644 assets/images/ui/round-spinner.svg create mode 100644 assets/scripts/app/templates/env_vars/_form.hbs create mode 100644 assets/scripts/app/templates/env_vars/index.hbs create mode 100644 assets/scripts/lib/travis/validations.coffee diff --git a/assets/images/ui/bubbles-spinner.svg b/assets/images/ui/bubbles-spinner.svg new file mode 100644 index 00000000..abea1057 --- /dev/null +++ b/assets/images/ui/bubbles-spinner.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/ui/round-spinner.svg b/assets/images/ui/round-spinner.svg new file mode 100644 index 00000000..d9501046 --- /dev/null +++ b/assets/images/ui/round-spinner.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/scripts/app/controllers/env_var.coffee b/assets/scripts/app/controllers/env_var.coffee index f2c55777..ee46f294 100644 --- a/assets/scripts/app/controllers/env_var.coffee +++ b/assets/scripts/app/controllers/env_var.coffee @@ -1,5 +1,14 @@ -Travis.EnvVarController = Ember.ObjectController.extend +require 'travis/validations' + +Travis.EnvVarController = Ember.ObjectController.extend Travis.Validations, isEditing: false + isDeleting: false + + validates: + name: ['presence'] + + actionType: 'Save' + showValueField: Ember.computed.alias('public') value: ( (key, value) -> if arguments.length == 2 @@ -13,7 +22,11 @@ Travis.EnvVarController = Ember.ObjectController.extend actions: delete: -> - @get('model').deleteRecord() + return if @get('isDeleting') + @set('isDeleting', true) + + deletingDone = => @set('isDeleting', false) + @get('model').deleteRecord().then deletingDone, deletingDone edit: -> @set('isEditing', true) @@ -23,8 +36,17 @@ Travis.EnvVarController = Ember.ObjectController.extend @get('model').revert() save: -> - env_var = @get('model') + return if @get('isSaving') + @set('isSaving', true) - # TODO: handle errors - env_var.save().then => - @set('isEditing', false) + if @isValid() + env_var = @get('model') + + # TODO: handle errors + env_var.save().then => + @set('isEditing', false) + @set('isSaving', false) + , => + @set('isSaving', false) + else + @set('isSaving', false) diff --git a/assets/scripts/app/controllers/env_var_new.coffee b/assets/scripts/app/controllers/env_var_new.coffee index b65c3543..2aac40e2 100644 --- a/assets/scripts/app/controllers/env_var_new.coffee +++ b/assets/scripts/app/controllers/env_var_new.coffee @@ -1,20 +1,41 @@ -Travis.EnvVarsNewController = Travis.Controller.extend - needs: ['repoSettings'] - repo: Ember.computed.alias('controllers.repoSettings.model') +require 'travis/validations' + +Travis.EnvVarsNewController = Travis.Controller.extend Travis.Validations, + needs: ['repo'] + repo: Ember.computed.alias('controllers.repo.repo') + + validates: + name: ['presence'] + + actionType: 'Add' + showValueField: true + + reset: -> + @setProperties(name: null, value: null, public: null) actions: cancel: -> + @reset() @transitionToRoute('env_vars') save: -> - console.log(@get('repo.id')) - env_var = Travis.EnvVar.create( - name: @get('name') - value: @get('value') - public: @get('public') - repo: @get('repo') - ) + return if @get('isSaving') + @set('isSaving', true) - self = this - env_var.save().then -> - self.transitionToRoute('env_vars') + if @isValid() + env_var = Travis.EnvVar.create( + name: @get('name') + value: @get('value') + public: @get('public') + repo: @get('repo') + ) + + self = this + env_var.save().then => + @set('isSaving', false) + @reset() + self.transitionToRoute('env_vars') + , => + @set('isSaving', false) + else + @set('isSaving', false) diff --git a/assets/scripts/app/controllers/ssh_key.coffee b/assets/scripts/app/controllers/ssh_key.coffee index 9dbcd473..110cc384 100644 --- a/assets/scripts/app/controllers/ssh_key.coffee +++ b/assets/scripts/app/controllers/ssh_key.coffee @@ -1,9 +1,14 @@ -Travis.SshKeyController = Ember.ObjectController.extend +require 'travis/validations' + +Travis.SshKeyController = Ember.ObjectController.extend Travis.Validations, isEditing: false defaultKey: null - needs: ['repoSettings'] - repo: Ember.computed.alias('controllers.repoSettings.model') + needs: ['repo'] + repo: Ember.computed.alias('controllers.repo.repo') + + validates: + value: ['presence'] actions: add: -> @@ -12,8 +17,12 @@ Travis.SshKeyController = Ember.ObjectController.extend @set('isEditing', true) save: -> - @get('model').save().then => - @set('isEditing', false) + if @isValid() + @get('model').save().then => + @set('isEditing', false) + , (xhr) => + if xhr.status == 422 + @addErrorsFromResponse(JSON.parse(xhr.response)['errors']) delete: -> @get('model').deleteRecord().then => diff --git a/assets/scripts/app/helpers/handlebars.coffee b/assets/scripts/app/helpers/handlebars.coffee index 15146547..dec9ef5b 100644 --- a/assets/scripts/app/helpers/handlebars.coffee +++ b/assets/scripts/app/helpers/handlebars.coffee @@ -59,6 +59,43 @@ Ember.LinkView.reopen _trackEvent: (event) -> event.preventDefault() +FormFieldRowView = Ember.View.extend + invalid: Ember.computed.notEmpty('errors.[]') + classNameBindings: ['invalid'] + classNames: 'field' + +Ember.Handlebars.registerHelper('travis-field', (name, options) -> + errors = @get('errors').for(name) + template = options.fn + delete options.fn + + view = FormFieldRowView.create( + controller: this + template: template + errors: errors + ) + + Ember.Handlebars.helpers.view.call(this, view, options) +) +Travis.ErrorsView = Ember.View.extend + tagName: 'span' + template: Ember.Handlebars.compile("{{#each view.errors}}{{message}}{{/each}}") + classNames: ['error'] + classNameBindings: ['codes'] + codes: (-> + @get('errors').mapBy('code') + ).property('@errors') + +Ember.Handlebars.helper('travis-errors', (name, options) -> + errors = @get('errors').for(name) + view = Travis.ErrorsView.create( + controller: this + errors: errors + ) + + Ember.Handlebars.helpers.view.call(this, view, options) +) + Ember.Handlebars.registerHelper('settings-form', (path, options) -> if arguments.length == 1 options = path diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee index 9bd8877d..4c7faf1b 100644 --- a/assets/scripts/app/routes.coffee +++ b/assets/scripts/app/routes.coffee @@ -366,7 +366,7 @@ Travis.SettingsIndexRoute = Travis.Route.extend Travis.EnvVarsRoute = Travis.Route.extend model: (params) -> repo = @modelFor('repo') - repo.get('envVars') + repo.get('envVars.promise') Travis.SshKeyRoute = Travis.Route.extend model: (params) -> diff --git a/assets/scripts/app/templates/env_vars.hbs b/assets/scripts/app/templates/env_vars.hbs index 84068b52..20cef410 100644 --- a/assets/scripts/app/templates/env_vars.hbs +++ b/assets/scripts/app/templates/env_vars.hbs @@ -1,27 +1,26 @@ -
- {{outlet}} -
+{{outlet}} -{{#link-to "env_vars.new" class="env_var_add"}}Add a new variable{{/link-to}} - -{{#each controller itemController="envVar"}} - {{#if isEditing}} - {{! TODO: reuse it with "new", it's almost identical }} -
-

Name: {{input value=name}}

- {{#if public}}

Value: {{input value=value}}

{{/if}} -

Public: {{input type="checkbox" checked=public}}

- -

- {{input type="submit" value="Save"}} or Cancel -

-
- {{else}} -

- Name: {{name}}
- Value: {{value}}
- Edit - Delete -

- {{/if}} -{{/each}} + diff --git a/assets/scripts/app/templates/env_vars/_form.hbs b/assets/scripts/app/templates/env_vars/_form.hbs new file mode 100644 index 00000000..682f908e --- /dev/null +++ b/assets/scripts/app/templates/env_vars/_form.hbs @@ -0,0 +1,22 @@ +
+ {{#travis-field "name"}} + + {{input value=name class="env-name"}} {{travis-errors "name"}} + {{/travis-field}} + {{#if showValueField}} +
+ + {{input value=value class="env-value"}} +
+ {{/if}} +
+ + {{input type="checkbox" checked=public}} +
+ +
+ + or + Cancel +
+
diff --git a/assets/scripts/app/templates/env_vars/index.hbs b/assets/scripts/app/templates/env_vars/index.hbs new file mode 100644 index 00000000..04eb7bef --- /dev/null +++ b/assets/scripts/app/templates/env_vars/index.hbs @@ -0,0 +1 @@ +{{#link-to "env_vars.new" class="add-env-var"}}Add a new variable{{/link-to}} diff --git a/assets/scripts/app/templates/env_vars/new.hbs b/assets/scripts/app/templates/env_vars/new.hbs index c883c086..1afee20f 100644 --- a/assets/scripts/app/templates/env_vars/new.hbs +++ b/assets/scripts/app/templates/env_vars/new.hbs @@ -1,9 +1 @@ -
-

Name: {{input value=name class="env-name"}}

-

Value: {{input value=value class="env-value"}}

-

Public: {{input type="checkbox" checked=public}}

- -

- {{input type="submit" value="Add" class="submit-env-var"}} or Cancel -

-
+{{partial 'env_vars/form'}} diff --git a/assets/scripts/app/templates/settings.hbs b/assets/scripts/app/templates/settings.hbs index 0c5941a5..d9d7c616 100644 --- a/assets/scripts/app/templates/settings.hbs +++ b/assets/scripts/app/templates/settings.hbs @@ -4,6 +4,6 @@
  • {{#link-to "ssh_key"}}Ssh key{{/link-to}}
  • -
    +
    {{outlet}}
    diff --git a/assets/scripts/app/templates/ssh_key.hbs b/assets/scripts/app/templates/ssh_key.hbs index 79755f00..d8234c79 100644 --- a/assets/scripts/app/templates/ssh_key.hbs +++ b/assets/scripts/app/templates/ssh_key.hbs @@ -2,7 +2,7 @@ {{#if isEditing}}

    Description: {{input value=description}}

    -

    Value: {{textarea value=value}}

    +

    Value: {{textarea value=value}} {{travis-errors "value"}}

    {{input type="submit" value="Save"}} or Cancel diff --git a/assets/scripts/lib/travis/expandable_record_array.coffee b/assets/scripts/lib/travis/expandable_record_array.coffee index c19a6043..4db4321e 100644 --- a/assets/scripts/lib/travis/expandable_record_array.coffee +++ b/assets/scripts/lib/travis/expandable_record_array.coffee @@ -2,6 +2,23 @@ Travis.ExpandableRecordArray = Ember.RecordArray.extend isLoaded: false isLoading: false + promise: (-> + console.log 'promise' + self = this + new Ember.RSVP.Promise (resolve, reject) -> + console.log 'inside promise' + observer = -> + console.log 'observer', self.get('isLoaded') + if self.get('isLoaded') + console.log 'resolve' + resolve(self) + self.removeObserver('isLoaded', observer) + true + + unless observer() + self.addObserver 'isLoaded', observer + ).property() + load: (array) -> @set 'isLoading', true self = this diff --git a/assets/scripts/lib/travis/validations.coffee b/assets/scripts/lib/travis/validations.coffee new file mode 100644 index 00000000..f39d375f --- /dev/null +++ b/assets/scripts/lib/travis/validations.coffee @@ -0,0 +1,87 @@ +get = Ember.get + +Error = Ember.Object.extend + message: (-> + switch @get('code') + when 'blank' then "can't be blank" + when 'not_a_private_key' then "the key is not a valid private key" + else "unknown error" + ).property('code') + +FieldErrors = Ember.ArrayProxy.extend + add: (error) -> + @get('content').pushObject(error) + + isValid: -> + @get('length') == 0 + +Errors = Ember.ArrayProxy.extend + for: (name) -> + fieldErrors = @findBy('name', name) + unless fieldErrors + fieldErrors = FieldErrors.create(name: name, content: []) + @get('content').pushObject(fieldErrors) + + fieldErrors + + add: (name, code) -> + @for(name).add(Error.create(name: name, code: code)) + + isValid: -> + @every (fieldErrors) -> fieldErrors.isValid() + + clear: -> + @forEach (fieldErrors) -> fieldErrors.clear() + +Validator = Ember.Object.extend + setError: (target) -> + target.get('errors').add(@get('name'), @get('code')) + + isValid: (target) -> + name = @get('name') + @get('validator').call(target, get(target, name)) + + validate: (target) -> + unless @isValid(target) + @setError(target) + +Travis.Validations = Ember.Mixin.create + init: -> + @_super.apply this, arguments + + @validators = [] + @set('errors', Errors.create(content: [])) + + if validations = @get('validates') + for field, properties of validations + for property in properties + @_addValidation(field, property) + + _addValidation: (name, type) -> + observer = -> + @get('errors').for(name).clear() + @addObserver(name, this, observer) + @["_add#{type.capitalize()}Validator"].call(this, name) + + _addPresenceValidator: (name) -> + @_addValidator name, "blank", (value) -> + !Ember.isBlank(value) + + _addValidator: (name, code, validator) -> + @validators.pushObject(Validator.create(name: name, code: code, validator: validator)) + + validate: -> + @get('errors').clear() + for validator in @validators + validator.validate(this) + + isValid: -> + @validate() + @get('errors').isValid() + + clearValidations: -> + @get('errors').clear() + + addErrorsFromResponse: (errors) -> + for error in errors + @get('errors').add(error.field, error.code) diff --git a/assets/styles/settings.sass b/assets/styles/settings.sass index 35f3049c..3d4cd67a 100644 --- a/assets/styles/settings.sass +++ b/assets/styles/settings.sass @@ -1,28 +1,29 @@ -.settings-form - margin: 20px +#settings + .settings-form + margin: 20px - p.short-settings-element - display: inline-block - vertical-align: middle - margin: 0 10px 0 0 - float: left + p.short-settings-element + display: inline-block + vertical-align: middle + margin: 0 10px 0 0 + float: left - label - line-height: 30px + 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 + 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 @@ -30,88 +31,117 @@ border-color: #ffb6c1 background: rgb(252, 227, 230) -.new-env-var - margin-top: 20px + form.env-var + margin-top: 20px -a.env_var_add - color: #ffffff - background-color: #97a3aa - border-radius: 4px - padding: 8px 12px 8px 12px - font-size: 13px - line-height: 70px - cursor: pointer + .field + height: 33px + label + width: 40px + display: inline-block -a.env_var_add:hover - background-color: #7c878d + span.error + display: inline-block + margin-left: 10px + color: #a80000 -input.env-name - margin-left: 6px + .env-var input[type=submit].saving, .env-var .delete-var.deleting + background-color: #bbb + background-image: inline-image('ui/round-spinner.svg') + background-repeat: no-repeat + background-position: center + background-size: 20px + text-indent: -9999px -input.env-value - margin-left: 8px -input - display: inline-block - width: 260px - height: 20px - padding: 0 5px 0 5px - background-color: #fff - border: 1px solid #ddd - line-height: 17px - font-size: 13px - color: #999999 - text-align: left + a.add-env-var + color: #ffffff + background-color: #97a3aa + border-radius: 4px + padding: 8px 12px 8px 12px + font-size: 13px + line-height: 70px + cursor: pointer -p.env-actions - margin-bottom: 35px + .add-env-var:hover + background-color: #7c878d -input.submit-env-var - color: #ffffff - background-color: #7ea35a - border-radius: 4px - font-size: 13px - width: 60px - height: 32px - text-align: center - margin-right: 6px - cursor: pointer + input + display: inline-block + width: 260px + height: 20px + padding: 0 5px 0 5px + background-color: #fff + border: 1px solid #ddd + line-height: 17px + font-size: 13px + color: #999999 + text-align: left -input.submit-env-var:hover - background-color: #6f924f + form.env-var .actions + margin-bottom: 35px -a.cancel-env-var - margin-left: 3px + input.submit-env-var + color: #ffffff + background-color: #7ea35a + border-radius: 4px + font-size: 13px + width: 60px + height: 32px + text-align: center + margin-right: 6px + cursor: pointer -a.cancel-env-var:hover - text-decoration: underline + .submit-env-var:hover + background-color: #6f924f -input[type="checkbox"] - display: inline-block - vertical-align: middle + .cancel-env-var + margin-left: 3px -a.edit_var - display: inline-block - margin: 6px 0 15px 0 - color: #ffffff - background-color: #bcbcbb - border-radius: 4px - padding: 4px 15px 4px 15px - font-size: 13px - cursor: pointer + .cancel-env-var:hover + text-decoration: underline -a.edit_var:hover - background-color: #a4a5a4 + input[type="checkbox"] + display: inline-block + vertical-align: middle -a.delete_var - display: inline-block - margin: 6px 0 15px 0 - color: #ffffff - background-color: #932828 - border-radius: 4px - padding: 4px 15px 4px 15px - font-size: 13px - cursor: pointer + .edit-var + display: inline-block + margin: 6px 0 15px 0 + color: #ffffff + background-color: #bcbcbb + border-radius: 4px + padding: 4px 15px 4px 15px + font-size: 13px + cursor: pointer -a.delete_var:hover - background-color: #820b0b \ No newline at end of file + .edit-var:hover + background-color: #a4a5a4 + + .delete-var + display: inline-block + margin: 6px 0 15px 0 + color: #ffffff + background-color: #932828 + border-radius: 4px + padding: 4px 15px 4px 15px + font-size: 13px + cursor: pointer + + .delete-var:hover + background-color: #820b0b + + ul.env-vars + max-width: 500px + li.env-var + .var + font-size: 13px + display: inline-block + margin-right: 10px + + .row + .label + width: 35px + display: inline-block + text-align: left + margin-right: 5px