lanes 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/client/lanes/Config.coffee +3 -1
  4. data/client/lanes/access/screens/user-management/UserManagement.cjsx +2 -2
  5. data/client/lanes/components/grid/Body.cjsx +8 -1
  6. data/client/lanes/components/grid/EditingMixin.cjsx +5 -2
  7. data/client/lanes/components/grid/Grid.cjsx +1 -1
  8. data/client/lanes/components/grid/PopOverMixin.cjsx +1 -1
  9. data/client/lanes/components/grid/row-editor.scss +6 -0
  10. data/client/lanes/components/grid/styles.scss +1 -1
  11. data/client/lanes/components/record-finder/RecordFinder.cjsx +22 -16
  12. data/client/lanes/components/select-field/SelectField.cjsx +20 -27
  13. data/client/lanes/components/shared/DateTime.cjsx +12 -23
  14. data/client/lanes/components/shared/DisplayValue.cjsx +5 -10
  15. data/client/lanes/components/shared/FieldMixin.cjsx +107 -56
  16. data/client/lanes/components/shared/FieldWrapper.cjsx +59 -6
  17. data/client/lanes/components/shared/FormGroup.cjsx +3 -8
  18. data/client/lanes/components/shared/Helpers.coffee +1 -1
  19. data/client/lanes/components/shared/Icon.cjsx +1 -1
  20. data/client/lanes/components/shared/ImageAsset.cjsx +46 -0
  21. data/client/lanes/components/shared/Input.cjsx +5 -1
  22. data/client/lanes/components/shared/InputFieldMixin.cjsx +8 -27
  23. data/client/lanes/components/shared/NumberInput.cjsx +11 -4
  24. data/client/lanes/components/shared/RadioField.cjsx +18 -8
  25. data/client/lanes/components/shared/ScreenWrapper.cjsx +1 -1
  26. data/client/lanes/components/shared/TextArea.cjsx +15 -0
  27. data/client/lanes/components/shared/ToggleField.cjsx +11 -19
  28. data/client/lanes/components/shared/fields.scss +22 -76
  29. data/client/lanes/components/shared/{image-saver.scss → image-asset.scss} +1 -1
  30. data/client/lanes/components/shared/styles.scss +1 -1
  31. data/client/lanes/components/toolbar/RemoteChangeSets.cjsx +13 -10
  32. data/client/lanes/components/toolbar/changes-notification.scss +14 -1
  33. data/client/lanes/extension/Base.coffee +5 -1
  34. data/client/lanes/models/Asset.coffee +81 -0
  35. data/client/lanes/models/AssociationMap.coffee +14 -5
  36. data/client/lanes/models/AssociationProxy.coffee +9 -6
  37. data/client/lanes/models/Base.coffee +6 -3
  38. data/client/lanes/models/Collection.coffee +1 -3
  39. data/client/lanes/models/JobStatus.coffee +3 -0
  40. data/client/lanes/models/Query.coffee +3 -2
  41. data/client/lanes/models/Sync.coffee +6 -4
  42. data/client/lanes/react/mixins/Access.coffee +1 -1
  43. data/client/lanes/react/mixins/Data.coffee +33 -31
  44. data/client/lanes/screens/Commands.coffee +0 -1
  45. data/client/lanes/screens/Definitions.coffee +1 -1
  46. data/client/lanes/screens/SystemSettings.cjsx +23 -11
  47. data/client/lanes/screens/UserPreferences.cjsx +3 -2
  48. data/client/lanes/styles/bootstrap-custom-grid.scss +6 -4
  49. data/client/lanes/styles/fonts.scss +9 -0
  50. data/client/lanes/styles/global/styles.scss +1 -0
  51. data/client/lanes/styles/variables.scss +1 -1
  52. data/client/lanes/testing/Helpers.coffee +5 -3
  53. data/client/lanes/vendor/development/base.js +17206 -19211
  54. data/client/lanes/vendor/development/calendar.js +67 -471
  55. data/client/lanes/vendor/development/commons.js +21680 -19814
  56. data/client/lanes/vendor/development/helpers.js +40 -29
  57. data/client/lanes/vendor/development/toggle.js +22 -19
  58. data/client/lanes/vendor/development/widgets.js +2476 -2625
  59. data/client/lanes/vendor/production/base.js +19034 -21038
  60. data/client/lanes/vendor/production/calendar.js +67 -471
  61. data/client/lanes/vendor/production/commons.js +21369 -19136
  62. data/client/lanes/vendor/production/toggle.js +22 -19
  63. data/client/lanes/vendor/production/widgets.js +2476 -2625
  64. data/client/lanes/vendor/styles/widgets.scss +2 -0
  65. data/client/lanes/workspace/FirstRun.cjsx +69 -0
  66. data/client/lanes/workspace/Navbar.cjsx +4 -2
  67. data/client/lanes/workspace/ScreenView.cjsx +9 -1
  68. data/client/lanes/workspace/index.js +1 -0
  69. data/client/lanes/workspace/styles/screens.scss +11 -0
  70. data/config/database.yml +2 -2
  71. data/db/migrate/01_create_system_settings.rb +0 -1
  72. data/db/migrate/02_create_assets.rb +15 -0
  73. data/lanes.gemspec +25 -28
  74. data/lib/lanes/access/authentication_provider.rb +20 -7
  75. data/lib/lanes/access/role_collection.rb +9 -2
  76. data/lib/lanes/api/default_routes.rb +4 -4
  77. data/lib/lanes/api/formatted_reply.rb +15 -11
  78. data/lib/lanes/api/handlers/asset.rb +37 -0
  79. data/lib/lanes/api/request_wrapper.rb +18 -4
  80. data/lib/lanes/api/routing.rb +17 -6
  81. data/lib/lanes/api/updates.rb +1 -1
  82. data/lib/lanes/api.rb +1 -1
  83. data/lib/lanes/asset.rb +38 -0
  84. data/lib/lanes/concerns/all.rb +1 -1
  85. data/lib/lanes/concerns/asset_uploader.rb +60 -0
  86. data/lib/lanes/configuration.rb +1 -2
  87. data/lib/lanes/extension.rb +1 -1
  88. data/lib/lanes/logger.rb +26 -16
  89. data/lib/lanes/system_settings.rb +13 -8
  90. data/lib/lanes/version.rb +1 -1
  91. data/lib/lanes.rb +1 -0
  92. data/npm-build/base.js +1 -1
  93. data/npm-build/package.json +9 -9
  94. data/spec/command-reference-files/initial/Gemfile +1 -1
  95. data/spec/fixtures/logo.png +0 -0
  96. data/spec/lanes/components/grid/PopoverEditorSpec.coffee +48 -0
  97. data/spec/server/asset_spec.rb +34 -0
  98. data/spec/server/spec_helper.rb +14 -2
  99. metadata +118 -127
  100. data/client/lanes/components/shared/ControlLabel.cjsx +0 -45
  101. data/client/lanes/components/shared/ImageSaver.cjsx +0 -33
  102. data/client/lanes/models/SystemSettings.coffee +0 -0
  103. data/client/lanes/models/mixins/FileSupport.coffee +0 -60
  104. data/lib/lanes/api/handlers/file.rb +0 -26
  105. data/lib/lanes/concerns/image_uploader.rb +0 -42
