super 0.0.16 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/super/application.js +1401 -125
  3. data/app/assets/stylesheets/super/application.css +90702 -90026
  4. data/app/controllers/super/application_controller.rb +49 -6
  5. data/app/controllers/super/substructure_controller.rb +121 -37
  6. data/app/views/layouts/super/application.html.erb +1 -1
  7. data/app/views/super/application/_batch_button.html.erb +12 -0
  8. data/app/views/super/application/_batch_checkbox.csv.erb +1 -0
  9. data/app/views/super/application/_batch_checkbox.html.erb +5 -0
  10. data/app/views/super/application/_batch_form.html.erb +3 -0
  11. data/app/views/super/application/_collection_header.html.erb +7 -6
  12. data/app/views/super/application/_csv_button.html.erb +25 -0
  13. data/app/views/super/application/_display_actions.html.erb +2 -2
  14. data/app/views/super/application/_filter.html.erb +62 -2
  15. data/app/views/super/application/_layout.html.erb +13 -14
  16. data/app/views/super/application/_link.html.erb +8 -0
  17. data/app/views/super/application/_member_header.html.erb +7 -7
  18. data/app/views/super/application/_pagination.html.erb +1 -0
  19. data/app/views/super/application/_site_header.html.erb +4 -4
  20. data/app/views/super/application/_sort_expression.html.erb +2 -2
  21. data/app/views/super/application/_view_chain.html.erb +5 -0
  22. data/app/views/super/application/index.csv.erb +14 -0
  23. data/config/locales/en.yml +8 -0
  24. data/frontend/super-frontend/dist/application.css +90702 -90026
  25. data/frontend/super-frontend/dist/application.js +1401 -125
  26. data/frontend/super-frontend/dist/package.json +13 -0
  27. data/lib/generators/super/webpacker/USAGE +1 -7
  28. data/lib/generators/super/webpacker/templates/pack_super_application.js.tt +2 -0
  29. data/lib/generators/super/webpacker/webpacker_generator.rb +10 -6
  30. data/lib/super/cheat.rb +1 -0
  31. data/lib/super/display/guesser.rb +1 -1
  32. data/lib/super/display/schema_types.rb +6 -1
  33. data/lib/super/display.rb +2 -1
  34. data/lib/super/engine.rb +5 -0
  35. data/lib/super/error.rb +12 -0
  36. data/lib/super/filter/form_object.rb +74 -48
  37. data/lib/super/filter/guesser.rb +2 -0
  38. data/lib/super/filter/operator.rb +90 -64
  39. data/lib/super/filter/schema_types.rb +63 -80
  40. data/lib/super/filter.rb +1 -1
  41. data/lib/super/form/builder.rb +6 -3
  42. data/lib/super/form/field_transcript.rb +43 -0
  43. data/lib/super/form/guesser.rb +1 -1
  44. data/lib/super/form/schema_types.rb +11 -20
  45. data/lib/super/layout.rb +9 -33
  46. data/lib/super/link.rb +2 -7
  47. data/lib/super/link_builder.rb +0 -4
  48. data/lib/super/packaged_asset.rb +49 -0
  49. data/lib/super/pagination.rb +2 -1
  50. data/lib/super/reorderable_hash.rb +88 -0
  51. data/lib/super/reset.rb +20 -1
  52. data/lib/super/schema.rb +4 -0
  53. data/lib/super/version.rb +1 -1
  54. data/lib/super/view_chain.rb +25 -0
  55. data/lib/super.rb +4 -0
  56. metadata +46 -9
  57. data/app/views/super/application/_filter_type_select.html.erb +0 -21
  58. data/app/views/super/application/_filter_type_text.html.erb +0 -18
  59. data/app/views/super/application/_filter_type_timestamp.html.erb +0 -24
  60. data/app/views/super/application/_form_field_select.html.erb +0 -1
  61. data/lib/generators/super/webpacker/templates/pack_super_application.js.erb.tt +0 -2
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@superadministration/super",
3
+ "description": "The frontend code for Super, a Rails admin framework",
4
+ "homepage": "https://github.com/zachahn/super",
5
+ "license": "LGPL-3.0-only",
6
+ "main": "application.js",
7
+ "files": [
8
+ "application.js",
9
+ "application.css",
10
+ "package.json"
11
+ ],
12
+ "version": "0.19.0"
13
+ }
@@ -1,15 +1,9 @@
1
1
  Description:
