super 0.0.0 → 0.0.5

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +10 -0
  3. data/LICENSE +165 -0
  4. data/README.md +66 -18
  5. data/Rakefile +3 -1
  6. data/app/assets/config/super_manifest.js +2 -0
  7. data/app/assets/javascripts/super/application.js +3618 -0
  8. data/app/assets/stylesheets/super/application.css +98584 -0
  9. data/app/controllers/super/application_controller.rb +103 -0
  10. data/app/helpers/super/application_helper.rb +32 -0
  11. data/app/views/layouts/super/application.html.erb +39 -0
  12. data/app/views/super/application/_flash.html.erb +17 -0
  13. data/app/views/super/application/_form.html.erb +17 -0
  14. data/app/views/super/application/_form_field__destroy.html.erb +9 -0
  15. data/app/views/super/application/_form_field_select.html.erb +23 -0
  16. data/app/views/super/application/_form_field_text.html.erb +13 -0
  17. data/app/views/super/application/_form_fieldset.html.erb +8 -0
  18. data/app/views/super/application/_form_has_many.html.erb +21 -0
  19. data/app/views/super/application/_form_has_one.html.erb +11 -0
  20. data/app/views/super/application/_form_inline_errors.html.erb +10 -0
  21. data/app/views/super/application/_index.html.erb +45 -0
  22. data/app/views/super/application/_resource_header.html.erb +16 -0
  23. data/app/views/super/application/_resources_header.html.erb +16 -0
  24. data/app/views/super/application/_show.html.erb +10 -0
  25. data/app/views/super/application/_super_layout.html.erb +34 -0
  26. data/app/views/super/application/_super_panel.html.erb +11 -0
  27. data/app/views/super/application/edit.html.erb +6 -0
  28. data/app/views/super/application/index.html.erb +6 -0
  29. data/app/views/super/application/new.html.erb +6 -0
  30. data/app/views/super/application/nothing.html.erb +0 -0
  31. data/app/views/super/application/show.html.erb +6 -0
  32. data/app/views/super/feather/README.md +32 -0
  33. data/app/views/super/feather/_chevron_down.svg +1 -0
  34. data/docs/README.md +6 -0
  35. data/docs/controls.md +39 -0
  36. data/docs/faq.md +44 -0
  37. data/docs/quick_start.md +45 -0
  38. data/docs/webpacker.md +17 -0
  39. data/docs/yard_customizations.rb +41 -0
  40. data/frontend/super-frontend/build.js +36 -0
  41. data/frontend/super-frontend/dist/application.css +98584 -0
  42. data/frontend/super-frontend/dist/application.js +3618 -0
  43. data/frontend/super-frontend/package.json +21 -0
  44. data/frontend/super-frontend/postcss.config.js +6 -0
  45. data/frontend/super-frontend/src/javascripts/super/application.ts +18 -0
  46. data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +21 -0
  47. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +1 -0
  48. data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +15 -0
  49. data/frontend/super-frontend/src/stylesheets/super/application.css +77 -0
  50. data/frontend/super-frontend/tailwind.config.js +9 -0
  51. data/frontend/super-frontend/tsconfig.json +13 -0
  52. data/frontend/super-frontend/yarn.lock +5540 -0
  53. data/lib/generators/super/install/USAGE +34 -0
  54. data/lib/generators/super/install/install_generator.rb +46 -0
  55. data/lib/generators/super/install/templates/base_controller.rb.tt +2 -0
  56. data/lib/generators/super/install/templates/initializer.rb.tt +5 -0
  57. data/lib/generators/super/resource/USAGE +8 -0
  58. data/lib/generators/super/resource/resource_generator.rb +52 -0
  59. data/lib/generators/super/resource/templates/resources_controller.rb.tt +45 -0
  60. data/lib/generators/super/webpacker/USAGE +14 -0
  61. data/lib/generators/super/webpacker/templates/pack_super_application.js.erb.tt +2 -0
  62. data/lib/generators/super/webpacker/webpacker_generator.rb +25 -0
  63. data/lib/super.rb +22 -4
  64. data/lib/super/action_inquirer.rb +101 -0
  65. data/lib/super/assets.rb +63 -0
  66. data/lib/super/compatibility.rb +13 -0
  67. data/lib/super/configuration.rb +103 -0
  68. data/lib/super/controls.rb +120 -0
  69. data/lib/super/display.rb +9 -0
  70. data/lib/super/display/schema_types.rb +40 -0
  71. data/lib/super/engine.rb +6 -0
  72. data/lib/super/error.rb +18 -0
  73. data/lib/super/form/schema_types.rb +115 -0
  74. data/lib/super/layout.rb +19 -0
  75. data/lib/super/link.rb +87 -0
  76. data/lib/super/navigation/automatic.rb +71 -0
  77. data/lib/super/pagination.rb +70 -0
  78. data/lib/super/panel.rb +17 -0
  79. data/lib/super/partial.rb +11 -0
  80. data/lib/super/plugin.rb +89 -0
  81. data/lib/super/schema.rb +73 -0
  82. data/lib/super/step.rb +36 -0
  83. data/lib/super/version.rb +1 -1
  84. data/lib/super/view_helper.rb +43 -0
  85. metadata +215 -14
  86. data/LICENSE.txt +0 -40
