oversee 0.2.0 → 0.3.1

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -3
  3. data/app/assets/config/oversee_manifest.js +2 -0
  4. data/app/components/oversee/dashboard/actions.rb +58 -0
  5. data/app/components/oversee/dashboard/filter.rb +12 -0
  6. data/app/components/oversee/dashboard/filters.rb +5 -51
  7. data/app/components/oversee/dashboard/header.rb +34 -18
  8. data/app/components/oversee/dashboard/index.rb +23 -11
  9. data/app/components/oversee/dashboard/javascript.rb +4 -4
  10. data/app/components/oversee/dashboard/pagination.rb +1 -1
  11. data/app/components/oversee/dashboard/sidebar.rb +38 -1
  12. data/app/components/oversee/dashboard/tailwind.rb +30 -1
  13. data/app/components/oversee/field/display.rb +12 -21
  14. data/app/components/oversee/field/form.rb +5 -37
  15. data/app/components/oversee/field/input/belongs_to.rb +5 -0
  16. data/app/components/oversee/field/input/boolean.rb +3 -18
  17. data/app/components/oversee/field/input/date.rb +5 -0
  18. data/app/components/oversee/field/input/datetime.rb +2 -17
  19. data/app/components/oversee/field/input/integer.rb +2 -17
  20. data/app/components/oversee/field/input/json.rb +5 -0
  21. data/app/components/oversee/field/input/rich_text.rb +10 -0
  22. data/app/components/oversee/field/input/string.rb +2 -17
  23. data/app/components/oversee/field/input.rb +13 -11
  24. data/app/components/oversee/field/label.rb +16 -15
  25. data/app/components/oversee/field/set.rb +36 -0
  26. data/app/components/oversee/field/value/belongs_to.rb +15 -0
  27. data/app/components/oversee/field/value/boolean.rb +2 -47
  28. data/app/components/oversee/field/value/date.rb +5 -0
  29. data/app/components/oversee/field/value/datetime.rb +2 -6
  30. data/app/components/oversee/field/value/enum.rb +2 -6
  31. data/app/components/oversee/field/value/integer.rb +2 -6
  32. data/app/components/oversee/field/value/json.rb +6 -0
  33. data/app/components/oversee/field/value/rich_text.rb +5 -0
  34. data/app/components/oversee/field/value/string.rb +3 -10
  35. data/app/components/oversee/field/value/text.rb +2 -6
  36. data/app/components/oversee/field/value.rb +14 -5
  37. data/app/components/oversee/field.rb +42 -0
  38. data/app/components/oversee/flash.rb +39 -0
  39. data/app/components/oversee/layout/application.rb +46 -0
  40. data/app/components/oversee/resources/index.rb +22 -3
  41. data/app/components/oversee/resources/new.rb +7 -3
  42. data/app/components/oversee/resources/show.rb +139 -68
  43. data/app/components/oversee/resources/table.rb +8 -2
  44. data/app/controllers/oversee/dashboard_controller.rb +1 -1
  45. data/app/controllers/oversee/resources/fields_controller.rb +24 -36
  46. data/app/controllers/oversee/resources_controller.rb +29 -24
  47. data/app/javascript/oversee/application.js +3 -0
  48. data/app/javascript/oversee/controllers/clipboard_controller.js +21 -0
  49. data/app/javascript/oversee/controllers/index.js +9 -0
  50. data/app/javascript/oversee/controllers/notification_controller.js +17 -0
  51. data/app/javascript/oversee/controllers/reveal_controller.js +19 -0
  52. data/app/javascript/oversee/controllers/sidebar/state_controller.js +21 -0
  53. data/app/models/oversee/resource.rb +49 -20
  54. data/app/{oversee → service/oversee}/filter.rb +2 -2
  55. data/app/{oversee → service/oversee}/search.rb +1 -1
  56. data/app/views/oversee/base.rb +5 -0
  57. data/config/importmap.rb +14 -0
  58. data/config/routes.rb +16 -10
  59. data/lib/generators/oversee/install_generator.rb +7 -0
  60. data/lib/oversee/engine.rb +19 -0
  61. data/lib/oversee/version.rb +1 -1
  62. data/lib/oversee.rb +13 -0
  63. data/lib/tasks/oversee_tasks.rake +6 -4
  64. metadata +71 -9
  65. data/app/jobs/oversee/application_job.rb +0 -4
  66. data/app/oversee/cards.rb +0 -2
  67. data/app/oversee/resource.rb +0 -10
  68. data/app/views/layouts/oversee/application.html.erb +0 -26