2
2
  Creates the necessary files for assets to be served under Webpacker
3
3
 
4
- Webpacker support requires that you install ERB support for Webpacker.
5
-
6
- Installing ERB on webpacker is usually done by running the command:
7
- `rails webpacker:install:erb`
8
-
9
4
  Example:
10
- rails webpacker:install:erb # if you hadn't set up ERB already
11
5
  rails generate super:webpacker
12
6
 
13
7
  This will:
14
- Create app/javascript/packs/super/application.js.erb
8
+ Create app/javascript/packs/super/application.js
15
9
  Modify config/initializers/super.rb
@@ -0,0 +1,2 @@
1
+ import Super from "@superadministration/super";
2
+ import "@superadministration/super/application.css";
@@ -7,12 +7,12 @@ module Super
7
7
  def copy_the_pack_file
8
8
  path =
9
9
  if Gem::Dependency.new("webpacker", ">= 6.0.0.beta2", "!= 6.0.0.pre1", "!= 6.0.0.pre2").matching_specs.any?
10
- "app/packs/entrypoints/super/application.js.erb"
10
+ "app/packs/entrypoints/super/application.js"
11
11
  else
12
- "app/javascript/packs/super/application.js.erb"
12
+ "app/javascript/packs/super/application.js"
13
13
  end
14
14
 
15
- template("pack_super_application.js.erb", path)
15
+ template("pack_super_application.js", path)
16
16
  end
17
17
 
18
18
  def set_asset_handler_to_webpacker
@@ -24,9 +24,13 @@ module Super
24
24
  )
25
25
  end
26
26
 
27
- def remind_about_erb
28
- say "Make sure ERB is set up for Webpacker!", :bold
29
- say "Run if needed: bundle exec rails webpacker:install:erb"
27
+ def install_via_yarn
28
+ super_root = Pathname.new(__dir__).join("../../../..")
29
+ if super_root.join("dummy_path.rb").file?
30
+ run "yarn add file:#{super_root.join("frontend/super-frontend/dist")}"
31
+ else
32
+ run "yarn add @superadministration/super@#{Super::VERSION} --exact"
33
+ end
30
34
  end
31
35
  end
32
36
  end
data/lib/super/cheat.rb CHANGED
@@ -8,6 +8,7 @@ module Super
8
8
  paths
9
9
  .map { |f| File.read(File.expand_path(f, __dir__)) }
10
10
  .flat_map { |content| content.scan(/^\s+(?:helper_method )?def .*$/) }
11
+ .reject { |method| method =~ /\bdef self\./ }
11
12
  .map { |method| method.strip.sub(/^(?:helper_method )?def /, "#") }
12
13
 
13
14
  puts "== Super::ApplicationController"
@@ -30,7 +30,7 @@ module Super
30
30
  when :time
31
31
  @type.time
32
32
  else
33
- @type.text
33
+ @type.string
34
34
  end
35
35
  end
36
36
  end
@@ -129,8 +129,13 @@ module Super
129
129
  real(:column, &transform_block)
130
130
  end
131
131
 
132
+ def batch
133
+ real do |value|
134
+ Partial.new("batch_checkbox", locals: { value: value })
135
+ end
136
+ end
137
+
132
138
  def string; real(&:to_s); end
133
- alias text string
134
139
 
135
140
  def timestamp; real(&:to_s); end
136
141
  def time; real { |value| value.strftime("%H:%M:%S") }; end
data/lib/super/display.rb CHANGED
@@ -32,10 +32,11 @@ module Super
32
32
  yield(@fields, @schema_types)
33
33
  end
34
34
 
35
- def apply(action:)
35
+ def apply(action:, format:)
36
36
  @action_inquirer = action
37
37
  return self if !@action_inquirer.index?
38
38
  return self if @schema_types.actions_called?
39
+ return self if !format.html?
39
40
  @fields[:actions] = @schema_types.actions
40
41
  self
41
42
  end
data/lib/super/engine.rb CHANGED
@@ -15,6 +15,11 @@ module Super
15
15
  Super::Plugin::Registry.controller.ordered do |klass, method_name|
16
16
  Super::ApplicationController.public_send(method_name, klass)
17
17
  end
18
+
19
+ Super::PackagedAsset.warning_message =
20
+ if !Super::PackagedAsset.version_matches_gem?(Rails.root.join("package.json"))
21
+ I18n.t("super.mismatching_package_json_gemfile_versions")
22
+ end
18
23
  end