@@ -64,6 +64,9 @@ class BaseModel
64
64
  modelTypeIdentifier: ->
65
65
  _.dasherize(_.last(@FILE?.path || ''))
66
66
 
67
+ extensionIdentifier: ->
68
+ @FILE.extension.identifier
69
+
67
70
  api_path: ->
68
71
  id = @FILE?.extension.identifier
69
72
  ( if id then "/#{id}" else '' ) + '/' + _.pluralize(@modelTypeIdentifier())
@@ -137,11 +140,11 @@ class BaseModel
137
140
  record
138
141
 
139
142
  # Sets the attribute data from a server respose
140
- setFromServer: (data, options) ->
143
+ setFromServer: (data, options, method) ->
141
144
  data = if _.isArray(data) then data[0] else data
142
145
  BaseModel.__super__.set.call(this, data )
143
146
  @unset('errors')
144
- this.associations.setFromServer(this, data, options) if this.associations
147
+ this.associations?.setFromServer(this, data, options, method)
145
148
  this.isDirty = false
146
149
 
147
150
  # save the model's data to the server
@@ -203,7 +206,7 @@ class BaseModel
203
206
  unCacheDerived: (name) ->
204
207
  delete this._cache[name]
205
208
 
206
- # True if the model has "name" as either a prop or session attribute
209
+ # True if the model has "name" as either a prop, session, or derived attribute
207
210
  hasAttribute: (name) ->