@@ -0,0 +1,103 @@
1
+ module Super
2
+ # @yield [Configuration]
3
+ # @return [Configuration]
4
+ def self.configuration
5
+ @configuration ||= Configuration.new
6
+
7
+ if block_given?
8
+ yield(@configuration)
9
+ end
10
+
11
+ @configuration
12
+ end
13
+
14
+ # Allows setting global configuration
15
+ #
16
+ # ```ruby
17
+ # Super.configuration do |c|
18
+ # c.title = "My Admin Site"
19
+ # end
20
+ # ```
21
+ class Configuration
22
+ module ConfigurationLogic # @api private
23
+ def self.included(base)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ def initialize
28
+ self.class.defaults.each do |key, value|
29
+ if value.respond_to?(:call)
30
+ value = value.call
31
+ end
32
+
33
+ public_send("#{key}=", value)
34
+ end
35
+ end
36
+
37
+ def configured?(attr)
38
+ instance_variable_defined?("@#{attr}")
39
+ end
40
+
41
+ module ClassMethods
42
+ def defaults
43
+ @defaults ||= {}
44
+ end
45
+
46
+ def wraps
47
+ @wraps ||= {}
48
+ end
49
+
50
+ def configure(attr, wrap: nil, enum: nil, **kwargs)
51
+ if kwargs.key?(:default)
52
+ defaults[attr] = kwargs[:default]
53
+ end
54
+
55
+ define_method(attr) do
56
+ if !configured?(attr)
57
+ raise Error::UnconfiguredConfiguration, "unconfigured: #{attr}"
58
+ end
59
+
60
+ result = instance_variable_get("@#{attr}")
61
+
62
+ if wrap.nil?
63
+ result
64
+ else
65
+ wrap.call(result)
66
+ end
67
+ end
68
+
69
+ define_method("#{attr}=") do |value|
70
+ if enum.is_a?(Array)
71
+ if !enum.include?(value)
72
+ raise Error::InvalidConfiguration,
73
+ "tried to set `#{attr}` to `#{value.inspect}`, " \
74
+ "expected: #{enum.join(", ")}"
75
+ end
76
+ end
77
+
78
+ instance_variable_set("@#{attr}", value)
79
+ value
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ include ConfigurationLogic
86
+
87
+ # @!attribute [rw]
88
+ configure :title
89
+ # @!attribute [rw]
90
+ configure :index_resources_per_page, default: 20
91
+ # @!attribute [rw]
92
+ configure :controller_namespace, default: "admin"
93
+ # @!attribute [rw]
94
+ configure :route_namespace, default: :admin, wrap: -> (val) { [val].flatten }
95
+ # @!attribute [rw]
96
+ configure :asset_handler, default: -> { Super::Assets.auto }
97
+
98
+ # @api private
99
+ def path_parts(*parts)
100
+ route_namespace + parts
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,120 @@
1
+ module Super
2
+ # A wrapper around the per-controller Controls classes. This class often
3
+ # directly delegates to the per-controller classes, but it can also provide
4
+ # some default implementation.
5
+ class Controls
6
+ def initialize(actual)
7
+ @actual = actual
8
+ end
9
+
10
+ attr_reader :actual
11
+
12
+ # This is an optional method
13
+ #
14
+ # @return [String]
15
+ def title
16
+ if @actual.respond_to?(:title)
17
+ return @actual.title
18
+ end
19
+
20
+ model.name.pluralize
21
+ end
22
+
23
+ # Specifies the model. This is a required method
24
+ #
25
+ # @return [ActiveRecord::Base]
26
+ def model
27
+ @actual.model
28
+ end
29
+
30
+ # Configures the actions linked to on the index page. This is an optional
31
+ # method
32
+ #
33
+ # @param params [ActionController::Parameters]
34
+ # @param action [ActionInquirer]
35
+ # @return [Array<Link>]
36
+ def resources_actions(params:, action:)
37
+ actions =
38
+ if @actual.respond_to?(:resources_actions)
39
+ @actual.resources_actions(params: params, action: action)
40
+ else
41
+ [:new]
42
+ end
43
+
44
+ actions.map do |link|
45
+ link = Link.resolve(link)
46
+
47
+ link.call(params: params)
48
+ end
49
+ end
50
+
51
+ # Configures the actions linked to on the show page as well as each row of
52
+ # the table on the index page. This is an optional method
53
+ #
54
+ # @param resource [ActiveRecord::Base]
55
+ # @param params [ActionController::Parameters]
56
+ # @param action [ActionInquirer]
57
+ # @return [Array<Link>]
58
+ def resource_actions(resource, params:, action:)
59
+ actions =
60
+ if @actual.respond_to?(:resource_actions)
61
+ @actual.resource_actions(resource, params: params, action: action)
62
+ else
63
+ if action.show?
64
+ [:edit, :destroy]
65
+ elsif action.edit?
66
+ [:show, :destroy]
67
+ else
68
+ [:show, :edit, :destroy]
69
+ end
70
+ end
71
+
72
+ actions.map do |link|
73
+ link = Link.resolve(link)
74
+
75
+ link.call(resource, params: params)
76
+ end
77
+ end
78
+
79
+ # Configures what database records are visible on load. This is an optional
80
+ # method, it defaults to "`all`" methods
81
+ #
82
+ # @param action [ActionInquirer]
83
+ # @return [ActiveRecord::Relation]
84
+ def scope(action:)
85
+ if @actual.respond_to?(:scope)
86
+ return @actual.scope(action: action)
87
+ end
88
+
89
+ model.all
90
+ end
91
+
92
+ # Configures which parameters could be written to the database. This is a
93
+ # required method
94
+ #
95
+ # @param params [ActionController::Parameters]
96
+ # @param action [ActionInquirer]
97
+ # @return [ActionController::Parameters]
98
+ def permitted_params(params, action:)
99
+ @actual.permitted_params(params, action: action)
100
+ end
101
+
102
+ # Configures the fields that are displayed on the index and show actions.
103
+ # This is a required method
104
+ #
105
+ # @param action [ActionInquirer]
106
+ # @return [Schema]
107
+ def display_schema(action:)
108
+ @actual.display_schema(action: action)
109
+ end
110
+
111
+ # Configures the editable fields on the new and edit actions. This is a
112
+ # required method
113
+ #
114
+ # @param action [ActionInquirer]
115
+ # @return [Schema]
116
+ def form_schema(action:)
117
+ @actual.form_schema(action: action)
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,9 @@
1
+ module Super
2
+ class Display
3
+ def self.format(schema, resource, column)
4
+ value = resource.public_send(column)
5
+
6
+ schema.fields[column].present(value)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ module Super
2
+ class Display
3
+ # This schema type is meant to be used for +#index+ or +#show+ actions to
4
+ # transform database fields into something that is human friendly.
5
+ #
6
+ # ```
7
+ # class MembersController::Controls
8
+ # # ...
9
+ #
10
+ # def show_schema
11
+ # Super::Schema.new(Super::Display::SchemaTypes.new) do |fields, type|
12
+ # fields[:name] = type.dynamic { |name| name }
13
+ # fields[:rank] = type.dynamic { |rank| rank }
14
+ # fields[:position] = type.dynamic { |position| position }
15
+ # fields[:ship] = type.dynamic { |ship| "#{ship.name} (Ship ##{ship.id})" }
16
+ # fields[:created_at] = type.dynamic { |created_at| created_at.iso8601 }
17
+ # fields[:updated_at] = type.dynamic { |updated_at| updated_at.iso8601 }
18
+ # end
19
+ # end
20
+ #
21
+ # # ...
22
+ # end
23
+ # ```
24
+ class SchemaTypes
25
+ class Dynamic
26
+ def initialize(transform_block)
27
+ @transform_block = transform_block
28
+ end
29
+
30
+ def present(field)
31
+ @transform_block.call(field)
32
+ end
33
+ end
34
+
35
+ def dynamic(&transform_block)
36
+ Dynamic.new(transform_block)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,4 +1,10 @@
1
1
  module Super