19
24
  end
20
25
  end
data/lib/super/error.rb CHANGED
@@ -28,11 +28,23 @@ module Super
28
28
  # a more specific error
29
29
  class Initalization < Error; end
30
30
  class ArgumentError < Error; end
31
+ class AlreadyRegistered < Error; end
32
+ class AlreadyTranscribed < Error; end
33
+ class NotImplementedError < Error; end
31
34
 
32
35
  class Enum < Error
33
36
  class ImpossibleValue < Enum; end
34
37
  class ArgumentError < Enum; end
35
38
  class UndefinedCase < Enum; end
36
39
  end
40
+
41
+ class ReorderableHash < Error
42
+ class DuplicateKey < ReorderableHash; end
43
+ class KeyError < ReorderableHash; end
44
+ end
45
+
46
+ class ViewChain < Error
47
+ class ChainAlreadyStarted < ViewChain; end
48
+ end
37
49
  end
38
50
  end
@@ -3,58 +3,95 @@
3
3
  module Super
4
4
  class Filter
5
5
  class FormObject
6
- class FilterFormField
7
- def initialize(humanized_field_name:, field_name:, type:, params:)
8
- @humanized_field_name = humanized_field_name
6
+ class AttributeForm
7
+ def initialize(model:, field_name:, operators:, params:)
8
+ @model = model
9
9
  @field_name = field_name
10
- @field_type = type
11
- @params = params
10
+ @operators = operators
11
+ @params = params || {}
12
+ end
13
+
14
+ attr_reader :model
15
+ attr_reader :field_name
16
+ attr_reader :operators
17
+ attr_reader :params
18
+
19
+ def each_operator
20
+ return enum_for(:each_operator) if !block_given?
21
+
22
+ @operators.each do |operator|
23
+ operator_form = OperatorForm.new(
24
+ operator: operator,
25
+ params: @params[operator.identifier]
26
+ )
27
+
28
+ yield(operator_form)
29
+ end
30
+ end
31
+
32
+ def humanized_attribute_name
33
+ @model.human_attribute_name(@field_name)
34
+ end
35
+ end
36
+
37
+ class OperatorForm
38
+ NULLARY = :_apply
39
+
40
+ def initialize(operator:, params:)
41
+ @operator = operator
42
+ @params = params || {}
43
+ query_parameter_keys = operator.query_parameter_keys
44
+ query_parameter_keys = [NULLARY] if query_parameter_keys.empty?
12
45
  @specified_values =
13
- type.q
14
- .map do |query_field_name|
15
- [
16
- query_field_name,
17
- (params || {})[query_field_name],
18
- ]
19
- end
20
- .to_h
46
+ query_parameter_keys
47
+ .map { |key| [key, @params[key].presence&.strip] }
48
+ .to_h
21
49
 
22
50
  @specified_values.each do |key, value|
23
51
  define_singleton_method(key) { value }
24
52
  end
25
53
  end
26
54
 
27
- attr_reader :humanized_field_name
28
- attr_reader :field_name
29
- attr_reader :field_type
55
+ def specified?
56
+ @specified_values.any? { |_key, value| value }
57
+ end
58
+
59
+ attr_reader :operator
30
60
  attr_reader :specified_values
31
61
 
32
- def op
33
- (@params || {})[:op]
62
+ def identifier
63
+ @operator.identifier
34
64
  end
35
65
 
36
- def operators
37
- @field_type.operators
38
- .map { |o| [o.name, o.identifier] }
39
- .to_h
40
- end
66
+ def each_field
67
+ return enum_for(:each_field) if !block_given?
41
68
 
42
- def to_partial_path
43
- @field_type.to_partial_path
69
+ @specified_values.each do |key, _value|
70
+ yield(key)
71
+ end
44
72
  end
45
73
  end
46
74
 
47
75
  def initialize(model:, params:, schema:)
48
76
  @model = model
49
- @params = params
77
+ @params = params || {}
50
78
  @schema = schema
51
79
 
52
80
  @form_fields = {}
53
81
  end
54
82
 
55
- def each_field
56
- @schema.fields.each do |field_name, _field_type|
57
- yield(form_field_for(field_name))
83
+ def each_attribute
84
+ return enum_for(:each_attribute) if !block_given?
85
+
86
+ @schema.fields.each do |field_name, field_operators|
87
+ attribute_form = AttributeForm.new(
88
+ model: @model,
89
+ field_name: field_name,
90
+ operators: field_operators,
91
+ params: @params[field_name]
92
+ )
93
+
94
+ yield(attribute_form)
58
95
  end