208
211
  !! (this._definition[name] || this._derived[name])
209
212
 
@@ -199,9 +199,7 @@ class Lanes.Models.AssociationCollection extends Lanes.Models.Collection
199
199
  _.extend(attrs, @associationFilter)
200
200
  model = super
201
201
  if @options.inverse
202
- parent = this.parent.clone()
203
- parent[@options.inverse.without].reset() if @options.inverse.without
204
- model.set(@options.inverse.name, parent, options)
202
+ model.set(@options.inverse.name, @parent, options)
205
203
  model
206
204
 
207
205
  fetch: (options) ->
@@ -11,6 +11,9 @@ class Lanes.Models.JobStatus extends Lanes.Models.Base
11
11
  errors: 'any'
12
12
  data: 'object'
13
13
 
14
+ session:
15
+ parent: 'object'
16
+
14
17
  events:
15
18
  'remote-update': 'onUpdate'
16
19
 
@@ -271,15 +271,16 @@ class Lanes.Models.Query extends Lanes.Models.Base
271
271
  reset: ->
272
272
  unless @defaultSort is false
273
273
  sort = @defaultSort or @fields.findWhere(visible: true).id
274
- @setSortField( @fields.findWhere(id: sort), sortAscending: true, silent: true )
274
+ @setSortField( @fields.findWhere(id: sort), sortAscending: @sortAscending, silent: true )
275
275
 
276
276
  @clauses.reset([
277
277
  {query: this, available_fields: @fields, field: @initialField}
278
278
  ])
279
279
 
280
280
  setSortField: (field, options = {silent: false}) ->
281
+ options.sortAscending ?= (if @sortField is field then !@sortAscending else true)
281
282
  @set({
282
- sortAscending: options.sortAscending || (if @sortField is field then !@sortAscending else true)
283
+ sortAscending: options.sortAscending
283
284
  sortField: field
284
285
  }, options)
285
286
 
