express_admin 1.7.8 → 1.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,6 +34,7 @@
34
34
  @import 'shared/alerts'
35
35
  @import 'shared/progress'
36
36
  @import 'shared/reveal'
37
+ @import 'shared/buttons'
37
38
 
38
39
  @import 'components/command_button'
39
40
 
@@ -0,0 +1,2 @@
1
+ .button-login
2
+ margin: .5em
@@ -39,4 +39,4 @@
39
39
  background: #fff
40
40
  border: 1px solid #ddd
41
41
  margin: 100px 0
42
- padding: 40px 20px 0 20px
42
+ padding: 40px 20px
@@ -0,0 +1,131 @@
1
+ module ExpressAdmin
2
+ module Components
3
+ class FileUpload < ExpressTemplates::Components::Forms::FormComponent
4
+
5
+ has_option :action, 'The form action containing the resource path or url.'
6
+ has_option :max_file_size, 'The maximum file size a user can upload', type: :int
7
+
8
+ contains -> {
9
+ div(class: 'dropzone-previews') {
10
+ div(class: 'dz-default dz-message') {
11
+ span(class: 'dz-message-instruction') {'Drop files here or click to upload.'}
12
+ }
13
+ }
14
+
15
+ div(class: 'dz-preview dz-file-preview', id: 'preview-template'){
16
+ div(class: 'dz-error-message hide') {
17
+ span('data-dz-errormessage' => 'true')
18
+ }
19
+ div(class: 'dz-image'){
20
+ img('data-dz-thumbnail' => true)
21
+ }
22
+ div(class: 'dz-close hide') {
23
+ icon_link('close-round', 'data-dz-remove' => true)
24
+ }
25
+ div(class: 'dz-details hide'){
26
+ div(class: 'dz-filename'){
27
+ span('data-dz-name' => true)
28
+ }
29
+ div(class: 'dz-size', 'data-dz-size' => true)
30
+ }
31
+ div(class: 'dz-progress') {
32
+ span(class: 'dz-upload', 'data-dz-uploadprogress' => true)
33
+ }
34
+ div(class: 'dz-success-mark hide') {
35
+ icon('checkmark-circled')
36
+ }
37
+ div(class: 'dz-error-mark hide') {
38
+ icon('close-circled')
39
+ }
40
+ }
41
+
42
+ content_for(:page_javascript) {
43
+ script {
44
+ %Q(
45
+ $(function() {
46
+ previewNode = $('#preview-template').get(0).outerHTML;
47
+ $('#preview-template').remove();
48
+ var submitButton;
49
+ Dropzone.options.#{config[:id].to_s.camelize(:lower)} = {
50
+ url: window.location.origin + '#{config[:action]}',
51
+ paramName: '#{config[:id]}[file]',
52
+ autoProcessQueue: false,
53
+ uploadMultiple: false,
54
+ maxFiles: 1,
55
+ maxFilesize: #{max_file_size},
56
+ previewTemplate: previewNode,
57
+ previewsContainer: '.dropzone-previews',
58
+ clickable: '.dropzone-previews',
59
+ init: function() {
60
+ var myDropzone = this;
61
+ submitButton = this.element.querySelector('input[type=submit]');
62
+
63
+ $(submitButton).on('click', function(e) {
64
+ e.preventDefault();
65
+ myDropzone.processQueue();
66
+ });
67
+
68
+ this.on('addedfile', function(file) {
69
+ if (this.files[1]) {
70
+ this.removeFile(this.files[0]);
71
+ }
72
+ $('.dz-message.dz-default').addClass('hide');
73
+ });
74
+
75
+ this.on('removedfile', function() {
76
+ $('.dz-message.dz-default').removeClass('hide');
77
+ $(submitButton).removeClass('disabled');
78
+ });
79
+
80
+ this.on('processing', function() {
81
+ $('.dz-image').zIndex('-999');
82
+ $('.dz-image > img').addClass('blur');
83
+ $(submitButton).addClass('disabled');
84
+ });
85
+
86
+ this.on('maxfilesexceeded', function(file) {
87
+ this.removeAllFiles();
88
+ this.addFile(file);
89
+ });
90
+
91
+ this.on('success', function(file) {
92
+ $('.dz-progress').css('opacity', 0);
93
+ $('.dz-success-mark').removeClass('hide');
94
+ setTimeout( function() {
95
+ $('.dz-image').zIndex('999');
96
+ $('.dz-image > img').removeClass('blur');
97
+ location.reload();
98
+ }, 2000);
99
+ });
100
+
101
+ this.on('complete', function(file) {
102
+ if (file.accepted === false) {
103
+ $('.dz-progress').css('opacity', 0);
104
+ setTimeout(function() { $('.dz-error-mark').removeClass('hide') }, 500);
105
+
106
+ setTimeout(function() {
107
+ $('.dz-image').zIndex('999');
108
+ $('.dz-image > img').removeClass('blur');
109
+ $('.dz-error-message').removeClass('hide');
110
+ $(submitButton).addClass('disabled');
111
+ }, 3000);
112
+ }
113
+ });
114
+
115
+ this.on('drop', function() {
116
+ $('.dz-message.dz-default').addClass('hide');
117
+ });
118
+ },
119
+ }
120
+ });
121
+ ).html_safe
122
+ }
123
+ }
124
+ }
125
+
126
+ def max_file_size
127
+ config[:max_file_size]
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,27 @@
1
+ module ExpressAdmin
2
+ module Components
3
+ class MediaForm < ExpressTemplates::Components::Configurable
4
+
5
+ has_option :max_file_size, 'The maximum file size a user can upload', type: :int, default: 3
6
+
7
+ contains -> {
8
+ express_form(config[:id], enctype: 'multipart/form-data', class: 'dropzone'){
9
+ text :title
10
+ text :description
11
+ label_tag('tags', "Tags")
12
+ select_tag("media_item[tags]", helpers.options_for_select(ExpressSite::Tag.all.map(&:name), media_item.tags.map(&:name)), class: 'select2', multiple: true)
13
+ file_upload config[:id], action: form_action, max_file_size: max_file_size
14
+ submit class: 'button', value: 'Upload'
15
+ }
16
+ }
17
+
18
+ def form_action
19
+ config[:action] || (resource.try(:persisted?) ? resource_path(resource) : collection_path)
20
+ end
21
+
22
+ def max_file_size
23
+ config[:max_file_size]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ module ExpressAdmin
2
+ class OauthSignInLinks < ExpressTemplates::Components::Configurable
3
+
4
+ has_argument :providers, "OAuth providers to generate links for", as: :providers, type: :array
5
+
6
+ contains -> {
7
+ span(class: 'text-gray') { "Login with:" }
8
+ providers.each do |provider|
9
+ sign_in_link(provider)
10
+ end
11
+ }
12
+
13
+ def sign_in_link(provider)
14
+ link_to "#{provider_name(provider)}", user_omniauth_authorize_path(provider), class: 'hollow button button-login'
15
+ end
16
+
17
+ private
18
+
19
+ def provider_name(provider)
20
+ provider.to_s.humanize
21
+ end
22
+
23
+ def providers
24
+ config[:providers]
25
+ end
26
+
27
+ end
28
+ end
@@ -8,6 +8,11 @@ module ExpressAdmin
8
8
 
