rails_devtools 0.1.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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +92 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/devtools_manifest.js +1 -0
  5. data/app/assets/stylesheets/devtools/application.css +15 -0
  6. data/app/controllers/rails_devtools/application_controller.rb +4 -0
  7. data/app/controllers/rails_devtools/base_controller.rb +7 -0
  8. data/app/controllers/rails_devtools/database_tables_controller.rb +16 -0
  9. data/app/controllers/rails_devtools/frontend/modules_controller.rb +18 -0
  10. data/app/controllers/rails_devtools/gems_controller.rb +16 -0
  11. data/app/controllers/rails_devtools/host_app_images_controller.rb +35 -0
  12. data/app/controllers/rails_devtools/image_assets_controller.rb +43 -0
  13. data/app/controllers/rails_devtools/routes/route_path_inputs_controller.rb +23 -0
  14. data/app/controllers/rails_devtools/routes_controller.rb +21 -0
  15. data/app/forms/rails_devtools/database_table_search_form.rb +51 -0
  16. data/app/forms/rails_devtools/gem_search_form.rb +86 -0
  17. data/app/forms/rails_devtools/image_search_form.rb +30 -0
  18. data/app/forms/rails_devtools/route_search_form.rb +17 -0
  19. data/app/helpers/rails_devtools/application_helper.rb +4 -0
  20. data/app/javascript/application.js +4 -0
  21. data/app/javascript/controllers/application.js +10 -0
  22. data/app/javascript/controllers/checkbox_controller.js +9 -0
  23. data/app/javascript/controllers/index.js +11 -0
  24. data/app/javascript/controllers/search_reset_controller.js +7 -0
  25. data/app/javascript/controllers/turbo_form_controller.js +9 -0
  26. data/app/jobs/rails_devtools/application_job.rb +4 -0
  27. data/app/mailers/rails_devtools/application_mailer.rb +6 -0
  28. data/app/models/rails_devtools/application_record.rb +5 -0
  29. data/app/models/rails_devtools/image_assets/image_info.rb +85 -0
  30. data/app/models/rails_devtools/routes/collection.rb +83 -0
  31. data/app/models/rails_devtools/routes/controller_info.rb +30 -0
  32. data/app/models/rails_devtools/routes/engine_info.rb +33 -0
  33. data/app/models/rails_devtools/routes/route_info.rb +120 -0
  34. data/app/views/rails_devtools/application_layout.rb +90 -0
  35. data/app/views/rails_devtools/application_view.rb +6 -0
  36. data/app/views/rails_devtools/components/application_component.rb +24 -0
  37. data/app/views/rails_devtools/components/flash_message.rb +29 -0
  38. data/app/views/rails_devtools/components/lucide/base.rb +18 -0
  39. data/app/views/rails_devtools/components/lucide/close.rb +25 -0
  40. data/app/views/rails_devtools/components/lucide/database.rb +26 -0
  41. data/app/views/rails_devtools/components/lucide/external_link.rb +26 -0
  42. data/app/views/rails_devtools/components/lucide/images.rb +27 -0
  43. data/app/views/rails_devtools/components/lucide/menu.rb +26 -0
  44. data/app/views/rails_devtools/components/lucide/package.rb +30 -0
  45. data/app/views/rails_devtools/components/lucide/pocket_knife.rb +28 -0
  46. data/app/views/rails_devtools/components/lucide/sign_post.rb +29 -0
  47. data/app/views/rails_devtools/components/lucide/trash.rb +26 -0
  48. data/app/views/rails_devtools/components/lucide/triangle_alert.rb +29 -0
  49. data/app/views/rails_devtools/components/page_content.rb +27 -0
  50. data/app/views/rails_devtools/components/ui/drawer.rb +49 -0
  51. data/app/views/rails_devtools/components/ui/menu.rb +81 -0
  52. data/app/views/rails_devtools/components/ui/search_form.rb +65 -0
  53. data/app/views/rails_devtools/components.rb +5 -0
  54. data/app/views/rails_devtools/database_tables/index.rb +32 -0
  55. data/app/views/rails_devtools/database_tables/table_card.rb +61 -0
  56. data/app/views/rails_devtools/gems/gem_card.rb +74 -0
  57. data/app/views/rails_devtools/gems/index.rb +32 -0
  58. data/app/views/rails_devtools/image_assets/image_card.rb +37 -0
  59. data/app/views/rails_devtools/image_assets/image_details.rb +82 -0
  60. data/app/views/rails_devtools/image_assets/index.rb +39 -0
  61. data/app/views/rails_devtools/routes/index.rb +37 -0
  62. data/app/views/rails_devtools/routes/route_card.rb +70 -0
  63. data/app/views/rails_devtools/routes/route_details/controller_card.rb +87 -0
  64. data/app/views/rails_devtools/routes/route_details/route_path_input.rb +46 -0
  65. data/app/views/rails_devtools/routes/route_details.rb +171 -0
  66. data/config/routes.rb +24 -0
  67. data/lib/rails_devtools/asset_config.rb +52 -0
  68. data/lib/rails_devtools/asset_providers/jsbundling_rails_config.rb +21 -0
  69. data/lib/rails_devtools/asset_providers/propshaft_config.rb +19 -0
  70. data/lib/rails_devtools/asset_providers/shakapacker_config.rb +26 -0
  71. data/lib/rails_devtools/asset_providers/sprocket_config.rb +19 -0
  72. data/lib/rails_devtools/asset_providers/vite_rails_config.rb +19 -0
  73. data/lib/rails_devtools/engine.rb +22 -0
  74. data/lib/rails_devtools/importmap.rb +16 -0
  75. data/lib/rails_devtools/importmaps/base.rb +82 -0
  76. data/lib/rails_devtools/version.rb +3 -0
  77. data/lib/rails_devtools.rb +20 -0
  78. data/lib/tasks/rails_devtools_tasks.rake +4 -0
  79. data/vendor/javascript/@stimulus-components--clipboard.js +4 -0
  80. data/vendor/javascript/@stimulus-components--notification.js +4 -0
  81. data/vendor/javascript/@stimulus-components--reveal.js +4 -0
  82. data/vendor/javascript/stimulus-autoloader.js +54 -0
  83. data/vendor/javascript/stimulus-importmap-autoloader.js +27 -0
  84. data/vendor/javascript/stimulus-loading.js +93 -0
  85. data/vendor/javascript/stimulus-use.js +4 -0
  86. data/vendor/javascript/stimulus.min.js +5 -0
  87. data/vendor/javascript/turbo.min.js +36 -0
  88. metadata +241 -0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ module Components
