easy-admin-rails 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/builds/easy_admin.base.js +43505 -0
- data/app/assets/builds/easy_admin.base.js.map +7 -0
- data/app/assets/builds/easy_admin.css +6141 -0
- data/app/assets/config/easy_admin_manifest.js +1 -0
- data/app/assets/images/jsoneditor-icons.svg +749 -0
- data/app/assets/stylesheets/easy_admin/application.tailwind.css +390 -0
- data/app/components/easy_admin/base_component.rb +35 -0
- data/app/components/easy_admin/batch_action_bar_component.rb +125 -0
- data/app/components/easy_admin/batch_action_form_component.rb +124 -0
- data/app/components/easy_admin/combined_filters_component.rb +232 -0
- data/app/components/easy_admin/confirmation_modal_component.rb +61 -0
- data/app/components/easy_admin/context_menu_component.rb +161 -0
- data/app/components/easy_admin/dashboards/base_card_component.rb +152 -0
- data/app/components/easy_admin/dashboards/card_error_component.rb +23 -0
- data/app/components/easy_admin/dashboards/card_factory.rb +90 -0
- data/app/components/easy_admin/dashboards/card_stream_component.rb +22 -0
- data/app/components/easy_admin/dashboards/cards/base_card_component.rb +54 -0
- data/app/components/easy_admin/dashboards/cards/chart_card_component.rb +175 -0
- data/app/components/easy_admin/dashboards/cards/custom_card_component.rb +50 -0
- data/app/components/easy_admin/dashboards/cards/metric_card_component.rb +164 -0
- data/app/components/easy_admin/dashboards/cards/table_card_component.rb +148 -0
- data/app/components/easy_admin/dashboards/chart_card_component.rb +44 -0
- data/app/components/easy_admin/dashboards/metric_card_component.rb +56 -0
- data/app/components/easy_admin/dashboards/refresh_stream_component.rb +279 -0
- data/app/components/easy_admin/dashboards/show_component.rb +163 -0
- data/app/components/easy_admin/dashboards/table_card_component.rb +52 -0
- data/app/components/easy_admin/date_picker_component.rb +188 -0
- data/app/components/easy_admin/fields/base_component.rb +101 -0
- data/app/components/easy_admin/fields/belongs_to_edit_modal_component.rb +117 -0
- data/app/components/easy_admin/fields/form/belongs_to_component.rb +82 -0
- data/app/components/easy_admin/fields/form/boolean_component.rb +100 -0
- data/app/components/easy_admin/fields/form/date_component.rb +55 -0
- data/app/components/easy_admin/fields/form/datetime_component.rb +55 -0
- data/app/components/easy_admin/fields/form/email_component.rb +55 -0
- data/app/components/easy_admin/fields/form/file_component.rb +190 -0
- data/app/components/easy_admin/fields/form/has_many_component.rb +416 -0
- data/app/components/easy_admin/fields/form/json_component.rb +81 -0
- data/app/components/easy_admin/fields/form/number_component.rb +55 -0
- data/app/components/easy_admin/fields/form/select_component.rb +326 -0
- data/app/components/easy_admin/fields/form/text_component.rb +55 -0
- data/app/components/easy_admin/fields/form/textarea_component.rb +54 -0
- data/app/components/easy_admin/fields/index/belongs_to_component.rb +93 -0
- data/app/components/easy_admin/fields/index/boolean_component.rb +29 -0
- data/app/components/easy_admin/fields/index/date_component.rb +13 -0
- data/app/components/easy_admin/fields/index/datetime_component.rb +13 -0
- data/app/components/easy_admin/fields/index/email_component.rb +24 -0
- data/app/components/easy_admin/fields/index/filters/base_component.rb +48 -0
- data/app/components/easy_admin/fields/index/filters/boolean_component.rb +96 -0
- data/app/components/easy_admin/fields/index/filters/date_component.rb +182 -0
- data/app/components/easy_admin/fields/index/filters/number_component.rb +30 -0
- data/app/components/easy_admin/fields/index/filters/select_component.rb +101 -0
- data/app/components/easy_admin/fields/index/filters/string_component.rb +32 -0
- data/app/components/easy_admin/fields/index/json_component.rb +23 -0
- data/app/components/easy_admin/fields/index/number_component.rb +20 -0
- data/app/components/easy_admin/fields/index/select_component.rb +25 -0
- data/app/components/easy_admin/fields/index/text_component.rb +20 -0
- data/app/components/easy_admin/fields/inline_edit_modal_component.rb +135 -0
- data/app/components/easy_admin/fields/inline_edit_trigger_component.rb +144 -0
- data/app/components/easy_admin/fields/show/belongs_to_component.rb +93 -0
- data/app/components/easy_admin/fields/show/boolean_component.rb +21 -0
- data/app/components/easy_admin/fields/show/date_component.rb +13 -0
- data/app/components/easy_admin/fields/show/datetime_component.rb +13 -0
- data/app/components/easy_admin/fields/show/email_component.rb +19 -0
- data/app/components/easy_admin/fields/show/file_component.rb +304 -0
- data/app/components/easy_admin/fields/show/has_many_component.rb +192 -0
- data/app/components/easy_admin/fields/show/json_component.rb +45 -0
- data/app/components/easy_admin/fields/show/number_component.rb +20 -0
- data/app/components/easy_admin/fields/show/select_component.rb +25 -0
- data/app/components/easy_admin/fields/show/text_component.rb +17 -0
- data/app/components/easy_admin/fields/show/textarea_component.rb +26 -0
- data/app/components/easy_admin/filters_component.rb +120 -0
- data/app/components/easy_admin/form_tabs_component.rb +166 -0
- data/app/components/easy_admin/infinite_scroll_component.rb +82 -0
- data/app/components/easy_admin/lazy_chart_card_component.rb +128 -0
- data/app/components/easy_admin/lazy_metric_card_component.rb +76 -0
- data/app/components/easy_admin/modal_frame_component.rb +26 -0
- data/app/components/easy_admin/navbar_component.rb +226 -0
- data/app/components/easy_admin/notification_component.rb +83 -0
- data/app/components/easy_admin/pagination_component.rb +188 -0
- data/app/components/easy_admin/quick_filters_component.rb +65 -0
- data/app/components/easy_admin/resource_pagination_component.rb +14 -0
- data/app/components/easy_admin/resources/index_component.rb +211 -0
- data/app/components/easy_admin/resources/index_frame_component.rb +88 -0
- data/app/components/easy_admin/resources/show_page_actions_component.rb +324 -0
- data/app/components/easy_admin/resources/table_cell_component.rb +145 -0
- data/app/components/easy_admin/resources/table_component.rb +206 -0
- data/app/components/easy_admin/resources/table_row_component.rb +160 -0
- data/app/components/easy_admin/row_action_form_component.rb +127 -0
- data/app/components/easy_admin/scopes_component.rb +224 -0
- data/app/components/easy_admin/settings_sidebar_component.rb +140 -0
- data/app/components/easy_admin/show_layout_component.rb +600 -0
- data/app/components/easy_admin/sidebar_component.rb +174 -0
- data/app/components/easy_admin/turbo/response_component.rb +40 -0
- data/app/components/easy_admin/turbo/stream_component.rb +28 -0
- data/app/controllers/easy_admin/application_controller.rb +66 -0
- data/app/controllers/easy_admin/batch_actions_controller.rb +166 -0
- data/app/controllers/easy_admin/confirmation_modal_controller.rb +20 -0
- data/app/controllers/easy_admin/dashboard_controller.rb +6 -0
- data/app/controllers/easy_admin/dashboards_controller.rb +123 -0
- data/app/controllers/easy_admin/passwords_controller.rb +15 -0
- data/app/controllers/easy_admin/registrations_controller.rb +52 -0
- data/app/controllers/easy_admin/resources_controller.rb +907 -0
- data/app/controllers/easy_admin/row_actions_controller.rb +216 -0
- data/app/controllers/easy_admin/sessions_controller.rb +32 -0
- data/app/controllers/easy_admin/settings_controller.rb +94 -0
- data/app/helpers/easy_admin/application_helper.rb +4 -0
- data/app/helpers/easy_admin/dashboards_helper.rb +121 -0
- data/app/helpers/easy_admin/fields_helper.rb +27 -0
- data/app/helpers/easy_admin/pagy_helper.rb +30 -0
- data/app/helpers/easy_admin/resources_helper.rb +39 -0
- data/app/javascript/easy_admin/application.js +12 -0
- data/app/javascript/easy_admin/controllers/batch_modal_controller.js +66 -0
- data/app/javascript/easy_admin/controllers/batch_selection_controller.js +223 -0
- data/app/javascript/easy_admin/controllers/chart_controller.js +216 -0
- data/app/javascript/easy_admin/controllers/collapsible_filters_controller.js +118 -0
- data/app/javascript/easy_admin/controllers/confirmation_modal_controller.js +64 -0
- data/app/javascript/easy_admin/controllers/context_menu_controller.js +227 -0
- data/app/javascript/easy_admin/controllers/date_picker_controller.js +309 -0
- data/app/javascript/easy_admin/controllers/dropdown_controller.js +63 -0
- data/app/javascript/easy_admin/controllers/event_emitter_controller.js +19 -0
- data/app/javascript/easy_admin/controllers/file_controller.js +121 -0
- data/app/javascript/easy_admin/controllers/form_tabs_controller.js +100 -0
- data/app/javascript/easy_admin/controllers/has_many_search_controller.js +76 -0
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +174 -0
- data/app/javascript/easy_admin/controllers/ios_alert_controller.js +195 -0
- data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +88 -0
- data/app/javascript/easy_admin/controllers/modal_controller.js +75 -0
- data/app/javascript/easy_admin/controllers/navbar_scroll_controller.js +76 -0
- data/app/javascript/easy_admin/controllers/notification_controller.js +48 -0
- data/app/javascript/easy_admin/controllers/row_action_controller.js +124 -0
- data/app/javascript/easy_admin/controllers/row_modal_controller.js +59 -0
- data/app/javascript/easy_admin/controllers/select_field_controller.js +618 -0
- data/app/javascript/easy_admin/controllers/settings_button_controller.js +8 -0
- data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +186 -0
- data/app/javascript/easy_admin/controllers/sidebar_controller.js +102 -0
- data/app/javascript/easy_admin/controllers/sidebar_mobile_controller.js +23 -0
- data/app/javascript/easy_admin/controllers/sidebar_nav_controller.js +96 -0
- data/app/javascript/easy_admin/controllers/table_controller.js +28 -0
- data/app/javascript/easy_admin/controllers/table_row_controller.js +16 -0
- data/app/javascript/easy_admin/controllers/toggle_switch_controller.js +22 -0
- data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +9 -0
- data/app/javascript/easy_admin/controllers.js +54 -0
- data/app/javascript/easy_admin.base.js +4 -0
- data/app/models/easy_admin/admin_user.rb +53 -0
- data/app/models/easy_admin/application_record.rb +5 -0
- data/app/views/easy_admin/dashboard/index.html.erb +3 -0
- data/app/views/easy_admin/dashboards/show.html.erb +7 -0
- data/app/views/easy_admin/passwords/edit.html.erb +42 -0
- data/app/views/easy_admin/passwords/new.html.erb +41 -0
- data/app/views/easy_admin/registrations/new.html.erb +65 -0
- data/app/views/easy_admin/resources/_redirect.turbo_stream.erb +3 -0
- data/app/views/easy_admin/resources/_table_rows.html.erb +46 -0
- data/app/views/easy_admin/resources/edit.html.erb +151 -0
- data/app/views/easy_admin/resources/index.html.erb +12 -0
- data/app/views/easy_admin/resources/index.turbo_stream.erb +139 -0
- data/app/views/easy_admin/resources/index_frame.html.erb +142 -0
- data/app/views/easy_admin/resources/new.html.erb +100 -0
- data/app/views/easy_admin/resources/show.html.erb +31 -0
- data/app/views/easy_admin/sessions/new.html.erb +55 -0
- data/app/views/easy_admin/settings/_form.html.erb +51 -0
- data/app/views/easy_admin/settings/index.html.erb +53 -0
- data/app/views/layouts/easy_admin/application.html.erb +48 -0
- data/app/views/layouts/easy_admin/auth.html.erb +34 -0
- data/config/initializers/easy_admin_card_factory.rb +27 -0
- data/config/initializers/pagy.rb +15 -0
- data/config/initializers/rack_mini_profiler.rb +67 -0
- data/config/routes.rb +70 -0
- data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +45 -0
- data/lib/easy-admin.rb +32 -0
- data/lib/easy_admin/action.rb +159 -0
- data/lib/easy_admin/batch_action.rb +134 -0
- data/lib/easy_admin/configuration.rb +75 -0
- data/lib/easy_admin/dashboard.rb +110 -0
- data/lib/easy_admin/dashboard_registry.rb +30 -0
- data/lib/easy_admin/delete_action.rb +22 -0
- data/lib/easy_admin/engine.rb +54 -0
- data/lib/easy_admin/field.rb +118 -0
- data/lib/easy_admin/resource.rb +806 -0
- data/lib/easy_admin/resource_registry.rb +22 -0
- data/lib/easy_admin/types/json_type.rb +25 -0
- data/lib/easy_admin/version.rb +3 -0
- data/lib/generators/easy_admin/auth_generator.rb +69 -0
- data/lib/generators/easy_admin/card/card_generator.rb +94 -0
- data/lib/generators/easy_admin/card/templates/card_component.rb.erb +127 -0
- data/lib/generators/easy_admin/card/templates/card_component_spec.rb.erb +122 -0
- data/lib/generators/easy_admin/install/templates/easy_admin.rb +31 -0
- data/lib/generators/easy_admin/install_generator.rb +25 -0
- data/lib/generators/easy_admin/rbac/rbac_generator.rb +244 -0
- data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +23 -0
- data/lib/generators/easy_admin/rbac/templates/super_admin.rb +34 -0
- data/lib/generators/easy_admin/resource_generator.rb +43 -0
- data/lib/generators/easy_admin/templates/AUTH_README +35 -0
- data/lib/generators/easy_admin/templates/README +27 -0
- data/lib/generators/easy_admin/templates/create_easy_admin_admin_users.rb +45 -0
- data/lib/generators/easy_admin/templates/devise.rb +267 -0
- data/lib/generators/easy_admin/templates/easy_admin.rb +24 -0
- data/lib/generators/easy_admin/templates/resource.rb +29 -0
- data/lib/tasks/easy_admin_tasks.rake +4 -0
- metadata +445 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
class InlineEditTriggerComponent < EasyAdmin::BaseComponent
|
4
|
+
def initialize(record:, field:, position: :right)
|
5
|
+
@record = record
|
6
|
+
@field = field
|
7
|
+
@position = position
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template
|
11
|
+
return unless @field.editable?
|
12
|
+
|
13
|
+
if @field.editable_via_menu?
|
14
|
+
render_context_menu_trigger
|
15
|
+
else
|
16
|
+
render_modal_trigger
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def render_modal_trigger
|
23
|
+
a(
|
24
|
+
href: edit_field_url,
|
25
|
+
class: trigger_classes,
|
26
|
+
data: { turbo_frame: "modal" },
|
27
|
+
title: "Edit #{@field.label}"
|
28
|
+
) do
|
29
|
+
render_pencil_icon
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def render_context_menu_trigger
|
34
|
+
div(class: "relative", data: { controller: "dropdown" }) do
|
35
|
+
button(
|
36
|
+
class: trigger_classes,
|
37
|
+
data: {
|
38
|
+
action: "click->dropdown#toggle",
|
39
|
+
dropdown_target: "trigger"
|
40
|
+
},
|
41
|
+
title: "Edit #{@field.label}"
|
42
|
+
) do
|
43
|
+
render_pencil_icon
|
44
|
+
end
|
45
|
+
|
46
|
+
# Context menu (hidden by default)
|
47
|
+
div(
|
48
|
+
class: "absolute z-50 mt-1 w-48 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 hidden #{position_classes}",
|
49
|
+
data: { dropdown_target: "menu" }
|
50
|
+
) do
|
51
|
+
div(class: "py-1") do
|
52
|
+
a(
|
53
|
+
href: belongs_to_reattach_url,
|
54
|
+
class: "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors",
|
55
|
+
data: { turbo_frame: "modal" }
|
56
|
+
) do
|
57
|
+
div(class: "flex items-center") do
|
58
|
+
unsafe_raw <<~SVG
|
59
|
+
<svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
60
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"/>
|
61
|
+
</svg>
|
62
|
+
SVG
|
63
|
+
span { "Change #{@field.label}" }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
a(
|
68
|
+
href: belongs_to_edit_attached_url,
|
69
|
+
class: "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors",
|
70
|
+
data: { turbo_frame: "modal" }
|
71
|
+
) do
|
72
|
+
div(class: "flex items-center") do
|
73
|
+
unsafe_raw <<~SVG
|
74
|
+
<svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
75
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
76
|
+
</svg>
|
77
|
+
SVG
|
78
|
+
span { "Edit #{@field.label} Details" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_pencil_icon
|
87
|
+
unsafe_raw <<~SVG
|
88
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
89
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
90
|
+
</svg>
|
91
|
+
SVG
|
92
|
+
end
|
93
|
+
|
94
|
+
def trigger_classes
|
95
|
+
[
|
96
|
+
"inline-flex items-center justify-center w-6 h-6",
|
97
|
+
"text-gray-400 hover:text-blue-600 hover:bg-blue-50",
|
98
|
+
"rounded transition-colors duration-150",
|
99
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1"
|
100
|
+
].join(" ")
|
101
|
+
end
|
102
|
+
|
103
|
+
def position_classes
|
104
|
+
case @position
|
105
|
+
when :left then "right-0"
|
106
|
+
when :right then "left-0"
|
107
|
+
when :center then "left-1/2 transform -translate-x-1/2"
|
108
|
+
else "left-0"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def edit_field_url
|
113
|
+
easy_admin_url_helpers.edit_field_resource_path(
|
114
|
+
resource.route_key,
|
115
|
+
id: @record.id,
|
116
|
+
field: @field.name
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def belongs_to_reattach_url
|
121
|
+
easy_admin_url_helpers.belongs_to_reattach_resource_path(
|
122
|
+
resource.route_key,
|
123
|
+
id: @record.id,
|
124
|
+
field: @field.name
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def belongs_to_edit_attached_url
|
129
|
+
easy_admin_url_helpers.belongs_to_edit_attached_resource_path(
|
130
|
+
resource.route_key,
|
131
|
+
id: @record.id,
|
132
|
+
field: @field.name
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
def resource
|
137
|
+
# Find resource by model class name
|
138
|
+
model_name = @record.class.name
|
139
|
+
route_key = "#{model_name.downcase.pluralize}"
|
140
|
+
EasyAdmin::ResourceRegistry.find_resource(route_key)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Show
|
4
|
+
class BelongsToComponent < BaseComponent
|
5
|
+
private
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
div(class: "field-display") do
|
9
|
+
# Only show label if show_label is not explicitly false
|
10
|
+
if field.fetch(:show_label, true)
|
11
|
+
div(class: "field-label") { plain field_label }
|
12
|
+
end
|
13
|
+
|
14
|
+
div(class: "field-value") do
|
15
|
+
if association_record
|
16
|
+
if field[:show_link]
|
17
|
+
a(
|
18
|
+
href: view_path_for(association_record),
|
19
|
+
class: "field-link",
|
20
|
+
target: field[:link_target] || "_blank"
|
21
|
+
) do
|
22
|
+
plain display_text
|
23
|
+
end
|
24
|
+
else
|
25
|
+
plain display_text
|
26
|
+
end
|
27
|
+
else
|
28
|
+
span(class: "field-empty") { plain "—" }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def association_record
|
35
|
+
@association_record ||= load_association_record
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_association_record
|
39
|
+
return nil if value.blank?
|
40
|
+
|
41
|
+
association_class = determine_association
|
42
|
+
|
43
|
+
if value.respond_to?(:id)
|
44
|
+
# Already loaded record
|
45
|
+
value
|
46
|
+
else
|
47
|
+
# ID value, need to load the record
|
48
|
+
association_class.find_by(id: value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def determine_association
|
53
|
+
return field[:association] if field[:association]
|
54
|
+
|
55
|
+
# Try to determine from attribute name
|
56
|
+
if field_name.to_s.end_with?('_id')
|
57
|
+
field_name.to_s.gsub('_id', '').classify.constantize
|
58
|
+
else
|
59
|
+
field_name.to_s.classify.constantize
|
60
|
+
end
|
61
|
+
rescue NameError
|
62
|
+
raise ArgumentError, "Could not determine association for #{field_name}. Please specify :association option."
|
63
|
+
end
|
64
|
+
|
65
|
+
def display_text
|
66
|
+
return "—" unless association_record
|
67
|
+
|
68
|
+
if field[:display_method]
|
69
|
+
association_record.send(field[:display_method])
|
70
|
+
elsif association_record.respond_to?(:name)
|
71
|
+
association_record.name
|
72
|
+
elsif association_record.respond_to?(:title)
|
73
|
+
association_record.title
|
74
|
+
elsif association_record.respond_to?(:to_label)
|
75
|
+
association_record.to_label
|
76
|
+
else
|
77
|
+
"##{association_record.id}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def view_path_for(record)
|
82
|
+
return field[:view_path_proc].call(record) if field[:view_path_proc]
|
83
|
+
|
84
|
+
association_class = record.class
|
85
|
+
EasyAdmin::Engine.routes.url_helpers.resource_path(
|
86
|
+
resource_name: association_class.model_name.route_key,
|
87
|
+
id: record.id
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Show
|
4
|
+
class BooleanComponent < BaseComponent
|
5
|
+
def view_template
|
6
|
+
div(class: "ea-show-value") do
|
7
|
+
span(class: css_classes("ea-badge", badge_class)) do
|
8
|
+
value ? "Yes" : "No"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def badge_class
|
16
|
+
value ? "ea-badge--success" : "ea-badge--secondary"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Show
|
4
|
+
class EmailComponent < BaseComponent
|
5
|
+
def view_template
|
6
|
+
div(class: "ea-show-value") do
|
7
|
+
if value.present?
|
8
|
+
a(href: "mailto:#{value}", class: "ea-show-link") do
|
9
|
+
value.to_s
|
10
|
+
end
|
11
|
+
else
|
12
|
+
span(class: "ea-empty-value") { "—" }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Fields
|
3
|
+
module Show
|
4
|
+
class FileComponent < BaseComponent
|
5
|
+
private
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
div(class: "field-display ea-file-field") do
|
9
|
+
if file_exists?
|
10
|
+
render_file_display
|
11
|
+
else
|
12
|
+
render_empty_state
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def render_file_display
|
18
|
+
if image_file?
|
19
|
+
# Hero image display for images
|
20
|
+
div(class: "relative w-full") do
|
21
|
+
img(src: file_url, alt: file_name,
|
22
|
+
class: "w-full h-auto rounded-lg shadow-lg object-cover",
|
23
|
+
style: "max-height: 500px;")
|
24
|
+
|
25
|
+
# Overlay with file info - only show if we have file info to display
|
26
|
+
if file_name != "Unknown file" || file_size_text.present? || uploaded_date
|
27
|
+
div(class: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent p-4 rounded-b-lg") do
|
28
|
+
div(class: "flex items-end justify-between") do
|
29
|
+
div(class: "text-white") do
|
30
|
+
if file_name != "Unknown file"
|
31
|
+
div(class: "font-semibold text-lg drop-shadow-sm") { plain file_name }
|
32
|
+
end
|
33
|
+
if file_size_text.present? || uploaded_date
|
34
|
+
div(class: "text-sm opacity-90 drop-shadow-sm") do
|
35
|
+
if file_size_text.present?
|
36
|
+
span { plain file_size_text }
|
37
|
+
end
|
38
|
+
if uploaded_date
|
39
|
+
span(class: "ml-3") { plain "• #{uploaded_date}" }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
div(class: "flex gap-2") do
|
46
|
+
render_action_buttons(style: "overlay")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
# Card display for non-image files
|
54
|
+
div(class: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden") do
|
55
|
+
div(class: "bg-gradient-to-r from-blue-50 to-indigo-50 p-6") do
|
56
|
+
div(class: "flex items-center") do
|
57
|
+
div(class: "mr-4") do
|
58
|
+
render_file_icon
|
59
|
+
end
|
60
|
+
|
61
|
+
div(class: "flex-1") do
|
62
|
+
div(class: "font-semibold text-gray-900 text-lg") { plain file_name }
|
63
|
+
div(class: "flex items-center text-sm text-gray-600 mt-1") do
|
64
|
+
span { plain file_size_text }
|
65
|
+
if file_type_text
|
66
|
+
span(class: "ml-3 px-2 py-0.5 bg-blue-100 text-blue-700 rounded-full text-xs font-medium") do
|
67
|
+
plain file_type_text
|
68
|
+
end
|
69
|
+
end
|
70
|
+
if uploaded_date
|
71
|
+
span(class: "ml-3") { plain uploaded_date }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
div(class: "px-6 py-4 bg-gray-50 border-t border-gray-100") do
|
79
|
+
div(class: "flex gap-3") do
|
80
|
+
render_action_buttons(style: "card")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def render_image_preview
|
88
|
+
img(src: file_url, alt: file_name,
|
89
|
+
class: "img-thumbnail rounded",
|
90
|
+
style: "max-height: 80px; max-width: 80px; object-fit: cover;")
|
91
|
+
end
|
92
|
+
|
93
|
+
def render_file_icon
|
94
|
+
div(class: "flex items-center justify-center w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 text-white rounded-xl shadow-md") do
|
95
|
+
if pdf_file?
|
96
|
+
unsafe_raw %(<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
97
|
+
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
|
98
|
+
</svg>)
|
99
|
+
elsif video_file?
|
100
|
+
unsafe_raw %(<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
101
|
+
<path d="M17,10.5V7A1,1 0 0,0 16,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16A1,1 0 0,0 17,17V13.5L21,17.5V6.5L17,10.5Z"/>
|
102
|
+
</svg>)
|
103
|
+
elsif audio_file?
|
104
|
+
unsafe_raw %(<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
105
|
+
<path d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.85 14,18.71V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12Z"/>
|
106
|
+
</svg>)
|
107
|
+
else
|
108
|
+
unsafe_raw %(<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
109
|
+
<path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M15,18V16H6V18H15M18,14V12H6V14H18Z"/>
|
110
|
+
</svg>)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def render_action_buttons(style: "default")
|
116
|
+
if style == "overlay"
|
117
|
+
# Buttons for image overlay
|
118
|
+
if file_url
|
119
|
+
a(href: file_url, target: "_blank",
|
120
|
+
class: "inline-flex items-center px-3 py-1.5 bg-white/90 backdrop-blur-sm text-gray-700 rounded-lg hover:bg-white transition-colors text-sm font-medium") do
|
121
|
+
unsafe_raw %(<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
122
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
123
|
+
</svg>)
|
124
|
+
plain "Open"
|
125
|
+
end
|
126
|
+
|
127
|
+
a(href: "#{file_url}?download=true",
|
128
|
+
class: "inline-flex items-center px-3 py-1.5 bg-white/90 backdrop-blur-sm text-gray-700 rounded-lg hover:bg-white transition-colors text-sm font-medium") do
|
129
|
+
unsafe_raw %(<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
130
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
131
|
+
</svg>)
|
132
|
+
plain "Download"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
# Buttons for card display
|
137
|
+
if file_url
|
138
|
+
a(href: file_url, target: "_blank",
|
139
|
+
class: "inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium") do
|
140
|
+
unsafe_raw %(<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
141
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
142
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
143
|
+
</svg>)
|
144
|
+
plain "View File"
|
145
|
+
end
|
146
|
+
|
147
|
+
a(href: "#{file_url}?download=true",
|
148
|
+
class: "inline-flex items-center px-4 py-2 border border-gray-300 text-gray-700 bg-white rounded-lg hover:bg-gray-50 transition-colors text-sm font-medium") do
|
149
|
+
unsafe_raw %(<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
150
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"/>
|
151
|
+
</svg>)
|
152
|
+
plain "Download"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def render_empty_state
|
159
|
+
div(class: "bg-gray-50 rounded-xl border-2 border-dashed border-gray-300 p-12") do
|
160
|
+
div(class: "text-center") do
|
161
|
+
div(class: "flex justify-center mb-4") do
|
162
|
+
div(class: "w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center") do
|
163
|
+
unsafe_raw %(<svg class="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
164
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
|
165
|
+
</svg>)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
div(class: "text-gray-600 font-medium") { plain "No file uploaded" }
|
169
|
+
div(class: "text-gray-400 text-sm mt-1") { plain "Upload a file to see it here" }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def file_exists?
|
175
|
+
return false unless value.present?
|
176
|
+
|
177
|
+
# Handle Active Storage attachments
|
178
|
+
if value.respond_to?(:attached?)
|
179
|
+
value.attached?
|
180
|
+
elsif value.respond_to?(:attachment)
|
181
|
+
value.attachment.present?
|
182
|
+
else
|
183
|
+
true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def file_url
|
188
|
+
return nil unless file_exists?
|
189
|
+
|
190
|
+
begin
|
191
|
+
if value.respond_to?(:url)
|
192
|
+
value.url
|
193
|
+
elsif value.respond_to?(:service_url)
|
194
|
+
value.service_url
|
195
|
+
elsif value.respond_to?(:attachment) && value.attachment.present?
|
196
|
+
Rails.application.routes.url_helpers.rails_blob_path(value.attachment, only_path: true)
|
197
|
+
elsif value.is_a?(String)
|
198
|
+
value
|
199
|
+
end
|
200
|
+
rescue => e
|
201
|
+
Rails.logger.warn "Failed to generate file URL: #{e.message}"
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def file_name
|
207
|
+
return "Unknown file" unless file_exists?
|
208
|
+
|
209
|
+
if value.respond_to?(:filename) && value.filename.present?
|
210
|
+
value.filename.to_s
|
211
|
+
elsif value.respond_to?(:original_filename) && value.original_filename.present?
|
212
|
+
value.original_filename
|
213
|
+
elsif value.respond_to?(:attachment) && value.attachment.present? && value.attachment.filename.present?
|
214
|
+
value.attachment.filename.to_s
|
215
|
+
elsif value.is_a?(String)
|
216
|
+
File.basename(value)
|
217
|
+
else
|
218
|
+
"Uploaded file"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def file_size_text
|
223
|
+
return "" unless file_exists?
|
224
|
+
|
225
|
+
size = nil
|
226
|
+
if value.respond_to?(:byte_size) && value.byte_size.present?
|
227
|
+
size = value.byte_size
|
228
|
+
elsif value.respond_to?(:size) && value.size.present?
|
229
|
+
size = value.size
|
230
|
+
elsif value.respond_to?(:attachment) && value.attachment.present? && value.attachment.byte_size.present?
|
231
|
+
size = value.attachment.byte_size
|
232
|
+
end
|
233
|
+
|
234
|
+
return "" unless size
|
235
|
+
|
236
|
+
if size < 1024
|
237
|
+
"#{size} bytes"
|
238
|
+
elsif size < 1024 * 1024
|
239
|
+
"#{(size / 1024.0).round(1)} KB"
|
240
|
+
else
|
241
|
+
"#{(size / (1024.0 * 1024)).round(1)} MB"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def file_type_text
|
246
|
+
return nil unless file_exists?
|
247
|
+
|
248
|
+
filename = file_name.downcase
|
249
|
+
ext = File.extname(filename)
|
250
|
+
|
251
|
+
case ext
|
252
|
+
when '.pdf' then 'PDF'
|
253
|
+
when '.jpg', '.jpeg' then 'JPEG'
|
254
|
+
when '.png' then 'PNG'
|
255
|
+
when '.gif' then 'GIF'
|
256
|
+
when '.svg' then 'SVG'
|
257
|
+
when '.mp4' then 'MP4'
|
258
|
+
when '.mov' then 'MOV'
|
259
|
+
when '.mp3' then 'MP3'
|
260
|
+
when '.wav' then 'WAV'
|
261
|
+
when '.doc', '.docx' then 'Word'
|
262
|
+
when '.xls', '.xlsx' then 'Excel'
|
263
|
+
when '.zip' then 'ZIP'
|
264
|
+
else ext.upcase.gsub('.', '') if ext.present?
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def uploaded_date
|
269
|
+
return nil unless file_exists?
|
270
|
+
|
271
|
+
date = nil
|
272
|
+
if value.respond_to?(:created_at) && value.created_at.present?
|
273
|
+
date = value.created_at
|
274
|
+
elsif value.respond_to?(:attachment) && value.attachment.present? && value.attachment.created_at.present?
|
275
|
+
date = value.attachment.created_at
|
276
|
+
end
|
277
|
+
|
278
|
+
date&.strftime('%b %d, %Y')
|
279
|
+
rescue
|
280
|
+
nil
|
281
|
+
end
|
282
|
+
|
283
|
+
def image_file?
|
284
|
+
filename = file_name.downcase
|
285
|
+
%w[.jpg .jpeg .png .gif .webp .svg].any? { |ext| filename.end_with?(ext) }
|
286
|
+
end
|
287
|
+
|
288
|
+
def pdf_file?
|
289
|
+
file_name.downcase.end_with?('.pdf')
|
290
|
+
end
|
291
|
+
|
292
|
+
def video_file?
|
293
|
+
filename = file_name.downcase
|
294
|
+
%w[.mp4 .mov .avi .mkv .webm].any? { |ext| filename.end_with?(ext) }
|
295
|
+
end
|
296
|
+
|
297
|
+
def audio_file?
|
298
|
+
filename = file_name.downcase
|
299
|
+
%w[.mp3 .wav .ogg .m4a].any? { |ext| filename.end_with?(ext) }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|