59
96
  end
60
97
 
@@ -63,32 +100,21 @@ module Super
63
100
  end
64
101
 
65
102
  def apply_changes(relation)
66
- each_field do |form_field|
67
- next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
68
- next if !Super::Filter::Operator.registry.key?(form_field.op)
103
+ each_attribute do |attribute_form|
104
+ attribute_form.each_operator do |operator_form|
105
+ next if operator_form.specified_values.values.map(&:to_s).map(&:presence).none?
69
106
 
70
- operator = Super::Filter::Operator.registry[form_field.op]
71
- updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
107
+ operator_behavior = operator_form.operator.behavior
108
+ updated_relation = operator_behavior.call(relation, attribute_form.field_name, **operator_form.specified_values)
72
109
 
73
- if updated_relation.is_a?(ActiveRecord::Relation)
74
- relation = updated_relation
110
+ if updated_relation.is_a?(ActiveRecord::Relation)
111
+ relation = updated_relation
112
+ end
75
113
  end
76
114
  end
77
115
 
78
116
  relation
79
117
  end
80
-
81
- private
82
-
83
- def form_field_for(field_name)
84
- @form_fields[field_name] ||=
85
- FilterFormField.new(
86
- humanized_field_name: @model.human_attribute_name(field_name),
87
- field_name: field_name,
88
- type: @schema.fields[field_name],
89
- params: (@params || {})[field_name]
90
- )
91
- end
92
118
  end
93
119
  end
94
120
  end
@@ -23,6 +23,8 @@ module Super
23
23
  case type
24
24
  when :datetime
25
25
  @type.timestamp
26
+ when :boolean
27
+ @type.boolean
26
28
  else
27
29
  @type.text
28
30
  end
@@ -2,100 +2,126 @@
2
2
 
3
3
  module Super
4
4
  class Filter
5
- module Operator
6
- class Definition
7
- def initialize(identifier, name, filter)
8
- @identifier = identifier
9
- @name = name
10
- @filter = filter
11
- end
12
-
13
- attr_reader :identifier
14
- attr_reader :name
15
-
16
- def filter(*args)
17
- @filter.call(args)
18
- end
19
- end
20
-
5
+ class Operator
21
6
  class << self
22
7
  def registry
23
8
  @registry ||= {}
24
9
  end
25
10
 
26
- def range_defaults
27
- [
28
- registry["between"],
29
- ]
11
+ def [](key)
12
+ registry.fetch(key.to_s)
30
13
  end
31
14
 
32
- def select_defaults
33
- [
34
- registry["eq"],
35
- registry["neq"],
36
- ]
15
+ def register(identifier, operator)
16
+ identifier = identifier.to_s
17
+ if registry.key?(identifier)
18
+ raise Error::AlreadyRegistered, "Already registered: #{identifier}"
19
+ end
20
+
21
+ registry[identifier] = operator
37
22
  end
38
23
 
39
- def text_defaults
40
- [
41
- registry["eq"],
42
- registry["neq"],
43
- registry["contain"],
44
- registry["ncontain"],
45
- registry["start"],
46
- registry["end"],
47
- ]
24
+ def define(identifier, display, &block)
25
+ operator = new(identifier, display, &block).freeze
26
+ register(identifier, operator)
27
+ operator
48
28
  end
29
+ end
49
30
 
50
- def define(identifier, name, &filter)
51
- identifier = identifier.to_s
52
- name = name.to_s
31
+ def initialize(identifier, display, &behavior)
32
+ @identifier = identifier.to_s
33
+ @humanized_operator_name = display
34
+ self.behavior = behavior
35
+ end
36
+
37
+ def behavior=(behavior)
38
+ behavior_params = behavior.parameters
39
+ if behavior_params.size < 2
40
+ raise Error::ArgumentError, "Operator behavior must include `column_name` and `relation`"
41
+ end
42
+ if behavior_params[0][0] != :req && behavior_params[0][0] != :opt
43
+ raise Error::ArgumentError, "First argument `relation` must be a required, positional argument"
44
+ end
45
+ if behavior_params[1][0] != :req && behavior_params[1][0] != :opt
46
+ raise Error::ArgumentError, "Second argument `column_name` must be a required, positional argument"
47
+ end
48
+ if !behavior_params[2..-1].all? { |(type, _name)| type == :keyreq }
49
+ raise Error::ArgumentError, "All query parameter keys must be required, keyword arguments"
50
+ end
51
+ @behavior = behavior
52
+ @query_parameter_keys = behavior_params[2..-1].map(&:last)
53
+ end
53
54
 