5
+ class Lucide::PocketKnife < Lucide::Base
6
+ def view_template
7
+ svg(
8
+ xmlns: "http://www.w3.org/2000/svg",
9
+ width: width,
10
+ height: height,
11
+ viewbox: "0 0 24 24",
12
+ fill: "none",
13
+ stroke: "currentColor",
14
+ stroke_width: "2",
15
+ stroke_linecap: "round",
16
+ stroke_linejoin: "round",
17
+ class: "lucide lucide-pocket-knife"
18
+ ) do |s|
19
+ s.path(d: "M3 2v1c0 1 2 1 2 2S3 6 3 7s2 1 2 2-2 1-2 2 2 1 2 2")
20
+ s.path(d: "M18 6h.01")
21
+ s.path(d: "M6 18h.01")
22
+ s.path(d: "M20.83 8.83a4 4 0 0 0-5.66-5.66l-12 12a4 4 0 1 0 5.66 5.66Z")
23
+ s.path(d: "M18 11.66V22a4 4 0 0 0 4-4V6")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ module Components
5
+ class Lucide::SignPost < Lucide::Base
6
+ def view_template
7
+ svg(
8
+ xmlns: "http://www.w3.org/2000/svg",
9
+ width: width,
10
+ height: height,
11
+ viewbox: "0 0 24 24",
12
+ fill: "none",
13
+ stroke: "currentColor",
14
+ stroke_width: "2",
15
+ stroke_linecap: "round",
16
+ stroke_linejoin: "round",
17
+ class: "lucide lucide-signpost"
18
+ ) do |s|
19
+ s.path(d: "M12 13v8")
20
+ s.path(d: "M12 3v3")
21
+ s.path(
22
+ d:
23
+ "M18 6a2 2 0 0 1 1.387.56l2.307 2.22a1 1 0 0 1 0 1.44l-2.307 2.22A2 2 0 0 1 18 13H6a2 2 0 0 1-1.387-.56l-2.306-2.22a1 1 0 0 1 0-1.44l2.306-2.22A2 2 0 0 1 6 6z"
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ module Components
5
+ class Lucide::Trash < Lucide::Base
6
+ def view_template
7
+ svg(
8
+ xmlns: "http://www.w3.org/2000/svg",
9
+ width: width,
10
+ height: height,
11
+ viewbox: "0 0 24 24",
12
+ fill: "none",
13
+ stroke: "currentColor",
14
+ stroke_width: "2",
15
+ stroke_linecap: "round",
16
+ stroke_linejoin: "round",
17
+ class: "lucide lucide-trash"
18
+ ) do |s|
19
+ s.path(d: "M3 6h18")
20
+ s.path(d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6")
21
+ s.path(d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ module Components
5
+ class Lucide::TriangleAlert < Lucide::Base
6
+ def view_template
7
+ svg(
8
+ xmlns: "http://www.w3.org/2000/svg",
9
+ width: width,
10
+ height: height,
11
+ viewbox: "0 0 24 24",
12
+ fill: "none",
13
+ stroke: "currentColor",
14
+ stroke_width: "2",
15
+ stroke_linecap: "round",
16
+ stroke_linejoin: "round",
17
+ class: "lucide lucide-triangle-alert"
18
+ ) do |s|
19
+ s.path(
20
+ d:
21
+ "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"
22
+ )
23
+ s.path(d: "M12 9v4")
24
+ s.path(d: "M12 17h.01")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ module Components
5
+ class PageContent < Components::ApplicationComponent
6
+ def view_template(&)
7
+ turbo_frame_tag("page_content", &)
8
+ end
9
+
10
+ def page_title(&)
11
+ h1(class: "text-2xl font-bold", &)
12
+ end
13
+
14
+ def search_form(form:, path:, method: :get)
15
+ render Components::Ui::SearchForm.new(
16
+ form: form,
17
+ path: path,
18
+ method: method
19
+ )
20
+ end
21
+
22
+ def results(&)
23
+ div(class: "mt-4", &)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ module Components
5
+ class Ui::Drawer < Components::ApplicationComponent
6
+ def initialize(id:, direction: "left", classes: "")
7
+ @id = id
8
+ @direction = direction
9
+ @classes = classes
10
+ end
11
+
12
+ def view_template(&block)
13
+ div(class: drawer_classes, data_controller: "checkbox") do
14
+ input(id: @id, type: "checkbox", class: "drawer-toggle", data_checkbox_target: "checkbox")
15
+ block.call
16
+ end
17
+ end
18
+
19
+ def content(&)
20
+ div(class: "drawer-content flex flex-col", &)
21
+ end
22
+
23
+ def drawer_side(&block)
24
+ div(class: "drawer-side") do
25
+ label(
26
+ for: @id,
27
+ aria_label: "close sidebar",
28
+ class: "drawer-overlay"
29
+ )
30
+ block.call
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def drawer_classes
37
+ [
38
+ "drawer",
39
+ direction_class,
40
+ @classes
41
+ ].join(" ")
42
+ end
43
+
44
+ def direction_class
45
+ @direction == "left" ? "" : "drawer-end"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ class Components::Ui::Menu < Components::ApplicationComponent
5
+ include Phlex::Rails::Helpers::CurrentPage
6
+
7
+ def view_template
8
+ large_screen_menu
9
+ small_screen_menu
10
+ end
11
+
12
+ private
13
+
14
+ def large_screen_menu
15
+ div(class: "hidden lg:block h-full") do
16
+ menu_items(classes: "w-60 p-4") do
17
+ div(class: "border-b-2 border-base-300 pl-4 pb-4 mb-2") do
18
+ menu_title
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def small_screen_menu
25
+ div(
26
+ data_controller: "reveal",
27
+ data_reveal_hidden_class: "hidden",
28
+ class: "lg:hidden w-full p-3 bg-base-200"
29
+ ) do
30
+ div(class: "flex flex-col") do
31
+ div(class: "flex justify-between") do
32
+ menu_title
33
+ button(data_action: " click->reveal#toggle", type: "button") do
34
+ render Components::Lucide::Menu
35
+ end
36
+ end
37
+ end
38
+ div(
39
+ data_reveal_target: "item",
40
+ class: "hidden mt-2 pl-3 border-t-2 border-base-300 "
41
+ ) do
42
+ menu_items(classes: "")
43
+ end
44
+ end
45
+ end
46
+
47
+ def menu_title
48
+ div(class: "flex gap-3") do
49
+ render Components::Lucide::PocketKnife
50
+ h2(class: "font-bold text-xl") { "Devtools" }
51
+ end
52
+ end
53
+
54
+ MenuItem = Data.define(:icon, :name, :path)
55
+
56
+ def menu_items(classes:, &block)
57
+ ul(class: [classes, "menu bg-base-200 text-base-content h-full"]) do
58
+ block.call if block_given?
59
+
60
+ items.each do |menu_item|
61
+ item = MenuItem.new(**menu_item)
62
+ li do
63
+ a(href: item.path, class: current_page?(item.path) && "active") do
64
+ span(class: "mr-1") { render item.icon.new(width: 16, height: 16) }
65
+ span { item.name }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def items
73
+ [
74
+ { icon: Lucide::Database, name: "Database tables", path: helpers.database_tables_path },
75
+ { icon: Lucide::Package, name: "Gems", path: helpers.gems_path },
76
+ { icon: Lucide::SignPost, name: "Routes", path: helpers.routes_path },
77
+ { icon: Lucide::Images, name: "Image assets", path: helpers.image_assets_path }
78
+ ]
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ module Components
5
+ class Ui::SearchForm < Components::ApplicationComponent
6
+ include Phlex::Rails::Helpers::FormWith
7
+ include Phlex::Rails::Helpers::Request
8
+
9
+ def initialize(form:, path:, method:)
10
+ @form = form
11
+ @path = path
12
+ @method = method
13
+ end
14
+
15
+ def view_template
16
+ div(class: "flex gap-2 mt-4") do
17
+ form_with(
18
+ model: @form,
19
+ url: @path,
20
+ method: @method,
21
+ data: { turbo_action: :advance },
22
+ class: "w-full max-w-sm"
23
+ ) do |form|
24
+ label(class: "input input-bordered flex items-center gap-2 w-full grow") do
25
+ form.text_field(:search,
26
+ { class: " w-full ", placeholder: "Type search then enter", value: search_params })
27
+ search_icon
28
+ end
29
+ end
30
+
31
+ reset_button if search_params.present?
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def reset_button
38
+ link_to(@path, class: "btn text-neutral", data: { turbo_action: :advance }) do
39
+ render Components::Lucide::Close.new(width: 16, height: 16)
40
+ plain "Reset"
41
+ end
42
+ end
43
+
44
+ def search_params
45
+ request.params.dig(@form.model_name.param_key.to_sym, :search)
46
+ end
47
+
48
+ def search_icon
49
+ svg(
50
+ xmlns: "http://www.w3.org/2000/svg",
51
+ viewbox: "0 0 16 16",
52
+ fill: "currentColor",
53
+ class: "h-4 w-4 opacity-70"
54
+ ) do |s|
55
+ s.path(
56
+ fill_rule: "evenodd",
57
+ d:
58
+ "M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z",
59
+ clip_rule: "evenodd"
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,5 @@
1
+ module RailsDevtools
2
+ module Components
3
+ extend Phlex::Kit
4
+ end
5
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ class DatabaseTables::Index < ApplicationView
5
+ def initialize(form: nil, tables: [])
6
+ @tables = tables
7
+ @form = form
8
+ end
9
+
10
+ def view_template
11
+ render Components::PageContent.new do |page|
12
+ page.page_title { "Database tables" }
13
+ page.search_form(form: @form, path: helpers.database_tables_path)
14
+ page.results { results }
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def results
21
+ if @tables.empty?
22
+ div(class: "text-neutral") { "No results found" }
23
+ else
24
+ div(class: "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 3xl:grid-cols-4 gap-2 w-full items-start") do
25
+ @tables.each do |table|
26
+ render DatabaseTables::TableCard.new(table: table)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ class DatabaseTables::TableCard < Components::ApplicationComponent
5
+ def initialize(table:)
6
+ @table = table
7
+ end
8
+
9
+ def view_template
10
+ div(class: "card bg-white text-sm w-full shadow-sm") do
11
+ div(class: "card-body") do
12
+ h2(class: "card-title") { @table.table_name.capitalize }
13
+ columns_list
14
+ indexes_list
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def columns_list
22
+ div(class: "flex flex-col gap-2 divide-y divide-base-200") do
23
+ @table.columns.each do |column|
24
+ div(class: "flex gap-x-2 text-neutral pt-2 justify-between items-center") do
25
+ div(class: "flex gap-x-2") do
26
+ div(class: "font-bold") { column.name }
27
+ div(class: "text-neutral italic") { column.type }
28
+ end
29
+ span(class: "text-xs opacity-75") { column.default } if column.default
30
+ div(class: "text-xs opacity-75") { "not null" } unless column.null
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def indexes_list
37
+ div(class: "mt-8") do
38
+ h3(class: "text-lg font-bold") { "#{@table.table_name.capitalize} indexes" }
39
+ div(class: "mt-2 flex flex-col gap-2 divide-y divide-base-200") do
40
+ @table.indexes.each do |index|
41
+ div(class: "flex gap-x-2 text-neutral pt-2 justify-between items-center") do
42
+ div do
43
+ div(class: "font-bold") { index.name }
44
+ div(class: "text-xs opacity-75") do
45
+ [index.unique ? "unique" : nil, index_columns_text(index.columns)].compact.join(" ")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def index_columns_text(columns)
55
+ columns_list = columns.join(", ")
56
+ return columns_list if columns.size == 1
57
+
58
+ "composite of #{columns_list}"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ class Gems::GemCard < Components::ApplicationComponent
5
+ include Phlex::Rails::Helpers::DistanceOfTimeInWords
6
+
7
+ def initialize(gem:)
8
+ @gem = gem
9
+ end
10
+
11
+ def view_template
12
+ div(class: "card card-compact bg-white text-sm w-full shadow-sm") do
13
+ div(class: "card-body") do
14
+ div(class: "card-actions") do
15
+ @gem.groups.each do |group|
16
+ div(class: "badge badge-sm") { group }
17
+ end
18
+ end
19
+ h2(class: "inline-flex card-title justify-between items-center") do
20
+ div(class: "flex items-center gap-2") do
21
+ span do
22
+ [@gem.name.titleize.capitalize, @gem.actual_version].join(" ")
23
+ end
24
+ span(class: "badge badge-sm badge-warning font-normal") { "outdated" } if @gem.outdated?
25
+ end
26
+ span(class: "text-sm text-neutral opacity-75 font-normal") do
27
+ @gem.date.strftime("%b %d, %Y")
28
+ end
29
+ end
30
+
31
+ div(class: "text-neutral opacity-75") do
32
+ outdated_info
33
+ div { @gem.summary }
34
+ end
35
+ div(class: "card-actions mt-4 justify-end") do
36
+ if @gem.source_code
37
+ a(href: @gem.source_code, class: "btn btn-xs btn-outline btn-secondary") do
38
+ "Source Code"
39
+ end
40
+ end
41
+ if @gem.documentation
42
+ a(href: @gem.documentation, class: "btn btn-xs btn-outline btn-secondary") do
43
+ "Documentation"
44
+ end
45
+ end
46
+ a(href: @gem.homepage, class: "btn btn-xs") { "Homepage" } if @gem.homepage
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def outdated_info
55
+ return unless @gem.outdated?
56
+
57
+ div(class: "alert mb-4 flex justify-between") do
58
+ div do
59
+ h4(class: "font-bold text-left") { "Version #{@gem.latest_version.version} available" }
60
+ p(class: "text-left") do
61
+ "Your version came out #{months_since_last_update} months before the current release"
62
+ end
63
+ end
64
+ div do
65
+ a(href: @gem.changelog, class: "btn btn-sm btn-secondary") { "Changelog" } if @gem.changelog
66
+ end
67
+ end
68
+ end
69
+
70
+ def months_since_last_update
71
+ (@gem.latest_version.date - @gem.date).seconds.in_months.to_i
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ class Gems::Index < ApplicationView
5
+ def initialize(form: nil, gems: [])
6
+ @gems = gems
7
+ @form = form
8
+ end
9
+
10
+ def view_template
11
+ render Components::PageContent.new do |page|
12
+ page.page_title { "Gems" }
13
+ page.search_form(form: @form, path: helpers.gems_path)
14
+ page.results { results }
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def results
21
+ if @gems.empty?
22
+ div(class: "text-neutral") { "No results found" }
23
+ else
24
+ div(class: "w-full flex flex-col gap-y-2") do
25
+ @gems.each do |gem|
26
+ render Gems::GemCard.new(gem: gem)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ class ImageAssets::ImageCard < Components::ApplicationComponent
5
+ def initialize(image_info:)
6
+ @image_info = image_info
7
+ end
8
+
9
+ def view_template
10
+ turbo_frame_tag(@image_info.full_path) do
11
+ a(
12
+ href: helpers.image_asset_path(
13
+ @image_info.name,
14
+ full_name: @image_info.basename,
15
+ image_path: @image_info.full_path
16
+ ),
17
+ data: { turbo_frame: 'drawer_content', action: 'click->checkbox#toggle' },
18
+ class: 'group'
19
+ ) do
20
+ div(class: 'card card-compact bg-white shadow-sm group-hover:bg-primary w-[150px]') do
21
+ figure do
22
+ img(
23
+ src: helpers.host_app_image_path(@image_info.devtools_image_path),
24
+ class: 'card-image',
25
+ width: '150'
26
+ )
27
+ end
28
+
29
+ div(class: 'card-body group-hover:text-primary-content') do
30
+ p(class: 'text-xs truncate') { @image_info.basename }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDevtools
4
+ class ImageAssets::ImageDetails < Components::ApplicationComponent
5
+ def initialize(image_info:)
6
+ @image_info = image_info
7
+ end
8
+
9
+ def view_template
10
+ turbo_frame_tag("drawer_content", class: "flex flex-col") do
11
+ figure do
12
+ img(
13
+ src: helpers.host_app_image_path(@image_info.devtools_image_path),
14
+ width: "400"
15
+ )
16
+ end
17
+
18
+ div(class: "mt-4") do
19
+ h3(class: "text-lg font-bold") { @image_info.basename }
20
+ div(class: "mt-4 pt-4 border-t-2 border-base-300 grid grid-cols-3 gap-x-4 gap-y-2 mt-2 text-sm text-neutral") do
21
+ # Image size
22
+ div(class: "text-right font-bold ") { "Image size" }
23
+ div(class: "col-span-2") { "#{@image_info.width} x #{@image_info.height}" }
24
+
25
+ # File size
26
+ div(class: "text-right font-bold text-neutral") { "File size" }
27
+ div(class: "col-span-2") { bytes_to_kb(@image_info.file_size) }
28
+ end
29
+ end
30
+
31
+ image_tag_input
32
+
33
+ div(class: "mt-8 pt-8 border-t-2 border-base-300 flex gap-x-2 justify-end") do
34
+ delete_button
35
+ end
36
+ end
37
+ end
38
+
39
+ def image_tag_input
40
+ div(class: "mt-8 w-full") do
41
+ div(
42
+ class: "join w-full",
43
+ data_controller: "clipboard",
44
+ data_clipboard_success_content_value: "Copied!"
45
+ ) do
46
+ input(
47
+ value: @image_info.image_helper_snippet,
48
+ class: "input input-bordered input-primary input-sm w-full join-item",
49
+ data_clipboard_target: "source"
50
+ )
51
+ button(
52
+ class: "btn btn-primary btn-outline btn-sm join-item",
53
+ data_action: "clipboard#copy",
54
+ data_clipboard_target: "button"
55
+ ) { "Copy" }
56
+ end
57
+ end
58
+ end
59
+
60
+ def delete_button
61
+ button_to(
62
+ helpers.image_asset_path(@image_info.name, image_path: @image_info.full_path),
63
+ class: "btn btn-outline btn-error btn-sm",
64
+ method: :delete,
65
+ form: { data: {
66
+ turbo_confirm: "Are you sure you want to delete this image?",
67
+ action: "turbo:submit-end->checkbox#toggle"
68
+ } }
69
+ ) do
70
+ span { render Components::Lucide::Trash.new(width: 16, height: 16) }
71
+ plain "delete"
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def bytes_to_kb(bytes)
78
+ kb = bytes.to_f / 1024
79
+ "#{kb.round(2)} KB"
80
+ end
81
+ end
82
+ end