9
9
  MAX_COLS_TO_SHOW_IDX = 5
10
10
  MAX_ROWS_TO_SHOW_IDX = 10
11
+ COLUMN_REGEX_LIST = {
12
+ link: /(\w+)_link$/,
13
+ checkmark: /(\w+)_checkmark/,
14
+ in_words: /(\w+)_in_words/
15
+ }
11
16
 
12
17
  attr :columns
13
18
 
@@ -33,6 +38,9 @@ module ExpressAdmin
33
38
  end
34
39
  options = options + resource_class.instance_methods.grep(/_count$/).map(&:to_s)
35
40
  }
41
+ has_option :sortable, 'Specify the target column. Provide column sort with caret display to signify column is sortable',
42
+ type: :array,
43
+ default: nil
36
44
  has_option :rows, 'Specify the number of rows to show', type: :integer
37
45
 
38
46
  has_option :pagination, 'Add pagination to the bottom of the table', type: :string, default: 'bottom'
@@ -50,7 +58,7 @@ module ExpressAdmin
50
58
  tr {
51
59
  display_columns.each do |column|
52
60
  th(class: column.name) {
53
- column.title
61
+ config[:sortable].present? ? sortable(column) : column.title
54
62
  }
55
63
  end
56
64
  actions_header if should_show_actions?
@@ -82,6 +90,31 @@ module ExpressAdmin
82
90
  _initialize_columns
83
91
  }
84
92
 
93
+ def sortable(column)
94
+ table_column = column.table_column
95
+ if config[:sortable].include?(column.title) && table_column.present?
96
+ a(href: sort_link(column)) {
97
+ text_node column.title
98
+ if helpers.params[:asc].eql?(table_column)
99
+ i(class: 'icon ion-ios-arrow-up')
100
+ else
101
+ i(class: 'icon ion-ios-arrow-down')
102
+ end
103
+ }
104
+ else
105
+ column.title
106
+ end
107
+ end
108
+
109
+ def sort_link(column)
110
+ uri = URI.parse(config[:id].to_s)
111
+ table_column = column.table_column
112
+ param_asc = helpers.params[:asc]
113
+ sort_direction = param_asc.eql?(table_column) ? :desc : :asc
114
+ uri.query = { sort_direction => table_column }.to_param
115
+ uri.to_s
116
+ end
117
+
85
118
  def pagination