54
- definition = Definition.new(identifier, name, filter)
55
+ attr_reader :identifier
56
+ attr_reader :query_parameter_keys
57
+ attr_reader :humanized_operator_name
55
58
 
56
- registry[identifier] = definition
59
+ def behavior(&block)
60
+ self.behavior = block if block_given?
61
+ @behavior
62
+ end
57
63
 
58
- define_singleton_method(identifier) do
59
- registry[identifier]
60
- end
61
- end
64
+ define("eq", "Equals") do |relation, field, q:|
65
+ relation.where(field => q)
62
66
  end
63
67
 
64
- define("eq", "equals") do |relation, field, query|
65
- relation.where(field => query)
68
+ define("neq", "Doesn't equal") do |relation, field, q:|
69
+ relation.where.not(field => q)
66
70
  end
67
71
 
68
- define("neq", "doesn't equal") do |relation, field, query|
69
- relation.where.not(field => query)
72
+ define("null", "Is NULL") do |relation, field|
73
+ relation.where(field => nil)
70
74
  end
71
75
 
72
- define("contain", "contains") do |relation, field, query|
73
- query = "%#{Compatability.sanitize_sql_like(query)}%"
74
- relation.where("#{field} LIKE ?", "%#{query}%")
76
+ define("nnull", "Isn't NULL") do |relation, field|
77
+ relation.where.not(field => nil)
75
78
  end
76
79
 
77
- define("ncontain", "doesn't contain") do |relation, field, query|
78
- query = "%#{Compatability.sanitize_sql_like(query)}%"
79
- relation.where("#{field} NOT LIKE ?", query)
80
+ define("true", "Is true") do |relation, field|
81
+ relation.where(field => true)
82
+ end
83
+
84
+ define("false", "Is false") do |relation, field|
85
+ relation.where(field => false)
86
+ end
87
+
88
+ define("empty", "Is empty") do |relation, field|
89
+ relation.where(field => "")
80
90
  end
81
91
 
82
- define("start", "starts with") do |relation, field, query|
83
- query = "#{Compatability.sanitize_sql_like(query)}%"
84
- relation.where("#{field} LIKE ?", query)
92
+ define("nempty", "Isn't empty") do |relation, field|
93
+ relation.where.not(field => "")
85
94
  end
86
95
 
87
- define("end", "ends with") do |relation, field, query|
88
- query = "%#{Compatability.sanitize_sql_like(query)}"
89
- relation.where("#{field} LIKE ?", query)
96
+ define("blank", "Is blank") do |relation, field|
97
+ relation.where(field => [nil, ""])
98
+ end
99
+
100
+ define("nblank", "Isn't blank") do |relation, field|
101
+ relation.where.not(field => [nil, ""])
102
+ end
103
+
104
+ define("contain", "Contains") do |relation, field, q:|
105
+ query = "%#{Compatability.sanitize_sql_like(q)}%"
106
+ if relation.connection.adapter_name == "PostgreSQL"
107
+ relation.where("#{field} ILIKE ?", query)
108
+ else
109
+ relation.where("#{field} LIKE ?", query)
110
+ end
111
+ end
112
+
113
+ define("ncontain", "Doesn't contain") do |relation, field, q:|
114
+ query = "%#{Compatability.sanitize_sql_like(q)}%"
115
+ relation.where("#{field} NOT LIKE ?", query)
90
116
  end
91
117
 
92
- define("between", "between") do |relation, field, query0, query1|
93
- if query0.present?
94
- relation = relation.where("#{field} >= ?", query0)
118
+ define("between", "Between") do |relation, field, q0:, q1:|
119
+ if q0.present?
120
+ relation = relation.where("#{field} >= ?", q0)
95
121
  end
96
122
 
97
- if query1.present?
98
- relation = relation.where("#{field} <= ?", query1)
123
+ if q1.present?
124
+ relation = relation.where("#{field} <= ?", q1)
99
125
  end
100
126
 
101
127
  relation