warped 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +25 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +25 -19
- data/README.md +116 -270
- data/app/assets/config/warped_manifest.js +2 -0
- data/app/assets/javascript/warped/controllers/filter_controller.js +76 -0
- data/app/assets/javascript/warped/controllers/filters_controller.js +21 -0
- data/app/assets/javascript/warped/index.js +2 -0
- data/app/assets/stylesheets/warped/application.css +15 -0
- data/app/assets/stylesheets/warped/base.css +23 -0
- data/app/assets/stylesheets/warped/filters.css +115 -0
- data/app/assets/stylesheets/warped/pagination.css +74 -0
- data/app/assets/stylesheets/warped/search.css +33 -0
- data/app/assets/stylesheets/warped/table.css +114 -0
- data/app/views/warped/_actions.html.erb +9 -0
- data/app/views/warped/_cell.html.erb +3 -0
- data/app/views/warped/_column.html.erb +35 -0
- data/app/views/warped/_filters.html.erb +21 -0
- data/app/views/warped/_hidden_fields.html.erb +19 -0
- data/app/views/warped/_pagination.html.erb +34 -0
- data/app/views/warped/_row.html.erb +19 -0
- data/app/views/warped/_search.html.erb +21 -0
- data/app/views/warped/_table.html.erb +52 -0
- data/app/views/warped/filters/_filter.html.erb +40 -0
- data/config/importmap.rb +3 -0
- data/docs/controllers/FILTERABLE.md +193 -0
- data/docs/controllers/PAGEABLE.md +70 -0
- data/docs/controllers/README.md +8 -0
- data/docs/controllers/SEARCHABLE.md +95 -0
- data/docs/controllers/SORTABLE.md +94 -0
- data/docs/controllers/TABULATABLE.md +28 -0
- data/docs/controllers/views/PARTIALS.md +285 -0
- data/docs/jobs/README.md +22 -0
- data/docs/services/README.md +81 -0
- data/lib/generators/warped/install_generator.rb +1 -1
- data/lib/warped/api/filter/base/value.rb +52 -0
- data/lib/warped/api/filter/base.rb +84 -0
- data/lib/warped/api/filter/boolean.rb +41 -0
- data/lib/warped/api/filter/date.rb +26 -0
- data/lib/warped/api/filter/date_time.rb +32 -0
- data/lib/warped/api/filter/decimal.rb +31 -0
- data/lib/warped/api/filter/factory.rb +38 -0
- data/lib/warped/api/filter/integer.rb +38 -0
- data/lib/warped/api/filter/string.rb +25 -0
- data/lib/warped/api/filter/time.rb +25 -0
- data/lib/warped/api/filter.rb +14 -0
- data/lib/warped/api/sort/value.rb +40 -0
- data/lib/warped/api/sort.rb +65 -0
- data/lib/warped/controllers/filterable/ui.rb +46 -0
- data/lib/warped/controllers/filterable.rb +79 -42
- data/lib/warped/controllers/pageable/ui.rb +70 -0
- data/lib/warped/controllers/pageable.rb +11 -11
- data/lib/warped/controllers/searchable/ui.rb +37 -0
- data/lib/warped/controllers/searchable.rb +2 -0
- data/lib/warped/controllers/sortable/ui.rb +53 -0
- data/lib/warped/controllers/sortable.rb +53 -33
- data/lib/warped/controllers/tabulatable/ui.rb +54 -0
- data/lib/warped/controllers/tabulatable.rb +13 -27
- data/lib/warped/emails/components/align.rb +21 -0
- data/lib/warped/emails/components/base.rb +116 -0
- data/lib/warped/emails/components/button.rb +58 -0
- data/lib/warped/emails/components/divider.rb +15 -0
- data/lib/warped/emails/components/heading.rb +65 -0
- data/lib/warped/emails/components/layouts/columns.rb +36 -0
- data/lib/warped/emails/components/layouts/cta.rb +38 -0
- data/lib/warped/emails/components/layouts/main.rb +34 -0
- data/lib/warped/emails/components/link.rb +36 -0
- data/lib/warped/emails/components/spacer.rb +15 -0
- data/lib/warped/emails/components/stepper.rb +104 -0
- data/lib/warped/emails/components/table.rb +37 -0
- data/lib/warped/emails/components/text.rb +67 -0
- data/lib/warped/emails/helpers.rb +26 -0
- data/lib/warped/emails/slottable.rb +61 -0
- data/lib/warped/emails/styleable.rb +160 -0
- data/lib/warped/engine.rb +19 -0
- data/lib/warped/queries/filter.rb +32 -12
- data/lib/warped/table/action.rb +33 -0
- data/lib/warped/table/column.rb +34 -0
- data/lib/warped/version.rb +1 -1
- data/lib/warped.rb +2 -0
- data/warped.gemspec +1 -1
- metadata +73 -7
- data/lib/warped/emails/.keep +0 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
module Layouts
|
6
|
+
class Main < Base
|
7
|
+
variant do
|
8
|
+
base do
|
9
|
+
[
|
10
|
+
"font-family: 'Open Sans', 'Roboto', 'Segoe UI', Arial, sans-serif",
|
11
|
+
"max-width: 600px", "margin: 20px auto 0 auto",
|
12
|
+
"padding: 20px 30px", "background-color: #fff",
|
13
|
+
"border-radius: 5px", "box-shadow: 0 0 10px rgba(0, 0, 0, 0.1)"
|
14
|
+
]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
slots_one :header
|
19
|
+
slots_one :main
|
20
|
+
slots_one :footer
|
21
|
+
|
22
|
+
def template(&)
|
23
|
+
tag.div(style:) do
|
24
|
+
content_tag(:div, style: "vertical-align: middle;") do
|
25
|
+
concat tag.header(header)
|
26
|
+
concat tag.main(main)
|
27
|
+
concat tag.footer(footer)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
class Link < Text
|
6
|
+
variant do
|
7
|
+
base { ["text-decoration: underline"] }
|
8
|
+
end
|
9
|
+
|
10
|
+
default_variant size: :md, color: :info, display: :inline
|
11
|
+
|
12
|
+
def initialize(text = nil, href, size: nil, color: nil, display: nil)
|
13
|
+
super()
|
14
|
+
@text = text
|
15
|
+
@href = href
|
16
|
+
@size = size
|
17
|
+
@color = color
|
18
|
+
@display = display
|
19
|
+
end
|
20
|
+
|
21
|
+
def text
|
22
|
+
content.presence || @text
|
23
|
+
end
|
24
|
+
|
25
|
+
def template
|
26
|
+
style = style(size:, color:, display:)
|
27
|
+
|
28
|
+
tag.a(text, href:, style:)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :href, :size, :color, :display
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
class Stepper < Base
|
6
|
+
variant do
|
7
|
+
base { "padding: 20px;" }
|
8
|
+
end
|
9
|
+
|
10
|
+
variant :step do
|
11
|
+
base do
|
12
|
+
["border-radius: 100%;", "width: #{width}%",
|
13
|
+
"max-width: #{max_width}px", "height: #{height}px",
|
14
|
+
"text-align: center", "line-height: #{height}px",
|
15
|
+
"display: inline-block", "font-size: #{height / 2}px"]
|
16
|
+
end
|
17
|
+
|
18
|
+
state do
|
19
|
+
active { ["background-color: #007bff", "color: #fff"] }
|
20
|
+
inactive { ["background-color: #fff", "color: #007bff"] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
variant :step_divider do
|
25
|
+
base do
|
26
|
+
["height: 3px", "border-radius: 3px", "width: 100%"]
|
27
|
+
end
|
28
|
+
|
29
|
+
state do
|
30
|
+
active { ["background-color: #007bff;"] }
|
31
|
+
inactive { ["background-color: #ddd;"] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
variant :table do
|
36
|
+
base do
|
37
|
+
["display: inline-block", "min-width: 30px", "width: #{width}%",
|
38
|
+
"height: #{height}px", "border-collapse: collapse", "vertical-align: middle"]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
default_variant size: :md, color: :info, display: :block
|
43
|
+
|
44
|
+
def initialize(steps:, current_step: 1)
|
45
|
+
super()
|
46
|
+
@steps = steps
|
47
|
+
@current_step = current_step
|
48
|
+
end
|
49
|
+
|
50
|
+
def template
|
51
|
+
tag.div(style: "width: 100%") do
|
52
|
+
capture do
|
53
|
+
(1..steps).map do |step|
|
54
|
+
if step <= current_step
|
55
|
+
concat step(step, state: :active)
|
56
|
+
else
|
57
|
+
concat step(step, state: :inactive)
|
58
|
+
end
|
59
|
+
|
60
|
+
if step < steps
|
61
|
+
concat divider(state: step <= current_step ? :active : :inactive)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :steps, :current_step
|
71
|
+
|
72
|
+
def step(number, state:)
|
73
|
+
tag.div(style: "#{style(:step, state:)}; width: #{width}%;") do
|
74
|
+
tag.span(number)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def divider(state:)
|
79
|
+
tag.table(style: style(:table)) do
|
80
|
+
tag.tbody(style: "width: 100%; height: 100%; display: table") do
|
81
|
+
tag.tr(style: "width: 100%; height: 100%") do
|
82
|
+
tag.td(valign: :middle,
|
83
|
+
style: "vertical-align: middle; text-align: center; height: 100%; width: 100%; padding: 0 3px") do
|
84
|
+
tag.div(style: style(:step_divider, state:))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def width
|
92
|
+
(100 / (steps + (steps - 1))).round(4) - 0.8
|
93
|
+
end
|
94
|
+
|
95
|
+
def max_width
|
96
|
+
50
|
97
|
+
end
|
98
|
+
|
99
|
+
def height
|
100
|
+
50
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
class Table < Base
|
6
|
+
variant do
|
7
|
+
base do
|
8
|
+
["border-collapse: collapse"]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
variant :row do
|
13
|
+
base { ["border-bottom: 1px solid #f0f0f0"] }
|
14
|
+
end
|
15
|
+
|
16
|
+
variant :header do
|
17
|
+
base { ["background-color: #f9f9f9"] }
|
18
|
+
end
|
19
|
+
|
20
|
+
slots_one :header
|
21
|
+
slots_many :rows
|
22
|
+
|
23
|
+
def template
|
24
|
+
raise ArgumentError, "at least one row must be passed to the table" if rows.empty?
|
25
|
+
|
26
|
+
content_tag(:table, style:) do
|
27
|
+
concat content_tag(:thead, header, style: style(:header))
|
28
|
+
concat content_tag(:tbody) do
|
29
|
+
rows.each do |row|
|
30
|
+
concat content_tag(:tr, row, style: style(:row))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
class Text < Base
|
6
|
+
variant do
|
7
|
+
size do
|
8
|
+
xs { ["font-size: 11px", "line-height: 16px"] }
|
9
|
+
sm { ["font-size: 13px", "line-height: 16px"] }
|
10
|
+
md { ["font-size: 16px", "line-height: 24px"] }
|
11
|
+
lg { ["font-size: 19px", "line-height: 24px"] }
|
12
|
+
end
|
13
|
+
|
14
|
+
color do
|
15
|
+
regular { "color: #414750" }
|
16
|
+
placeholder { "color: #8B939F" }
|
17
|
+
info { "color: #1C51A4" }
|
18
|
+
success { "color: #60830D" }
|
19
|
+
warning { "color: #82620F" }
|
20
|
+
error { "color: #AB2816" }
|
21
|
+
end
|
22
|
+
|
23
|
+
align do
|
24
|
+
left { "text-align: left" }
|
25
|
+
right { "text-align: right" }
|
26
|
+
center { "text-align: center" }
|
27
|
+
end
|
28
|
+
|
29
|
+
weight do
|
30
|
+
regular { "font-weight: 400" }
|
31
|
+
bold { "font-weight: 700" }
|
32
|
+
end
|
33
|
+
|
34
|
+
display do
|
35
|
+
block { "display: block" }
|
36
|
+
inline { "display: inline" }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
default_variant size: :md, color: :regular, weight: :regular, display: :block
|
41
|
+
|
42
|
+
def initialize(text = nil, size: nil, color: nil, weight: nil, align: nil, display: nil)
|
43
|
+
super()
|
44
|
+
@text = text
|
45
|
+
@size = size
|
46
|
+
@color = color
|
47
|
+
@weight = weight
|
48
|
+
@align = align
|
49
|
+
@display = display
|
50
|
+
end
|
51
|
+
|
52
|
+
def text
|
53
|
+
content.presence || @text
|
54
|
+
end
|
55
|
+
|
56
|
+
def template(&)
|
57
|
+
style = style(size:, color:, weight:, align:, display:)
|
58
|
+
|
59
|
+
tag.p(text, style:)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :size, :color, :weight, :align, :display
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Warped
|
6
|
+
module Emails
|
7
|
+
module Helpers
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
helper_method :frontend_url
|
12
|
+
end
|
13
|
+
|
14
|
+
def frontend_url(path = [])
|
15
|
+
frontend_path = Array(path)
|
16
|
+
|
17
|
+
url = base_url
|
18
|
+
path ? [url, *frontend_path].join("/").gsub(%r{(?<!:)//}, "/") : url
|
19
|
+
end
|
20
|
+
|
21
|
+
def base_url
|
22
|
+
ENV["FRONTEND_URL"].presence || ENV["SERVER_HOST"].presence || "http://localhost:3000"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
module Slottable
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def slots_one(name)
|
12
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
13
|
+
# def with_header(&block)
|
14
|
+
# slots[:one][:header] = block
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def header
|
18
|
+
# capture(&slots[:one][:header]) if slots[:one][:header]
|
19
|
+
# end
|
20
|
+
def with_#{name}(&block)
|
21
|
+
slots[:one][:#{name}] = block
|
22
|
+
end
|
23
|
+
|
24
|
+
def #{name}
|
25
|
+
capture(&slots[:one][:#{name}]) if slots[:one][:#{name}]
|
26
|
+
end
|
27
|
+
RUBY
|
28
|
+
end
|
29
|
+
|
30
|
+
def slots_many(name)
|
31
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
32
|
+
# def with_column(&block)
|
33
|
+
# slots[:many][:columns] ||= []
|
34
|
+
# slots[:many][:columns] << block
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def columns
|
38
|
+
# return [] if slots[:many][:columns].empty?
|
39
|
+
#
|
40
|
+
# slots[:many][:columns].map { |block| capture(&block) }
|
41
|
+
# end
|
42
|
+
def with_#{name.to_s.singularize}(&block)
|
43
|
+
slots[:many][:#{name}] ||= []
|
44
|
+
slots[:many][:#{name}] << block
|
45
|
+
end
|
46
|
+
|
47
|
+
def #{name}
|
48
|
+
return [] if slots[:many][:#{name}].empty?
|
49
|
+
|
50
|
+
slots[:many][:#{name}].map { |block| capture(&block) }
|
51
|
+
end
|
52
|
+
RUBY
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def slots
|
57
|
+
@slots ||= { one: {}, many: {} }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
require "active_support/core_ext/hash/deep_merge"
|
5
|
+
require "active_support/core_ext/module/delegation"
|
6
|
+
require "active_support/core_ext/object/deep_dup"
|
7
|
+
|
8
|
+
module Warped
|
9
|
+
module Emails
|
10
|
+
module Styleable
|
11
|
+
def self.included(base)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
attr_writer :default_variants, :base_styles, :variants
|
17
|
+
|
18
|
+
def inherited(subclass)
|
19
|
+
super
|
20
|
+
|
21
|
+
subclass.default_variants = default_variants.deep_dup.deep_merge!(subclass.default_variants)
|
22
|
+
subclass.base_styles = base_styles.deep_dup.deep_merge!(subclass.base_styles)
|
23
|
+
subclass.variants = variants.deep_dup.deep_merge!(subclass.variants)
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_variants
|
27
|
+
@default_variants ||= {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def base_styles
|
31
|
+
@base_styles ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def variants
|
35
|
+
@variants ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def variant(name = :_base_variant, &)
|
39
|
+
raise ArgumentError, "You must provide a block" unless block_given?
|
40
|
+
|
41
|
+
variant_builder = VariantBuilder.new(self, variant_name: name)
|
42
|
+
variant_builder.compile_variants(&)
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_variant(variant_name = :_base_variant, **kwargs)
|
46
|
+
raise ArgumentError, "You must provide a hash" unless kwargs.is_a?(Hash)
|
47
|
+
|
48
|
+
self.default_variants ||= {}
|
49
|
+
self.default_variants[variant_name] ||= {}
|
50
|
+
self.default_variants[variant_name].merge!(kwargs)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_variants
|
55
|
+
self.class.default_variants
|
56
|
+
end
|
57
|
+
|
58
|
+
def base_styles
|
59
|
+
self.class.base_styles
|
60
|
+
end
|
61
|
+
|
62
|
+
def variants
|
63
|
+
self.class.variants
|
64
|
+
end
|
65
|
+
|
66
|
+
def style(variant_name = :_base_variant, **kwargs)
|
67
|
+
base_style_arr = base_block_value(variant_name)
|
68
|
+
|
69
|
+
validate_variants!(variant_name, **kwargs)
|
70
|
+
|
71
|
+
default_variants[variant_name] ||= {}
|
72
|
+
default_variants[variant_name].merge(kwargs.compact_blank).each do |group_name, subvariant_name|
|
73
|
+
subvariant_arr = variant_block_value(variant_name, group_name, subvariant_name)
|
74
|
+
base_style_arr.concat(subvariant_arr)
|
75
|
+
end
|
76
|
+
|
77
|
+
base_style_arr.join("; ")
|
78
|
+
end
|
79
|
+
|
80
|
+
# @!visibility private
|
81
|
+
class VariantBuilder < BasicObject
|
82
|
+
delegate :variants, to: :@component
|
83
|
+
|
84
|
+
def initialize(component, variant_name: :_base_variant, group_name: nil)
|
85
|
+
@component = component
|
86
|
+
@group_name = group_name
|
87
|
+
@variant_name = variant_name
|
88
|
+
end
|
89
|
+
|
90
|
+
def compile_variants(&)
|
91
|
+
instance_eval(&)
|
92
|
+
end
|
93
|
+
|
94
|
+
def method_missing(name, &)
|
95
|
+
return super unless ::Kernel.block_given?
|
96
|
+
|
97
|
+
if !@group_name && name == :base
|
98
|
+
_define_base_style(&)
|
99
|
+
elsif !@group_name
|
100
|
+
_define_variant_group(name, &)
|
101
|
+
else
|
102
|
+
_define_subvariant(name, &)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def respond_to_missing?(...) = true
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def _define_variant_group(name, &)
|
111
|
+
VariantBuilder.new(@component, variant_name: @variant_name, group_name: name).compile_variants(&)
|
112
|
+
end
|
113
|
+
|
114
|
+
def _define_subvariant(name, &block)
|
115
|
+
variants[@variant_name] ||= {}
|
116
|
+
variants[@variant_name][@group_name] ||= {}
|
117
|
+
variants[@variant_name][@group_name][name] = block
|
118
|
+
end
|
119
|
+
|
120
|
+
def _define_base_style(&block)
|
121
|
+
@component.base_styles[@variant_name] = block
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private_constant :VariantBuilder
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def base_block_value(variant_name)
|
130
|
+
return [] unless base_styles.key?(variant_name.to_sym)
|
131
|
+
|
132
|
+
base_block = base_styles[variant_name.to_sym]
|
133
|
+
Array.wrap(instance_eval(&base_block))
|
134
|
+
end
|
135
|
+
|
136
|
+
def variant_block_value(variant_name, group_name, subvariant_name)
|
137
|
+
return [] if subvariant_name.nil?
|
138
|
+
|
139
|
+
variant_block = self.class.variants[variant_name][group_name.to_sym][subvariant_name.to_sym]
|
140
|
+
Array.wrap(instance_eval(&variant_block))
|
141
|
+
end
|
142
|
+
|
143
|
+
def validate_variants!(name, **kwargs)
|
144
|
+
kwargs.each do |group_name, variant_name|
|
145
|
+
next if variant_name.nil?
|
146
|
+
|
147
|
+
unless variants[name].key?(group_name.to_sym)
|
148
|
+
raise ArgumentError,
|
149
|
+
"Invalid variant group: #{group_name}"
|
150
|
+
end
|
151
|
+
|
152
|
+
unless variants[name][group_name.to_sym].key?(variant_name.to_sym)
|
153
|
+
raise ArgumentError,
|
154
|
+
"Invalid variant name: #{variant_name}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
module Warped
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace Warped
|
8
|
+
|
9
|
+
initializer "warped.assets" do
|
10
|
+
if Rails.application.config.respond_to?(:assets)
|
11
|
+
Rails.application.config.assets.precompile += %w[warped_manifest.js]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "warped.importmap", before: "importmap" do |app|
|
16
|
+
app.config.importmap.paths << Engine.root.join("config/importmap.rb") if Rails.application.respond_to?(:importmap)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -10,22 +10,22 @@ module Warped
|
|
10
10
|
# This class provides a simple interface for filtering a scope by a set of conditions.
|
11
11
|
# The conditions are passed as an array of hashes, where each hash contains the following
|
12
12
|
# keys:
|
13
|
-
# - +relation+: the relation to use for the filter (e.g. "
|
13
|
+
# - +relation+: the relation to use for the filter (e.g. "eq", "gt", "in", etc.)
|
14
14
|
# - +field+: the field to filter by
|
15
15
|
# - +value+: the value to filter by
|
16
16
|
#
|
17
17
|
#
|
18
18
|
# @example Filter a scope by a set of conditions
|
19
19
|
# Warped::Queries::Filter.call(User.all, filter_conditions: [
|
20
|
-
# { relation: "
|
21
|
-
# { relation: "
|
20
|
+
# { relation: "eq", field: :first_name, value: "John" },
|
21
|
+
# { relation: "gt", field: :age, value: 18 }
|
22
22
|
# ])
|
23
23
|
# # => #<ActiveRecord::Relation [...]>
|
24
24
|
#
|
25
25
|
# @see RELATIONS
|
26
26
|
# To see the list of available relations, check the +RELATIONS+ constant.
|
27
27
|
class Filter
|
28
|
-
RELATIONS =
|
28
|
+
RELATIONS = ::Warped::Filter::Base::RELATIONS.freeze
|
29
29
|
|
30
30
|
# @param scope [ActiveRecord::Relation] the scope to filter
|
31
31
|
# @param filter_conditions [Array<Hash>] the conditions to filter by
|
@@ -66,26 +66,46 @@ module Warped
|
|
66
66
|
|
67
67
|
def filtered_scope(scope, relation, field, value)
|
68
68
|
case relation
|
69
|
-
when "
|
69
|
+
when "eq"
|
70
70
|
scope.where(field => value)
|
71
|
-
when "
|
71
|
+
when "in"
|
72
|
+
scope.where(field => Array.wrap(value))
|
73
|
+
when "neq", "not_in"
|
72
74
|
scope.where.not(field => value)
|
73
75
|
when "between"
|
74
|
-
scope.where(
|
76
|
+
scope.where(field => value.first..value.last)
|
75
77
|
when "starts_with"
|
76
|
-
scope.where(
|
78
|
+
scope.where(arel_table_for(scope, field).matches("#{value}%"))
|
77
79
|
when "ends_with"
|
78
|
-
scope.where(
|
80
|
+
scope.where(arel_table_for(scope, field).matches("%#{value}"))
|
79
81
|
when "contains"
|
80
|
-
scope.where(
|
82
|
+
scope.where(arel_table_for(scope, field).matches("%#{value}%"))
|
81
83
|
when "is_null"
|
82
84
|
scope.where(field => nil)
|
83
85
|
when "is_not_null"
|
84
86
|
scope.where.not(field => nil)
|
85
|
-
|
86
|
-
scope.where(
|
87
|
+
when "gt"
|
88
|
+
scope.where.not(field => ..value)
|
89
|
+
when "gte"
|
90
|
+
scope.where(field => value..)
|
91
|
+
when "lt"
|
92
|
+
scope.where.not(field => value..)
|
93
|
+
when "lte"
|
94
|
+
scope.where(field => ..value)
|
87
95
|
end
|
88
96
|
end
|
97
|
+
|
98
|
+
# @param scope [ActiveRecord::Relation]
|
99
|
+
# @param field [String|Symbol]
|
100
|
+
# @return [Arel::Table]
|
101
|
+
# The arel table for the given field. If the field has the format "table.column",
|
102
|
+
# the method will return the table for the field's table and column.
|
103
|
+
def arel_table_for(scope, field)
|
104
|
+
column, relation = field.to_s.split(".").reverse
|
105
|
+
return scope.arel_table[column.to_sym] unless relation
|
106
|
+
|
107
|
+
scope.arel_table.alias(relation.to_sym)[column.to_sym]
|
108
|
+
end
|
89
109
|
end
|
90
110
|
end
|
91
111
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
|
5
|
+
module Warped
|
6
|
+
module Table
|
7
|
+
class Action
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
delegate :[], to: :options
|
11
|
+
|
12
|
+
# @param name [String | Symbol | Proc] The name of the action
|
13
|
+
# @param path [String | Symbol | Proc] The path of the action
|
14
|
+
def initialize(name, path, **options)
|
15
|
+
@name = name
|
16
|
+
@path = path
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def path(...)
|
21
|
+
return @path.call(...) if @path.is_a?(Proc)
|
22
|
+
|
23
|
+
@path
|
24
|
+
end
|
25
|
+
|
26
|
+
def name(...)
|
27
|
+
return @name.call(...) if @name.is_a?(Proc)
|
28
|
+
|
29
|
+
@name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|