@@ -4,104 +4,156 @@ class Oversee::Resources::Show < Oversee::Base
4
4
  include Phlex::Rails::Helpers::ButtonTo
5
5
  include Phlex::Rails::Helpers::TurboFrameTag
6
6
 
7
+
8
+ attr_reader :resource
9
+ attr_reader :resource_class
10
+ attr_reader :resource_associations
11
+
7
12
  def initialize(resource:, resource_class:, resource_associations:, params:)
8
13
  @resource = resource
9
14
  @resource_class = resource_class
10
15
  @resource_associations = resource_associations
11
16
  @params = params
12
- @oversee_resource = Oversee::Resource.new(resource_class: @resource_class, resource: @resource)
17
+
18
+ @oversee_resource = Oversee::Resource.new(resource_class: @resource_class, instance: @resource)
13
19
  end
14
20
 
21
+ def around_template
22
+ render Oversee::Layout::Application.new { super }
23
+ end
15
24
 
16
25
  def view_template
17
- render Oversee::Dashboard::Header.new(title: @resource_class.to_s, subtitle: "##{@resource.id}", return_path: helpers.resources_path(resource_class_name: @params[:resource_class_name])) do
18
- button_to(helpers.resource_path(resource_class_name: @params[:resource_class_name]), method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "size-8 inline-flex items-center justify-center rounded-full text-rose-500 bg-rose-50 hover:bg-rose-100 text-sm font-medium transition-colors") { render Phlex::Icons::Iconoir::Trash.new(class: "size-4") }
26
+ render Oversee::Dashboard::Header.new(title: @resource_class.to_s.pluralize) do |h|
27
+ h.left do
28
+ h.separator
29
+ div(class: "inline-flex items-center gap-1 text-sm bg-gray-100 text-gray-600 h-6 px-2") do
30
+ render Phlex::Icons::Iconoir::Hashtag.new(class: "size-2.5 text-gray-500", stroke_width: 2)
31
+ span { @resource.to_param }
32
+ end
33
+ end
34
+ h.right do
35
+ details(class: "relative inline-block") do
36
+ summary(class: "cursor-pointer list-none") do
37
+ div(class: "inline-flex items-center justify-center size-8 hover:bg-indigo-50 transition") do
38
+ render Phlex::Icons::Iconoir::MoreHorizCircle.new(class: "size-4 text-gray-500")
39
+ end
40
+ end
41
+ ul(
42
+ class:
43
+ "absolute p-2 mt-2 right-0 top-full min-w-40 overflow-hidden rounded-lg bg-white border border-b-2 border-gray-200/75 divide-y text-xs text-gray-500 font-medium"
44
+ ) do
45
+ li(class: "w-full") do
46
+ button_to(helpers.resource_path(resource_class_name: @params[:resource_class_name]), method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "p-1 hover:bg-gray-50 w-full flex items-center gap-2 transition duration-100") do
47
+ div(class: "inline-flex items-center justify-center size-6 bg-gray-100") do
48
+ render Phlex::Icons::Iconoir::Trash.new(class: "size-3 text-gray-500")
49
+ end
50
+ plain "Delete"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
19
56
  end
20
57
 
21
- div(class: "p-8") do
58
+ hr(class: "my-4")
59
+
60
+ # COLUMNS
61
+ div(class: "flex flex-col gap-4") do
22
62
  @resource_class.columns_hash.each do |key, metadata|
