super 0.0.0 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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