86
119
  paginate collection, :route_set => route_set
87
120
  end
@@ -154,12 +187,12 @@ module ExpressAdmin
154
187
  rescue
155
188
  'Error: '+$!.to_s
156
189
  end
157
- elsif attrib = accessor.to_s.match(/(\w+)_link$/).try(:[], 1)
190
+ elsif attrib = accessor.to_s.match(COLUMN_REGEX_LIST[:link]).try(:[], 1)
158
191
  # TODO: only works with non-namespaced routes
159
192
  helpers.link_to item.send(attrib), resource_path(item)
160
- elsif attrib = accessor.to_s.match(/(\w+)_checkmark/).try(:[], 1)
193
+ elsif attrib = accessor.to_s.match(COLUMN_REGEX_LIST[:checkmark]).try(:[], 1)
161
194
  "<i class='ion-checkmark-round'></i>".html_safe if item.send(attrib)
162
- elsif attrib = accessor.to_s.match(/(\w+)_in_words/).try(:[], 1)
195
+ elsif attrib = accessor.to_s.match(COLUMN_REGEX_LIST[:in_words]).try(:[], 1)
163
196
  if item.send(attrib)
164
197
  if item.send(attrib) < DateTime.now
165
198
  "#{helpers.time_ago_in_words(item.send(attrib))} ago"
@@ -193,11 +226,24 @@ module ExpressAdmin
193
226
  end
194
227
 
195
228
  class Column
196
- attr :name, :title, :accessor
229
+ attr :name, :title, :accessor, :table_column
197
230
  def initialize(accessor, title = nil)
198
231
  @name = accessor.kind_of?(Symbol) ? accessor.to_s.underscore : title.titleize.gsub(/\s+/,'').underscore
199
232
  @accessor = accessor
200
233
  @title = title || @name.titleize
234
+ @table_column = table_column
235
+ end
236
+
237
+ def table_column
238
+ if accessor.kind_of?(Symbol)
239
+ COLUMN_REGEX_LIST.map do |key, value|
240
+ accessor_match(COLUMN_REGEX_LIST[key])
241
+ end.compact.first
242
+ end
243
+ end
244
+
245
+ def accessor_match(regex)
246
+ accessor.to_s.match(regex).try(:[], 1)
201
247
  end
202
248
  end
203
249
 
@@ -1,9 +1,5 @@
1
1
  module ExpressAdmin
2
2
  module AdminHelper
3
-
4
- # TODO move this out
5
- #include ExpressMedia::Admin::MediaHelper if Kernel.const_defined?("ExpressMedia::Engine")
6
-
7
3
  def title_partial
8
4
  (ExpressAdmin::Engine.config.title_partial rescue nil) || 'shared/express_admin/title'
9
5
  end
@@ -51,13 +51,10 @@ div(class: 'grid-container') {
51
51
  }
52
52
 
53
53
  hr
54
- div(class: 'services container') {
55
- a(href: "/users/auth/appexpress") { "Sign in with appexpress" }
56
- br
57
- }
54
+ oauth_sign_in_links(Devise.omniauth_providers)
58
55
  }
59
56
  }
60
57
  }
61
58
  }
62
59
  }
63
- }
60
+ }
@@ -26,19 +26,19 @@ components.each {|component| require component }
26
26
  module ExpressAdmin
27
27
  class Engine < ::Rails::Engine
28
28
 