@@ -38,13 +38,15 @@ Lanes.Models.Sync = {
38
38
  handler = (reply) ->
39
39
  delete model.requestInProgress
40
40
  model.lastServerMessage = reply.message
41
- model.setFromServer(reply.data, options, method)
42
- model.trigger("save", model, reply, options) if isSave
43
- model.trigger("sync", model, reply, options)
44
- if reply.errors
41
+ if reply.errors or reply.success is false
45
42
  Lanes.warn reply.errors
46
43
  model.errors = reply.errors
47
44
  model.trigger("error", model, options)
45
+ else
46
+ model.setFromServer(reply.data, options, method)
47
+ model.trigger("save", model, reply, options) if isSave
48
+ model.trigger("sync", model, reply, options)
49
+
48
50
  resolve(model)
49
51
 
50
52
  Lanes.Models.Sync.perform(method, options).then (reply) ->
@@ -13,7 +13,7 @@ calculateAccess = (comp, props) ->
13
13
  comp.setState({accessRight}) if accessRight
14
14
 
15
15
  Lanes.React.Mixins.Access = {
16
-
16
+ getInitialState: -> {}
17
17
  componentDidMount: ->
18
18
  calculateAccess(this, @props)
19
19
 
@@ -13,33 +13,34 @@ class DataWrapper
13
13
 
14
14
  rebind: (objects, options = {}) ->
15
15
  customEvents = _.result(@component, 'bindDataEvents', {})
16
- for name, state of objects
17
- if false == state
18
- continue
19
- unless state
20
- Lanes.warn "#{name} is not set on #{@componentName()}"
21
- continue
22
- prevState = @states[name]
23
- # onto next if the object is the same
24
- continue if prevState == state
25
-
26
- @states[name] = state
27
- this.stopListening(prevState) if prevState
28
-
29
- this.bindEvents(name, state, customEvents[name]) if state
30
-
31
- if Lanes.u.isModel(state)
32
- @listenToNetworkEvents(state) if @component.listenNetworkEvents
33
- if @isStateUsingPubsub(name)
34
- if !prevState? or prevState.getId() != state.getId()
35
- Lanes.Models.PubSub.remove(prevState) if prevState
36
- unless false == state.pubsub
37
- if state.isNew()
38
- state.once "change:#{state.idAttribute}", -> Lanes.Models.PubSub.add(state)
39
- else
40
- Lanes.Models.PubSub.add(state)
41
- @listenTo(state, 'remote-update', @onPubSubChangeSet)
42
- this.setComponentState({}) unless _.isEmpty(objects) or options.silent
16
+ @_rebindAttr(name, state, customEvents[name], options) for name, state of objects when state isnt false
17
+
18
+ _rebindAttr: (name, state, ev, options) ->
19
+ unless state
20
+ Lanes.warn "#{name} is not set on #{@componentName()}"
21
+ return
22
+
23
+ prevState = @states[name]
24
+
25
+ # onto next if the object is the same
26
+ return if prevState == state
27
+
28
+ @states[name] = state
29
+
30
+ this.bindEvents(name, state, ev)
31
+
32
+ if Lanes.u.isModel(state)
33
+ @listenToNetworkEvents(state) if @component.listenNetworkEvents
34
+ if @isStateUsingPubsub(name, state)
35
+ if !prevState? or prevState.getId() != state.getId()
36
+ Lanes.Models.PubSub.remove(prevState) if prevState
37
+ unless false == state.pubsub
38
+ if state.isNew()
39
+ state.once "change:#{state.idAttribute}", -> Lanes.Models.PubSub.add(state)
40
+ else
41
+ Lanes.Models.PubSub.add(state)
42
+ @listenTo(state, 'remote-update', @onPubSubChangeSet)
43
+ this.setComponentState({}) unless options.silent
43
44
 
44
45
  listenToNetworkEvents: (state) ->
45
46
  @listenTo(state, 'error', @onError)
@@ -102,11 +103,12 @@ class DataWrapper
102
103
  @component.setState(state)
103
104
  true
104
105
 
105
- isStateUsingPubsub: (name) ->
106
- not (false == @component.pubsub or false == @component.pubsub?[name])
106
+ isStateUsingPubsub: (name, state) ->
107
+ _.result(state, 'registerforPubSub') isnt false and
108
+ not (false == @component.pubsub or false == @component.pubsub?[name])
107
109
 
108
110
  destroy: (state, events, fn) ->
109
- for name, state of @states when @isStateUsingPubsub(name)
111
+ for name, state of @states when @isStateUsingPubsub(name, state)
110
112
  Lanes.Models.PubSub.remove(state) if Lanes.u.isModel(state)
111
113
  this.stopListening()
112
114
  delete @component.data
@@ -142,7 +144,7 @@ Lanes.React.Mixins.Data = {
142
144
  newState = readDataObjects(this, newProps)
143
145
  return if _.isEmpty(newState)
144
146
  if @data
145
- @data.rebind(newState, silent:true)
147
+ @data.rebind(newState)
146
148
  else
147
149
  @data = new DataWrapper(this, newState)
148
150
 
@@ -18,7 +18,6 @@ class Lanes.Screens.Commands extends Lanes.Models.State
18
18
  @options.modelDidRebind?(model)
19
19
 
20
20
  canEditModel: ->
21
-
22
21
  @screen.hasWriteAccess?()
23
22
 
24
23
  getSyncOptions: ->
@@ -95,7 +95,7 @@ class ScreenDefinition extends Lanes.Models.BasicModel
95
95
  resolve(me)
96
96
  else if attempt < 3
97
97
  attempt += 1
98
- _.delay(done, 500)
98
+ _.delay(done, 500 * attempt)
99
99
  else
100
100
  reject("Screen #{me.view} not definied after file retrieval")
101
101
  err = (msg) ->
@@ -13,27 +13,37 @@ class Lanes.Screens.SystemSettings extends Lanes.React.Screen
13
13
 
14
14
  renderFileOptions: ->
15
15
  dir = @config.settings.lanes.storage_dir
16
- <LC.FieldWrapper label='Store Directory' sm=9 {...@props} value={dir}>
16
+ <LC.FieldWrapper
17
+ model={@config} displayComponent={<span />}
18
+ label='Store Directory' sm=9 {...@props} value={dir}>
17
19
  <input type="text" className='value form-control'
18
20
  placeholder="Directory to store files" value={dir}
19
21
  onChange={_.partial(@onChange, 'storage_dir')} />
20
22
  </LC.FieldWrapper>
21
23
 
24
+ fogValue: ->
25
+ @state.fogValue or
26
+ JSON.stringify(@config.settings.lanes.fog_credentials, null, 2)
27
+ setFog: (ev) ->
28
+ @setState(fogValue: ev.target.value)
29
+
22
30
  renderFogOptions: ->
23
- value = JSON.stringify(@config.settings.lanes.fog_credentials, null, 2)
24
- <LC.FieldWrapper label='Fog Options' sm=9 {...@props} value={value}>
25
- <BS.Input type="textarea" placeholder="FOG options (as JSON)" value={value}
26
- onChange={_.partial(@onChange, 'fog_credentials')} />
27
- </LC.FieldWrapper>
31
+ <LC.TextArea sm=9 name='fog' placeholder="FOG options (as JSON)" model={@config}
32
+ getValue={@fogValue} onChange={@setFog} />
28
33
 
29
34
  saveConfig: ->
35
+ comp.onBeforeSave?() for id, comp of @refs
36
+ if @state.fogValue
37
+ try
38
+ @config.settings.lanes.fog_credentials = JSON.parse(@state.fogValue)
30
39
  @config.save(saveAll: true)
40
+ comp.onSave?() for id, comp of @refs
31
41
 
32
42
  render: ->
33
43
  choices = ['file', 'fog']
34
44
  storage = @config.settings.lanes?.storage || 'file'
35
45
 
36
- <LC.ScreenWrapper identifier="user-preferences">
46
+ <LC.ScreenWrapper identifier="system-settings">
37
47
  <BS.Nav bsStyle="pills" className="lanes-toolbar">
38
48
  <BS.Button navItem componentClass="button"
39
49
  onClick={@saveConfig} className="save navbar-btn control">
@@ -42,15 +52,17 @@ class Lanes.Screens.SystemSettings extends Lanes.React.Screen
42
52
  </BS.Nav>
43
53
 
44
54
  <BS.Row>
45
- <LC.FieldWrapper label='File Storage' sm=3 {...@props} value={storage}>
55
+ <LC.FieldWrapper
56
+ model={@config} displayComponent={<span />}
57
+ label='File Storage' sm=3 {...@props} value={storage}>
46
58
  <Lanes.Vendor.ReactWidgets.DropdownList
47
59
  data={choices} value={storage} onChange={_.partial(@onChange, 'storage')} />
48
60
  </LC.FieldWrapper>
49
61
  {if storage is 'file' then @renderFileOptions() else @renderFogOptions()}
50
62
  </BS.Row>
51
63
  <BS.Row>
52
- <LC.ImageSaver label='Logo' sm=4 model={@config} name='logo' />
64
+ <LC.ImageAsset sm=4 model={@config} name='logo' label='Logo' size='thumb' />
53
65
  </BS.Row>
54
- {for id, Ext of Lanes.Extensions.instances when Ext.settingsElement
55
- <Ext.settingsElement settings={@config.settings[id]} key={id} /> }
66
+ {for id, Ext of Lanes.Extensions.instances when Ext.getSettingsElement
67
+ Ext.getSettingsElement(ref: id, key: id, settings: @config.settings[id])}
56
68
  </LC.ScreenWrapper>
@@ -16,6 +16,7 @@ class Lanes.Screens.UserPreferences extends Lanes.React.Screen
16
16
  {id, label: screen.label}
17
17
  )
18
18
 
19
+
19
20
  render: ->
20
21
  <LC.ScreenWrapper identifier="user-preferences">
21
22
  <Lanes.Screens.CommonComponents commands={@state.commands} />
@@ -33,6 +34,6 @@ class Lanes.Screens.UserPreferences extends Lanes.React.Screen
33
34
  getSelection={@getScreens}
34
35
  />
35
36
  </BS.Row>
36
- {for id, Ext of Lanes.Extensions.instances when Ext.preferenceElement
37
- <Ext.preferenceElement key={id} /> }
37
+ {for id, Ext of Lanes.Extensions.instances when Ext.getPreferenceElement
38
+ Ext.getPreferenceElement(key: id)}
38
39
  </LC.ScreenWrapper>
@@ -90,9 +90,11 @@
90
90
  }
91
91
  }