23
- div(class: "py-4") do
24
- div(class: "space-y-2") do
25
- render Oversee::Field::Label.new(key: key, datatype: metadata.sql_type_metadata.type)
26
- div(id: dom_id(@resource, :"#{key}_row"), class: "flex items-center gap-2 mt-4") do
27
- render Oversee::Field::Display.new(resource: @resource, key: key, datatype: metadata.sql_type_metadata.type)
28
- # div(id: dom_id(@resource, :"#{key}_actions")) do
29
- # div(class: "bg-white text-gray-400 size-9 aspect-square inline-flex items-center justify-center"){ render Phlex::Icons::Iconoir::Copy.new(class: "size-5", stroke_width: 1.75) }
30
- # end
63
+ next if @oversee_resource.foreign_keys.include?(key.to_s)
64
+ value = @resource.send(key)
65
+ datatype = metadata.sql_type_metadata.type
66
+ render Oversee::Field::Set.new(resource:, key:, value:, datatype:)
67
+ end
68
+ end
69
+
70
+
71
+ # RICH TEXT Associations
72
+ if !!rich_text_associations.length
73
+ hr(class: "my-4")
74
+ rich_text_associations.each do |association|
75
+
76
+ # Remove the "rich_text_" prefix from the association name
77
+ key = association[:name].to_s[10..].to_sym
78
+
79
+ div do
80
+ div(class: "space-y-4") do
81
+ div(class:"flex items-center gap-2") do
82
+ render Oversee::Field::Label.new(key: key.to_s.titleize, datatype: :rich_text)
83
+ a(href: helpers.resources_path(resource_class_name: association[:class_name].to_s), class: "hover:text-blue-500") { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
84
+ end
85
+
86
+ div(id: dom_id(@resource, :"#{key.to_s}_row"), class: "flex items-center gap-2 mt-4") do
87
+ render Oversee::Field::Display.new(resource:, key:, value: @resource.send(key).to_plain_text[..196], datatype: :rich_text)
31
88
  end
32
89
  end
33
90
  end
34
91
  end
92
+ end
35
93
 
36
- # BELONGS_TO Associations
37
- if !!belongs_to_associations.length
38
- belongs_to_associations.each do |association|
39
- div(class: "py-6") do
40
- div(class: "space-y-4") do
41
- div(class:"flex items-center gap-2") do
42
- render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
43
- a(href: helpers.resources_path(resource_class_name: association[:class_name].to_s), class: "hover:text-blue-500") { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
44
- end
94
+ # BELONGS_TO Associations
95
+ if !!belongs_to_associations.length
96
+ belongs_to_associations.each do |association|
97
+ div(class: "py-6") do
98
+ div(class: "space-y-4") do
99
+ div(class:"flex items-center gap-2") do
100
+ render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
101
+ a(href: helpers.resources_path(resource_class_name: association[:class_name].to_s), class: "hover:text-blue-500") { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
102
+ end
45
103
 
46
- div(class: "flex items-center gap-2 flex-wrap") do
47
- foreign_key_value = @resource[association[:foreign_key]]
48
- path = !!foreign_key_value ? helpers.resource_path(id: foreign_key_value, resource_class_name: association[:class_name]) : helpers.resources_path(resource_class_name: association[:class_name])
49
-
50
- a(
51
- href: path,
52
- class:
53
- "inline-flex items-center gap-2 text-xs border border-transparent bg-gray-100 hover:bg-gray-200 px-3 py-1.5"
54
- ) do
55
- plain "#{association[:class_name]} | #{foreign_key_value}"
56
- span { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
57
- end
104
+ foreign_key = association[:foreign_key]
105
+ foreign_key_value = @resource[association[:foreign_key]]
106
+ path = !!foreign_key_value ? helpers.resource_path(id: foreign_key_value, resource_class_name: association[:class_name]) : helpers.resources_path(resource_class_name: association[:class_name])
107
+
108
+ div(id: dom_id(@resource, :"#{foreign_key}_row"), class: "flex items-center gap-2 mt-4") do
109
+ render Oversee::Field::Display.new(resource:, key: foreign_key, value: foreign_key_value, datatype: :belongs_to, display_key: true)
110
+ div(id: dom_id(@resource, :"#{foreign_key}_actions")) do
111
+ a(href: path, class: "bg-gray-100 hover:bg-gray-200 text-gray-400 hover:text-blue-500 size-10 aspect-square inline-flex items-center justify-center transition-colors"){ render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-4") }
58
112
  end
59
113
  end
60
114
  end
61
115
  end
62
116
  end
117
+ end
63
118
 
64
- # HAS_MANY Associations
65
- if !!has_many_associations.length
119
+ hr(class: "my-4")
120
+
121
+ # HAS_MANY Associations
122
+ if !!has_many_associations.length
123
+ div(class: "flex flex-col gap-8") do
66
124
  has_many_associations.each do |association|
67
125
  associated_resources = @resource.send(association[:name])
68
126
  associated_resource_class = association[:class_name].constantize
69
127
 
70
- div(class: "py-6") do
71
- div(class: "space-y-4") do
72
- div(class:"flex items-center gap-2") do
73
- render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
74
- a(href: helpers.resources_path(resource_class_name: association[:class_name]), class: "hover:text-blue-500") { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
75
- end
128
+ div(class: "space-y-4") do
129
+ div(class:"flex items-center gap-2") do
130
+ render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
131
+ a(href: helpers.resources_path(resource_class_name: association[:class_name]), class: "hover:text-blue-500") { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
132
+ end
133
+
134
+ div(class: "bg-gray-50 p-2") do
135
+ # turbo_frame_tag(
136
+ # dom_id(associated_resource_class, :table),
137
+ # src: helpers.resources_table_path(resources_table_params(association)),
138
+ # loading: :lazy,
139
+ # data: { turbo_stream: true }
140
+ # ) do
141
+ # div(class: "h-20 flex items-center justify-center") { render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "animate-pulse size-6 text-gray-600") }
142
+ # end
76
143
 
77
- div(class: "bg-gray-50 p-2") do
78
- if associated_resources.present?
79
- div(class: "bg-white") do
80
- # turbo_frame_tag(
81
- # dom_id(associated_resource_class, :table),
82
- # src: helpers.resources_table_path(
83
- # resource_class_name: association[:class_name],
84
- # filters: { eq: { association[:foreign_key] => [@resource.id] } }
85
- # ),
86
- # loading: :lazy,
87
- # data: { turbo_stream: true }
88
- # ) do
89
- # div(class: "h-20 flex items-center justify-center") { render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "animate-pulse size-6 text-gray-600") }
90
- # end
91
-
92
- #
93
- render Oversee::Resources::Table.new(
94
- resources: associated_resources,
95
- resource_class: associated_resource_class,
96
- params: @params
97
- )
98
- end
99
- else
100
- p(class: "bg-gray-50 p-2 pr-4 flex gap-2 items-center text-xs") {
101
- render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "size-3")
102
- plain "No #{association[:name].to_s.titleize.downcase} found"
103
- }
144
+ if associated_resources.present?
145
+ div(class: "bg-white") do
146
+ render Oversee::Resources::Table.new(
147
+ resources: associated_resources,
148
+ resource_class: associated_resource_class,
149
+ params: @params
150
+ )
104
151
  end
152
+ else
153
+ p(class: "bg-gray-50 p-2 pr-4 flex gap-2 items-center text-xs") {
154
+ render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "size-3")
155
+ plain "No #{association[:name].to_s.titleize.downcase} found"
156
+ }
105
157
  end
106
158
  end
107
159
  end
@@ -112,6 +164,10 @@ class Oversee::Resources::Show < Oversee::Base
112
164
 
113
165
  private
114
166
 
167
+ def rich_text_associations
168
+ @oversee_resource.rich_text_associations
169
+ end
170
+
115
171
  def belongs_to_associations
116
172
  @oversee_resource.associations[:belongs_to]
117
173
  end
@@ -123,4 +179,19 @@ class Oversee::Resources::Show < Oversee::Base
123
179
  def has_associations?
124
180
  @resource_associations.present?
125
181
  end
182
+
183
+ def resources_table_params(association)
184
+ # if association[:through].nil?
185
+ # return { association[:foreign_key] => { eq: [@resource.id] } }
186
+ # else
187
+ # keys = @resource.send(association[:through]).pluck(association[:foreign_key])
188
+ # return { @resource_class.primary_key => { eq: keys } }
189
+ # end
190
+
191
+ {
192
+ resource_class_name: association[:class_name],
193
+ association_name: association[:through],
194
+ filters: { association[:foreign_key] => { eq: [@resource.id] } }
195
+ }
196
+ end
126
197
  end
@@ -8,16 +8,19 @@ class Oversee::Resources::Table < Oversee::Base
8
8
  @resource_class = resource_class || resources.first.class
9
9
  @params = params
10
10
  @options = options
11
+
12
+ @oversee_resource = Oversee::Resource.new(resource_class: @resource_class)
11
13
  end
12
14
 
13
15
  def view_template
14
- turbo_frame_tag dom_id(@resource_class, :table), target: :_top do
16
+ turbo_frame_tag dom_id(@resource_class, :table), target: "_top" do
15
17
  render Oversee::Table.new do |table|
16
18
  table.head do |head|
17
19
  head.row do
18
20
  th(scope: "col", class: "px-4 py-3 text-left text-xs text-gray-900 uppercase")
19
21
  # Attributes
20
22
  @resource_class.columns_hash.each do |key, metadata|
23
+ next if @oversee_resource.foreign_keys.include?(key.to_s)
21
24
  th(scope: "col", class: "text-left text-xs text-gray-900 uppercase whitespace-nowrap hover:bg-gray-50 transition relative") do
22
25
  a(
23
26
  href:
@@ -29,6 +32,7 @@ class Oversee::Resources::Table < Oversee::Base
29
32
  @params[:sort_direction] == "asc" ? :desc : :asc
30
33
  )
31
34
  ),
35
+ target: "_top",
32
36
  class: "px-4 py-3 flex items-center justify-between gap-2 w-full h-full hover:text-gray-900 transition-colors"
33
37
  ) do
34
38
  render Oversee::Field::Label.new(key: key, datatype: metadata.sql_type_metadata.type)
@@ -52,6 +56,7 @@ class Oversee::Resources::Table < Oversee::Base
52
56
 
53
57
  table.body do |body|
54
58
  @resources.each do |resource|
59
+
55
60
  body.row do |row|
56
61
  td do
57
62
  div(class: "flex space-x-2 mx-4") do
@@ -70,9 +75,10 @@ class Oversee::Resources::Table < Oversee::Base
70
75
  end
71
76
 
72
77
  @resource_class.columns_hash.each do |key, metadata|
78
+ next if @oversee_resource.foreign_keys.include?(key.to_s)
73
79
  row.data do
74
80
  div(class: "max-w-96") do
75
- render Oversee::Field::Value.new(datatype: metadata.sql_type_metadata.type, value: resource.send(key), key: key)
81
+ render Oversee::Field::Value.new(key:, datatype: metadata.sql_type_metadata.type, value: resource.send(key), for_table: true)
76
82
  end
77
83
  end
78
84
  end
@@ -1,7 +1,7 @@
1
1
  module Oversee
2
2
  class DashboardController < BaseController
3
3
  def index
4
- render Oversee::Dashboard::Index.new
4
+ render Oversee::Dashboard::Index.new, layout: false
5
5
  end
6
6
 
7
7
  def show
@@ -1,45 +1,33 @@
1
- module Oversee
2
- module Resources
3
- class FieldsController < BaseController
4
- before_action :set_resource_class
1
+ class Oversee::Resources::FieldsController < Oversee::ResourcesController
2
+ before_action :set_resource, only: %i[input]
5
3
 
6
- def update
7
- @resource_class = params[:resource_class].constantize
8
- @resource = @resource_class.find(params[:id])
9
- # @datatype = @resource_class.columns_hash[@key.to_s].type
4
+ # Renders the display field for a resource
5
+ def show
6
+ end
10
7
 
11
- respond_to do |format|
12
- if @resource.update(resource_params)
13
- format.html { redirect_to resource_path(@resource, resource: @resource_class) }
14
- format.turbo_stream
15
- else
16
- end
17
- end
18
- end
19
-
20
- # Non-standard actions
21
- def edit
22
- @resource = @resource_class.find(params[:id])
23
- @key = params[:key].to_sym
24
- @value = @resource.send(@key)
25
- @datatype = @resource.class.columns_hash[@key.to_s].type
26
- puts "---"
27
- puts "key: #{@key}"
28
- puts "value: #{@value}"
29
- puts "datatype: #{@datatype}"
30
- puts "---"
8
+ # Renders the input field for a resource
9
+ def input
10
+ component_dom_id = dom_id(@resource, key)
11
+ component = Oversee::Field::Set.new(resource: @resource, datatype:, key:, value:, state: :input)
31
12
 
13
+ respond_to do |format|
14
+ format.turbo_stream do
15
+ render turbo_stream: turbo_stream.replace(component_dom_id, component)
32
16
  end
17
+ end
18
+ end
33
19
 
34
- private
20
+ private
35
21
 
36
- def set_resource_class
37
- @resource_class = params[:resource_class].constantize
38
- end
22
+ def key
23
+ params[:key].to_sym
24
+ end
39
25
 
40
- def resource_params
41
- params.require(:resource).permit!
42
- end
43
- end
26
+ def value
27
+ params[:value] || @resource.send(key)
28
+ end
29
+
30
+ def datatype
31
+ params[:datatype] || @resource.class.columns_hash[key.to_s].type
44
32
  end
45
33
  end
@@ -3,8 +3,8 @@ module Oversee
3
3
  include ActionView::RecordIdentifier
4
4
 
5
5
  before_action :set_resource_class
6
- before_action :set_resource, only: %i[show edit update destroy input_field]
7
- before_action :set_resources, only: %i[index table]
6
+ before_action :set_resource, only: %i[show edit update destroy]
7
+ before_action :set_resources, only: %i[index]
8
8
 
9
9
  def index
10
10
  @pagy, @resources = pagy(@resources, limit: params[:per_page] || Oversee.configuration.per_page)
@@ -14,7 +14,7 @@ module Oversee
14
14
  resource_class: @resource_class,
15
15
  pagy: @pagy,
16
16
  params: params
17
- )
17
+ ), layout: false
18
18
  end
19
19
 
20
20
  def new
@@ -23,7 +23,7 @@ module Oversee
23
23
  resource: @resource,
24
24
  resource_class: @resource_class,
25
25
  params: params
26
- )
26
+ ), layout: false
27
27
  end