29
- initializer :assets do |config|
30
- engine_assets_path = File.join(File.dirname(__FILE__), '..', '..', 'app', 'assets')
31
- all_assets = Dir.glob File.join(engine_assets_path, 'stylesheets', '**', '*.css*')
32
- all_assets += Dir.glob File.join(engine_assets_path, 'javascripts', '**', '*.js*')
33
- all_assets.each {|path| path.gsub!("#{engine_assets_path}/stylesheets/", '')}
34
- all_assets.each {|path| path.gsub!("#{engine_assets_path}/javascripts/", '')}
35
- all_assets.each {|path| path.gsub!("#{engine_assets_path}/fonts/", '')}
36
- all_assets.each {|path| path.gsub!(/.(scss|coffee)$/, '')}
37
- Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'fonts')
38
- Rails.application.config.assets.precompile << /\.(?:svg|eot|woff|ttf|png|jpg|jpeg|gif)$/
39
- Rails.application.config.assets.precompile << /\.(?:mp4|webm|mp3)$/
40
- Rails.application.config.assets.precompile += all_assets
41
- end
29
+ initializer :assets do |config|
30
+ engine_assets_path = File.join(File.dirname(__FILE__), '..', '..', 'app', 'assets')
31
+ all_assets = Dir.glob File.join(engine_assets_path, 'stylesheets', '**', '*.css*')
32
+ all_assets += Dir.glob File.join(engine_assets_path, 'javascripts', '**', '*.js*')
33
+ all_assets.each {|path| path.gsub!("#{engine_assets_path}/stylesheets/", '')}
34
+ all_assets.each {|path| path.gsub!("#{engine_assets_path}/javascripts/", '')}
35
+ all_assets.each {|path| path.gsub!("#{engine_assets_path}/fonts/", '')}
36
+ all_assets.each {|path| path.gsub!(/.(scss|coffee)$/, '')}
37
+ Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'fonts')
38
+ Rails.application.config.assets.precompile << /\.(?:svg|eot|woff|ttf|png|jpg|jpeg|gif)$/
39
+ Rails.application.config.assets.precompile << /\.(?:mp4|webm|mp3)$/
40
+ Rails.application.config.assets.precompile += all_assets
41
+ end
42
42
 
43
43
  def all_rails_engines
44
44
  Rails.application.eager_load!
@@ -59,4 +59,4 @@ module ExpressAdmin
59
59
  class Railtie < ::Rails::Railtie
60
60
  config.app_generators.template_engine :et
61
61
  end
62
- end
62
+ end
@@ -238,13 +238,25 @@ module ExpressAdmin
238
238
  resource_class
239
239
  end
240
240
  scope = search?(scope)
241
- self.instance_variable_set(collection_ivar, instance_variable(scope))
241
+ scope = instance_variable(scope)
242
+ scope = params[:asc] || params[:desc] ? sort_table(scope) : scope
243
+ self.instance_variable_set(collection_ivar, scope)
242
244
  end
243
245
 
244
246
  def instance_variable(scope)
245
247
  scope.kind_of?(Array) ? scope : scope.all
246
248
  end
247
249
 
250
+ def sort_table(scope)
251
+ if params.keys.include?('asc')
252
+ scope.sort{ |a, b| a.send(params[:asc]) <=> b.send(params[:asc])}
253
+ elsif params.keys.include?('desc')
254
+ scope.sort{ |a, b| b.send(params[:desc]) <=> a.send(params[:desc])}
255
+ else
256
+ scope
257
+ end
258
+ end
259
+
248
260
  def search?(scope)
249
261
  params[:search_string].present? ? search(scope) : scope
250
262
  end
@@ -1,3 +1,3 @@
1
1
  module ExpressAdmin
2
- VERSION = "1.7.8"
2
+ VERSION = "1.7.9"
3
3
  end
Binary file
@@ -100,6 +100,42 @@ module Components
100
100
  assert_match /class="created_at.*less than a minute ago/, fragment
101
101
  end
102
102
 
103
- end
103
+ test 'assign column to be sortable which generates a header link' do
104
+ fragment = arbre(widget: Widget.first, widgets: Widget.all){
105
+ smart_table(:widgets,
106
+ sortable: ['Created'],
107
+ columns: {
108
+ 'Created' => :created_at_in_words
109
+ })
110
+ }
111
+
112
+ assert_match "href=\"widgets?asc=created_at\"", fragment
113
+ end
114
+
115
+ test 'assign unknown column to sortable which should not generate a header link' do
116
+ fragment = arbre(widget: Widget.first, widgets: Widget.all){
117
+ smart_table(:widgets,
118
+ sortable: ['Ghost'],
119
+ columns: {
120
+ 'Created' => :created_at_in_words
121
+ })
122
+ }
104
123
 
124
+ assert_no_match "href=\"widgets?asc=ghost\"", fragment
125
+ end
126
+
127
+ test 'assign column with proc value to sortable which should not generate header link' do
128
+ fragment = arbre(widget: Widget.first, widgets: Widget.all){
129
+ smart_table(:widgets,
130
+ sortable: ['This column in will not be a link'],
131
+ columns: {
132
+ 'Created' => :created_at_in_words,
133
+ "This column will not be a link" => -> (widget) { widget.column2.upcase },
134
+ })
135
+ }
136
+
137
+ assert_match 'This column will not be a link', fragment
138
+ assert_no_match 'href=', fragment
139
+ end
140
+ end
105
141
  end