2
+ # Configures the host Rails app to work with Super
2
3
  class Engine < ::Rails::Engine
4
+ initializer "super.assets.precompile" do |app|
5
+ if Super::Assets.sprockets_available?
6
+ app.config.assets.precompile << "config/super_manifest.js"
7
+ end
8
+ end
3
9
  end
4
10
  end
@@ -0,0 +1,18 @@
1
+ module Super
2
+ # A container class for all custom errors thrown by this library
3
+ class Error < StandardError
4
+ class UnconfiguredConfiguration < Error; end
5
+ class InvalidConfiguration < Error; end
6
+ class InvalidStyle < Error; end
7
+ class InvalidPluginArgument < Error; end
8
+ class ActionInquirerError < Error; end
9
+ class LinkNotRegistered < Error; end
10
+
11
+ class ClientError < Error; end
12
+ class BadRequest < ClientError; end
13
+ class Unauthorized < ClientError; end
14
+ class Forbidden < ClientError; end
15
+ class NotFound < ClientError; end
16
+ class UnprocessableEntity < ClientError; end
17
+ end
18
+ end
@@ -0,0 +1,115 @@
1
+ module Super
2
+ class Form
3
+ # This schema type is used on your +#edit+ and +#new+ forms
4
+ #
5
+ # ```ruby
6
+ # class MembersController::Controls
7
+ # # ...
8
+ #
9
+ # def new_schema
10
+ # Super::Schema.new(Super::Form::SchemaTypes.new) do |fields, type|
11
+ # fields[:name] = type.generic("form_field_text")
12
+ # fields[:rank] = type.generic("form_field_select", collection: Member.ranks.keys)
13
+ # fields[:position] = type.generic("form_field_text")
14
+ # fields[:ship_id] = type.generic(
15
+ # "form_field_select",
16
+ # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
17
+ # )
18
+ # end
19
+ # end
20
+ #
21
+ # # ...
22
+ # end
23
+ # ```
24
+ class SchemaTypes
25
+ class Generic
26
+ def initialize(partial_path:, extras:, nested:)
27
+ @partial_path = partial_path
28
+ @extras = extras
29
+ @nested_fields = nested
30
+ end
31
+
32
+ attr_reader :nested_fields
33
+
34
+ # This takes advantage of a feature of Rails. If the value of
35
+ # `#to_partial_path` is `my_form_field`, Rails renders
36
+ # `app/views/super/application/_my_form_field.html.erb`, and this
37
+ # instance of Generic is accessible via `my_form_field`
38
+ #
39
+ # @return [String] the filename of the partial that will be rendered.
40
+ def to_partial_path
41
+ @partial_path
42
+ end
43
+
44
+ def [](key)
45
+ @extras[key]
46
+ end
47
+
48
+ def reader
49
+ @extras[:reader]
50
+ end
51
+
52
+ def label
53
+ if @extras.key?(:label)
54
+ return @extras[:label]
55
+ end
56
+
57
+ if @extras.key?(:reader)
58
+ return @extras[:reader].to_s.singularize.humanize
59
+ end
60
+ end
61
+
62
+ def ==(other)
63
+ return false if other.class != self.class
64
+ return false if other.instance_variable_get(:@partial_path) != @partial_path
65
+ return false if other.instance_variable_get(:@extras) != @extras
66
+ return false if other.instance_variable_get(:@nested_fields) != @nested_fields
67
+
68
+ true
69
+ end
70
+ end
71
+
72
+ def setup(fields:)
73
+ @fields = fields
74
+ end
75
+
76
+ def generic(partial_path, **extras)
77
+ Generic.new(partial_path: partial_path, extras: extras, nested: {})
78
+ end
79
+
80
+ def has_many(reader, **extras)
81
+ nested = @fields.nested do
82
+ yield
83
+ end
84
+
85
+ Generic.new(
86
+ partial_path: "form_has_many",
87
+ extras: extras.merge(reader: reader),
88
+ nested: nested
89
+ )
90
+ end
91
+
92
+ def has_one(reader, **extras)
93
+ nested = @fields.nested do
94
+ yield
95
+ end
96
+
97
+ Generic.new(
98
+ partial_path: "form_has_one",
99
+ extras: extras.merge(reader: reader),
100
+ nested: nested
101
+ )
102
+ end
103
+
104
+ alias_method :belongs_to, :has_one
105
+
106
+ def _destroy(**extras)
107
+ Generic.new(
108
+ partial_path: "form_field__destroy",
109
+ extras: extras,
110
+ nested: {}
111
+ )
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,19 @@
1
+ module Super
2
+ class Layout
3
+ def initialize(headers: nil, asides: nil, mains: nil, footers: nil)
4
+ @headers = Array(headers).compact
5
+ @asides = Array(asides).compact
6
+ @mains = Array(mains).compact
7
+ @footers = Array(footers).compact
8
+ end
9
+
10
+ attr_reader :headers
11
+ attr_reader :asides
12
+ attr_reader :mains
13
+ attr_reader :footers
14
+
15
+ def to_partial_path
16
+ "super_layout"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,87 @@
1
+ module Super
2
+ # Links have three required attributes that are passed directly into Rails'
3
+ # `link_to` helper
4
+ class Link
5
+ def self.resolve(link)
6
+ if link.kind_of?(self)
7
+ return link
8
+ end
9
+
10
+ if registry.key?(link)
11
+ return registry[link]
12
+ end
13
+
14
+ raise Error::LinkNotRegistered, "Unknown link `#{link}`"
15
+ end
16
+
17
+ def self.registry
18
+ @registry ||= {
19
+ new: -> (params:) {
20
+ new(
21
+ "New",
22
+ Rails.application.routes.url_for(
23
+ controller: params[:controller],
24
+ action: :new,
25
+ only_path: true
26
+ )
27
+ )
28
+ },
29
+ index: -> (params:) {
30
+ new(
31
+ "Index",
32
+ Rails.application.routes.url_for(
33
+ controller: params[:controller],
34
+ action: :index,
35
+ only_path: true
36
+ )
37
+ )
38
+ },
39
+ show: -> (resource, params:) {
40
+ new(
41
+ "View",
42
+ Rails.application.routes.url_for(
43
+ controller: params[:controller],
44
+ action: :show,
45
+ id: resource,
46
+ only_path: true
47
+ )
48
+ )
49
+ },
50
+ edit: -> (resource, params:) {
51
+ new(
52
+ "Edit",
53
+ Rails.application.routes.url_for(
54
+ controller: params[:controller],
55
+ action: :edit,
56
+ id: resource,
57
+ only_path: true
58
+ )
59
+ )
60
+ },
61
+ destroy: -> (resource, params:) {
62
+ new(
63
+ "Delete",
64
+ Rails.application.routes.url_for(
65
+ controller: params[:controller],
66
+ action: :destroy,
67
+ id: resource,
68
+ only_path: true
69
+ ),
70
+ method: :delete,
71
+ data: { confirm: "Really delete?" }
72
+ )
73
+ },
74
+ }
75
+ end
76
+
77
+ def initialize(text, href, **options)
78
+ @text = text
79
+ @href = href
80
+ @options = options
81
+ end
82
+
83
+ attr_reader :text
84
+ attr_reader :href
85
+ attr_reader :options
86
+ end
87
+ end