28
28
 
29
29
  def create
@@ -31,6 +31,7 @@ module Oversee
31
31
 
32
32
  respond_to do |format|
33
33
  if @resource.update(resource_params)
34
+ flash.now[:notice] = "Resource was successfully created."
34
35
  format.html { redirect_to resource_path(@resource.id, resource_class_name: @resource_class) }
35
36
  format.turbo_stream { redirect_to resource_path(@resource.id, resource_class_name: @resource_class), status: :see_other }
36
37
  else
@@ -46,7 +47,7 @@ module Oversee
46
47
  resource_class: @resource_class,
47
48
  resource_associations: resource_associations,
48
49
  params: params
49
- )
50
+ ), layout: false
50
51
  end
51
52
 
52
53
  def edit
@@ -58,10 +59,14 @@ module Oversee
58
59
 
59
60
  respond_to do |format|
60
61
  if @resource.update(resource_params)
62
+ flash.now[:notice] = "#{@resource.class.to_s.titleize.capitalize.gsub("::"," ")} was successfully updated!"
61
63
  format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
62
64
  format.turbo_stream do
63
- component = Oversee::Field::Display.new(resource: @resource, datatype:, key:, value: @resource.send(key))
64
- render turbo_stream: turbo_stream.replace(dom_id(@resource, key), component)
65
+ component = Oversee::Field::Set.new(resource: @resource, datatype:, key:, value: @resource.send(key))
66
+ render turbo_stream: [
67
+ turbo_stream.replace(dom_id(@resource, key), component),
68
+ turbo_stream.replace(:flash, Oversee::Flash)
69
+ ]
65
70
  end