92
92
 
93
- .modal {
93
+ .lanes-modal, .modal, .popover {
94
+ @include make-grid-columns();
95
+
94
96
  &.lg {
95
- .modal-lg {
97
+ .modal-lg, .popover-content {
96
98
  @include make-grid(xs);
97
99
  @include make-grid(sm);
98
100
  @include make-grid(md);
@@ -100,14 +102,14 @@
100
102
  }
101
103
  }
102
104
  &.md {
103
- .modal-lg {
105
+ .modal-lg, .popover-content {
104
106
  @include make-grid(xs);
105
107
  @include make-grid(sm);
106
108
  @include make-grid(md);
107
109
  }
108
110
  }
109
111
  &.sm {
110
- .modal-lg {
112
+ .modal-lg, .popover-content {
111
113
  @include make-grid(xs);
112
114
  @include make-grid(sm);
113
115
  }
@@ -13,3 +13,12 @@ button, a.btn {
13
13
  font-family: $lanes-icon-font;
14
14
  }
15
15
  }
16
+
17
+
18
+ // replace glyphicons with fa
19
+ .glyphicon {
20
+ @extend .icon;
21
+ &.glyphicon-ok:before { content: $fa-var-check; }
22
+ &.glyphicon-warning-sign:before { content: $fa-var-exclamation-triangle; }
23
+ &.glyphicon-error:before { content: $fa-var-exclamation; }
24
+ }
@@ -1 +1,2 @@
1
1
  .cursor-pointer { cursor: pointer; }
2
+ a { cursor: pointer; }
@@ -1,4 +1,4 @@
1
- $lanes-field-value-height: 34px;
1
+ $lanes-field-value-height: 30px;
2
2
 
3
3
  $lanes-color-selections: (
4
4
  #4D4D4D, // (gray)
@@ -25,12 +25,14 @@ Lanes.Test.renderComponent = (component, args = {}) ->
25
25
  Lanes.Test.Utils = Lanes.Vendor.ReactTestUtils
26
26
 
27
27
  wrap = (name) ->
28
- ->
29
- Lanes.Test.Utils.Simulate[name](@el, arguments...)
28
+ (options = {}) ->
29
+ args = _.extend {target: @el}, options
30
+ Lanes.Test.Utils.Simulate[name](@el, args)
30
31
  return @
31
32
 
32
33
  for name, func of Lanes.Test.Utils.Simulate
33
34
  Lanes.lib.Dom::[name] = wrap(name, func)
34
35
 
35
36
  Lanes.lib.Dom::setValue = (value) ->
36
- @setAttribute('value', value).change()
37
+ @value = value
38
+ @change()