Final touches on env vars

This commit is contained in:
Piotr Sarnacki 2014-08-01 02:50:20 +02:00
parent 68fe6f4f46
commit c9f58718cf
16 changed files with 446 additions and 154 deletions

View File

@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="64" height="64" fill="white">
<circle cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(45 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.125s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(90 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.25s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(135 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.375s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(180 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.5s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(225 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.625s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(270 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.75s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(315 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.875s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="rotate(180 16 16)" cx="16" cy="3" r="0">
<animate attributeName="r" values="0;3;0;0" dur="1s" repeatCount="indefinite" begin="0.5s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" calcMode="spline" />
</circle>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,26 @@
<svg id="loading" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="white">
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(0 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0"/>
</path>
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(45 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0.125s"/>
</path>
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(90 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0.25s"/>
</path>
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(135 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0.375s"/>
</path>
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(180 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0.5s"/>
</path>
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(225 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0.675s"/>
</path>
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(270 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0.75s"/>
</path>
<path opacity=".1" d="M14 0 H18 V8 H14 z" transform="rotate(315 16 16)">
<animate attributeName="opacity" from="1" to=".1" dur="1s" repeatCount="indefinite" begin="0.875s"/>
</path>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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)

View File

@ -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)

View File

@ -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 =>

View File

@ -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

View File

@ -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) ->

View File

@ -1,27 +1,26 @@
<div>
{{outlet}}
</div>
{{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 }}
<form {{action "save" on="submit"}}>
<p>Name: {{input value=name}}</p>
{{#if public}}<p>Value: {{input value=value}}</p>{{/if}}
<p>Public: {{input type="checkbox" checked=public}}</p>
<p>
{{input type="submit" value="Save"}} or <a href="#" {{action "cancel"}}>Cancel</a>
</p>
</form>
{{else}}
<p>
Name: {{name}}<br/>
Value: {{value}}<br/>
<a href="#" class="edit_var"{{action "edit"}}>Edit</a>
<a href="#" class="delete_var"{{action "delete"}}>Delete</a>
</p>
{{/if}}
{{/each}}
<ul class="env-vars">
{{#each controller itemController="envVar"}}
{{#if isEditing}}
{{partial 'env_vars/form'}}
{{else}}
<li class="env-var">
<div class="row">
<span class="label">Name:</span>
<span class="value">{{name}}</span>
</div>
<div class="row">
<span class="label">Value:</span>
<span class="value">{{value}}</span>
</div>
<div class="actions">
<a href="#" class="edit-var" {{action "edit"}}>Edit</a>
<a href="#" {{action "delete"}} {{bind-attr class=":delete-var isDeleting:deleting"}}>
Delete
</a>
</div>
</li>
{{/if}}
{{/each}}
</ul>

View File

@ -0,0 +1,22 @@
<form class="env-var" {{action "save" on="submit"}}>
{{#travis-field "name"}}
<label>Name:</label>
{{input value=name class="env-name"}} {{travis-errors "name"}}
{{/travis-field}}
{{#if showValueField}}
<div class="field">
<label>Value:</label>
{{input value=value class="env-value"}}
</div>
{{/if}}
<div class="field">
<label>Public:</label>
{{input type="checkbox" checked=public}}
</div>
<div class="actions">
<input type="submit" {{bind-attr value=actionType class=":submit-env-var isSaving:saving" disabled=isSaving}} />
<span class="or">or</span>
<a href="#" class="cancel-env-var" {{action "cancel"}}>Cancel</a>
</div>
</form>

View File

@ -0,0 +1 @@
{{#link-to "env_vars.new" class="add-env-var"}}Add a new variable{{/link-to}}

View File

@ -1,9 +1 @@
<form class="new-env-var" {{action "save" on="submit"}}>
<p>Name: {{input value=name class="env-name"}}</p>
<p>Value: {{input value=value class="env-value"}}</p>
<p>Public: {{input type="checkbox" checked=public}}</p>
<p class="env-actions">
{{input type="submit" value="Add" class="submit-env-var"}} or <a href="#" class="cancel-env-var" {{action "cancel"}}>Cancel</a>
</p>
</form>
{{partial 'env_vars/form'}}

View File

@ -4,6 +4,6 @@
<li>{{#link-to "ssh_key"}}Ssh key{{/link-to}}</li>
</ul>
<div>
<div id="settings">
{{outlet}}
</div>

View File

@ -2,7 +2,7 @@
{{#if isEditing}}
<form {{action "save" on="submit"}}>
<p>Description: {{input value=description}}</p>
<p>Value: {{textarea value=value}}</p>
<p>Value: {{textarea value=value}} {{travis-errors "value"}}</p>
<p>
{{input type="submit" value="Save"}} or <a href="#" {{action "cancel"}}>Cancel</a>

View File

@ -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

View File

@ -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)

View File

@ -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
.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