66
71
  else
67
72
  end
@@ -73,25 +78,25 @@ module Oversee
73
78
  redirect_to resources_path(resource: @resource_class)
74
79
  end
75
80
 
76
- # Non-standard actions
77
- def input_field
78
- set_resource
79
-
80
- key = params[:key].to_sym
81
- value = @resource.send(key)
82
- datatype = @resource.class.columns_hash[key.to_s].type
83
-
84
- field_dom_id = dom_id(@resource, key)
85
- field = Oversee::Field::Form.new(resource: @resource, datatype:, key:, value:)
81
+ def association
82
+ @resources = @resource_class.find(params[:id]).send(params[:association_name])
83
+ @pagy, @resources = pagy(@resources, limit: params[:per_page] || Oversee.configuration.per_page)
86
84
 
87
- respond_to do |format|
88
- format.turbo_stream do
89
- render turbo_stream: turbo_stream.replace(field_dom_id, field)
90
- end
91
- end
85
+ render Oversee::Resources::Index.new(
86
+ resources: @resources,
87
+ resource_class: @resource_class,
88
+ pagy: @pagy,
89
+ params: params
90
+ )
92
91
  end
93
92
 
94
93
  def table
94
+ if params[:association_name].present?
95
+ @resources = @resource_class.find(params[:id]).send(params[:association_name])
96
+ else
97
+ set_resources
98
+ end
99
+
95
100
  component_id = dom_id(@resource_class, :table)
96
101
  component = Oversee::Resources::Table.new(resource_class: @resource_class, resources: @resources, params: params)
97
102
 
@@ -119,8 +124,8 @@ module Oversee
119
124
  set_sorting_rules
120
125
 
121
126
  @resources = @resource_class.order(@sort_attribute.to_sym => sort_direction)
122
- @resources = Filter.new(collection: @resources, params:).apply
123
- @resources = Search.new(collection: @resources, resource_class: @resource_class, query: params[:query]).call
127
+ @resources = Oversee::Filter.new(collection: @resources, params:).apply
128
+ @resources = Oversee::Search.new(collection: @resources, resource_class: @resource_class, query: params[:query]).call
124
129
  end
125
130
 
126
131
  def resource_associations
@@ -0,0 +1,3 @@
1
+ import "trix";
2
+ import "@rails/actiontext";
3
+ import "controllers";
@@ -0,0 +1,21 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["copyIcon", "successIcon"];
5
+ static values = { content: String };
6
+
7
+ async copy() {
8
+ try {
9
+ await navigator.clipboard.writeText(this.contentValue);
10
+ this.toggleIcons(true);
11
+ setTimeout(() => this.toggleIcons(false), 1500);
12
+ } catch (error) {
13
+ console.error(error.message);
14
+ }
15
+ }
16
+
17
+ toggleIcons(isCopied) {
18
+ this.copyIconTarget.classList.toggle("hidden", isCopied);
19
+ this.successIconTarget.classList.toggle("hidden", !isCopied);
20
+ }
21
+ }
@@ -0,0 +1,9 @@
1
+ import { Application } from "@hotwired/stimulus";
2
+
3
+ const application = Application.start();
4
+
5
+ application.debug = false;
6
+ window.Stimulus = application;
7
+
8
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
9
+ eagerLoadControllersFrom("controllers", application);
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ setTimeout(() => {
6
+ this.element.classList.remove("opacity-0");
7
+ }, 100);
8
+
9
+ setTimeout(() => {
10
+ this.element.classList.remove("opacity-100");
11
+ this.element.classList.add("opacity-0");
12
+ }, 2500);
13
+ setTimeout(() => {
14
+ this.element.outerHTML = "";
15
+ }, 3000);
16
+ }
17
+ }
@@ -0,0 +1,19 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["revealable"];
5
+ static values = { revealableId: String };
6
+
7
+ connect() {
8
+ if (this.hasRevealableIdValue)
9
+ this.target = document.getElementById(this.revealableIdValue);
10
+
11
+ if (!this.target && this.hasRevealableTarget)
12
+ this.target = this.revealableTarget;
13
+ }
14
+
15
+ toggle() {
16
+ if (!this.target) return;
17
+ this.target.classList.toggle("hidden");
18
+ }
19
+ }
@@ -0,0 +1,21 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ const KEY_BASE = "oversee-sidebar";
4
+
5
+ export default class extends Controller {
6
+ initialize() {
7
+ this.key = `${KEY_BASE}--${this.element.id}`;
8
+ this.expanded = localStorage.getItem(this.key) === "true";
9
+ this.element.open = this.expanded;
10
+ }
11
+
12
+ toggle() {
13
+ this.expanded = !this.expanded;
14
+ this.element.open = this.expanded;
15
+ }
16
+
17
+ persist(event) {
18
+ this.expanded = event.newState === "open";
19
+ localStorage.setItem(this.key, this.expanded);
20
+ }
21
+ }