flexi_admin 0.0.5 → 0.0.6
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/README.md +254 -18
- data/lib/flexi_admin/components/helpers/action_helper.rb +21 -0
- data/lib/flexi_admin/components/helpers/frontend_helper.rb +23 -0
- data/lib/flexi_admin/components/helpers/link_helper.rb +35 -2
- data/lib/flexi_admin/components/helpers/resource_helper.rb +79 -14
- data/lib/flexi_admin/components/helpers/url_helper.rb +15 -0
- data/lib/flexi_admin/components/nav/breadcrumbs_component.html.slim +13 -0
- data/lib/flexi_admin/components/nav/breadcrumbs_component.rb +86 -0
- data/lib/flexi_admin/components/resource/autocomplete_component.html.slim +19 -7
- data/lib/flexi_admin/components/resource/autocomplete_component.rb +41 -11
- data/lib/flexi_admin/components/resource/button_select_component.html.slim +2 -2
- data/lib/flexi_admin/components/resource/button_select_component.rb +8 -1
- data/lib/flexi_admin/components/resource/filter_component.html.slim +47 -0
- data/lib/flexi_admin/components/resource/filter_component.rb +51 -0
- data/lib/flexi_admin/components/resource/form_component.rb +16 -1
- data/lib/flexi_admin/components/resource/form_element_component.rb +1 -1
- data/lib/flexi_admin/components/resource/form_mixin.rb +186 -42
- data/lib/flexi_admin/components/resource/link_action_component.html.slim +2 -2
- data/lib/flexi_admin/components/resource/link_action_component.rb +3 -2
- data/lib/flexi_admin/components/resource/view_component.html.slim +14 -4
- data/lib/flexi_admin/components/resource/view_component.rb +11 -2
- data/lib/flexi_admin/components/resources/bulk_action/button_component.rb +8 -1
- data/lib/flexi_admin/components/resources/bulk_action/modal_component.html.slim +21 -15
- data/lib/flexi_admin/components/resources/bulk_action/modal_component.rb +16 -8
- data/lib/flexi_admin/components/resources/bulk_action/row_action_button_component.html.slim +16 -0
- data/lib/flexi_admin/components/resources/bulk_action/row_action_button_component.rb +78 -0
- data/lib/flexi_admin/components/resources/bulk_action/row_actions_dropdown_component.html.slim +15 -0
- data/lib/flexi_admin/components/resources/bulk_action/row_actions_dropdown_component.rb +33 -0
- data/lib/flexi_admin/components/resources/grid_view/card_component.html.slim +4 -1
- data/lib/flexi_admin/components/resources/grid_view/card_component.rb +4 -1
- data/lib/flexi_admin/components/resources/grid_view/grid_component.html.slim +3 -1
- data/lib/flexi_admin/components/resources/grid_view/grid_component.rb +4 -2
- data/lib/flexi_admin/components/resources/grid_view_component.rb +7 -2
- data/lib/flexi_admin/components/resources/index_page_component.html.slim +5 -4
- data/lib/flexi_admin/components/resources/index_page_component.rb +1 -0
- data/lib/flexi_admin/components/resources/list_view/table_component.html.slim +1 -1
- data/lib/flexi_admin/components/resources/list_view_component.rb +2 -1
- data/lib/flexi_admin/components/resources/pagination_component.html.slim +51 -39
- data/lib/flexi_admin/components/resources/pagination_component.rb +52 -47
- data/lib/flexi_admin/components/resources/switch_view_component.html.slim +2 -2
- data/lib/flexi_admin/components/resources/view_component.html.slim +15 -6
- data/lib/flexi_admin/components/shared/medium_component.html.slim +1 -1
- data/lib/flexi_admin/components/shared/table/header_item_component.html.slim +1 -1
- data/lib/flexi_admin/components/shared/table/header_item_component.rb +59 -61
- data/lib/flexi_admin/components/shared/tabs_component.html.slim +26 -0
- data/lib/flexi_admin/components/shared/tabs_component.rb +68 -0
- data/lib/flexi_admin/components.rb +11 -3
- data/lib/flexi_admin/config.rb +21 -3
- data/lib/flexi_admin/controllers/modals_controller.rb +4 -2
- data/lib/flexi_admin/controllers/resources_controller.rb +246 -51
- data/lib/flexi_admin/engine.rb +4 -0
- data/lib/flexi_admin/helpers/application_helper.rb +3 -0
- data/lib/flexi_admin/javascript/controllers/add_row_controller.js +118 -0
- data/lib/flexi_admin/javascript/controllers/autocomplete_controller.js +17 -1
- data/lib/flexi_admin/javascript/controllers/bulk_action_controller.js +192 -33
- data/lib/flexi_admin/javascript/controllers/copy_to_clipboard_controller.js +16 -0
- data/lib/flexi_admin/javascript/controllers/datalist_controller.js +1 -1
- data/lib/flexi_admin/javascript/controllers/delete_controller.js +40 -0
- data/lib/flexi_admin/javascript/controllers/expandable_field_controller.js +15 -0
- data/lib/flexi_admin/javascript/controllers/filter_auto_submit_controller.js +93 -0
- data/lib/flexi_admin/javascript/controllers/index.js +18 -0
- data/lib/flexi_admin/javascript/controllers/pagination_controller.js +8 -0
- data/lib/flexi_admin/javascript/controllers/tabs_controller.js +24 -0
- data/lib/flexi_admin/models/concerns/application_resource.rb +6 -0
- data/lib/flexi_admin/models/concerns/parentable.rb +3 -0
- data/lib/flexi_admin/models/context_params.rb +35 -10
- data/lib/flexi_admin/models/resources/context.rb +4 -0
- data/lib/flexi_admin/routes.rb +7 -1
- data/lib/flexi_admin/services/create_resource.rb +6 -6
- data/lib/flexi_admin/version.rb +1 -1
- data/lib/flexi_admin/views/shared/_reload_frame.slim +19 -0
- data/lib/flexi_admin.rb +0 -3
- metadata +171 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 59b4913fe340c0507725c10bfdea39f47dcc271315c2d95e1c39469e6b44d56b
|
|
4
|
+
data.tar.gz: d45f9fdb404663bbe74a4e6afa103cf0d6c2018b6a346ee58b3f28ab55e19cd1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d04fac65d08116d344bf99247c1c5b61c5bf5ba3fced0fd792516b87c7c37d5c1fc9b8570f0b0bcc4274f05489b414051246d184237b52369cf3aef9bc31efaa
|
|
7
|
+
data.tar.gz: 5a4d7d818963fabbe4554960b4d2e75c9880013f5fbe3dd5fdddd059f8b987c68e5de33572cd1dc494cb183ec16d049e24bc2ea1d5aceced9b7c49cca22e9ba8
|
data/README.md
CHANGED
|
@@ -1,42 +1,278 @@
|
|
|
1
1
|
# FlexiAdmin Rails
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
FlexiAdmin is a Rails engine that provides a flexible admin interface with modern JavaScript integration using Stimulus controllers and Turbo.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Gemfile**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
1. Add to your `Gemfile`:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'flexi_admin', git: 'https://github.com/landovsky/flexi_admin.git', branch: 'main'
|
|
13
|
+
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
2. Run `bundle install`
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
## Setup
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
### Stylesheets
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Add to `app/assets/stylesheets/application.scss`:
|
|
20
22
|
|
|
21
23
|
```scss
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
@import "flexi_admin.scss";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### JavaScript Integration
|
|
28
|
+
|
|
29
|
+
FlexiAdmin works with modern JavaScript bundlers. Choose your bundler below:
|
|
30
|
+
|
|
31
|
+
#### With esbuild (Recommended)
|
|
32
|
+
|
|
33
|
+
Add to `app/javascript/application.js`:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import "flexi_admin"
|
|
26
37
|
```
|
|
27
38
|
|
|
28
|
-
|
|
39
|
+
**esbuild Configuration:**
|
|
40
|
+
|
|
41
|
+
The gem's Railtie automatically sets up NODE_PATH to include the FlexiAdmin JavaScript directory. If you're using a custom esbuild configuration, ensure it can resolve the gem's JavaScript:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// esbuild.config.mjs
|
|
45
|
+
import * as esbuild from 'esbuild'
|
|
46
|
+
import path from 'path'
|
|
47
|
+
|
|
48
|
+
await esbuild.build({
|
|
49
|
+
entryPoints: ['app/javascript/application.js'],
|
|
50
|
+
bundle: true,
|
|
51
|
+
sourcemap: true,
|
|
52
|
+
format: 'esm',
|
|
53
|
+
outdir: 'app/assets/builds',
|
|
54
|
+
publicPath: '/assets',
|
|
55
|
+
nodePaths: [
|
|
56
|
+
'./node_modules',
|
|
57
|
+
// Path to gem JavaScript (adjust if needed)
|
|
58
|
+
path.resolve('../flexi_admin/lib/flexi_admin/javascript')
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or set NODE_PATH in your package.json build script:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "NODE_PATH=./node_modules:$(bundle show flexi_admin)/lib/flexi_admin/javascript esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### With webpack
|
|
74
|
+
|
|
75
|
+
Add to `app/javascript/application.js`:
|
|
29
76
|
|
|
77
|
+
```javascript
|
|
78
|
+
import "flexi_admin"
|
|
30
79
|
```
|
|
31
|
-
# frozen_string_literal: true
|
|
32
80
|
|
|
81
|
+
**Webpack Configuration:**
|
|
82
|
+
|
|
83
|
+
Update your webpack configuration to resolve the gem's JavaScript directory:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// webpack.config.js
|
|
87
|
+
const path = require('path')
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
resolve: {
|
|
91
|
+
modules: [
|
|
92
|
+
path.resolve(__dirname, 'node_modules'),
|
|
93
|
+
// Add gem JavaScript directory
|
|
94
|
+
path.resolve(__dirname, '../flexi_admin/lib/flexi_admin/javascript')
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Or find the gem path dynamically:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const { execSync } = require('child_process')
|
|
104
|
+
const gemPath = execSync('bundle show flexi_admin').toString().trim()
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
resolve: {
|
|
108
|
+
modules: [
|
|
109
|
+
'node_modules',
|
|
110
|
+
path.join(gemPath, 'lib/flexi_admin/javascript')
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Stimulus Controllers
|
|
117
|
+
|
|
118
|
+
FlexiAdmin includes 19 Stimulus controllers that automatically register when you import the gem's JavaScript:
|
|
119
|
+
|
|
120
|
+
#### Available Controllers
|
|
121
|
+
|
|
122
|
+
- `add-row` - Dynamically add rows to forms
|
|
123
|
+
- `autocomplete` - Autocomplete search fields
|
|
124
|
+
- `bulk-action` - Bulk selection with persistence across pagination
|
|
125
|
+
- `button-select` - Custom button selection controls
|
|
126
|
+
- `datalist` - Enhanced datalist inputs
|
|
127
|
+
- `delete` - Confirmation dialogs for delete actions
|
|
128
|
+
- `filter-auto-submit` - Auto-submit filters on change
|
|
129
|
+
- `floating-toc` - Floating table of contents navigation
|
|
130
|
+
- `form` - Enhanced form behavior
|
|
131
|
+
- `form-validation` - Client-side form validation
|
|
132
|
+
- `pagination` - Page navigation controls
|
|
133
|
+
- `sorting` - Column sorting
|
|
134
|
+
- `switch-view` - Toggle between list/grid views
|
|
135
|
+
- `tabs` - Keeps the open `TabsComponent` tab in the URL hash
|
|
136
|
+
- `toast` - Toast notification system
|
|
137
|
+
- `trix` - Rich text editor integration
|
|
138
|
+
- `uploads` - File upload handling
|
|
139
|
+
|
|
140
|
+
#### Usage in Views
|
|
141
|
+
|
|
142
|
+
Use controllers in your ERB templates with the `data-controller` attribute:
|
|
143
|
+
|
|
144
|
+
```erb
|
|
145
|
+
<div data-controller="flexi-admin--bulk-action"
|
|
146
|
+
data-flexi-admin--bulk-action-scope-value="users">
|
|
147
|
+
<!-- Your bulk action content -->
|
|
148
|
+
</div>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Controller naming convention: `flexi-admin--[controller-name]`
|
|
152
|
+
|
|
153
|
+
#### Example: Pagination
|
|
154
|
+
|
|
155
|
+
```erb
|
|
156
|
+
<nav data-controller="flexi-admin--pagination">
|
|
157
|
+
<%= link_to "Previous",
|
|
158
|
+
admin_users_path(page: @page - 1),
|
|
159
|
+
data: {
|
|
160
|
+
turbo_frame: "users_list",
|
|
161
|
+
action: "flexi-admin--pagination#navigate"
|
|
162
|
+
} %>
|
|
163
|
+
</nav>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Example: Bulk Actions with Selection
|
|
167
|
+
|
|
168
|
+
```erb
|
|
169
|
+
<div data-controller="flexi-admin--bulk-action"
|
|
170
|
+
data-flexi-admin--bulk-action-scope-value="users">
|
|
171
|
+
|
|
172
|
+
<%= check_box_tag "select_all", nil, false,
|
|
173
|
+
data: { action: "flexi-admin--bulk-action#toggleAll" } %>
|
|
174
|
+
|
|
175
|
+
<% @users.each do |user| %>
|
|
176
|
+
<%= check_box_tag "user_ids[]", user.id, false,
|
|
177
|
+
data: {
|
|
178
|
+
action: "flexi-admin--bulk-action#toggle",
|
|
179
|
+
flexi_admin__bulk_action_target: "checkbox"
|
|
180
|
+
} %>
|
|
181
|
+
<% end %>
|
|
182
|
+
|
|
183
|
+
<%= button_to "Delete Selected",
|
|
184
|
+
bulk_action_admin_users_path,
|
|
185
|
+
data: {
|
|
186
|
+
flexi_admin__bulk_action_target: "actionButton",
|
|
187
|
+
confirm: "Are you sure?"
|
|
188
|
+
} %>
|
|
189
|
+
</div>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Example: Autocomplete Search
|
|
193
|
+
|
|
194
|
+
```erb
|
|
195
|
+
<%= text_field_tag :search, params[:search],
|
|
196
|
+
data: {
|
|
197
|
+
controller: "flexi-admin--autocomplete",
|
|
198
|
+
flexi_admin__autocomplete_url_value: autocomplete_admin_users_path,
|
|
199
|
+
action: "input->flexi-admin--autocomplete#search"
|
|
200
|
+
} %>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Example: Tabs on a show page
|
|
204
|
+
|
|
205
|
+
`FlexiAdmin::Components::Shared::TabsComponent` splits a long page into Bootstrap tabs. Every
|
|
206
|
+
pane is rendered up front (no lazy loading), so content behaves exactly as it did on one page.
|
|
207
|
+
The open tab is kept in the URL hash (`/orders/1#kalkulace`), so a reload or a shared link
|
|
208
|
+
reopens it.
|
|
209
|
+
|
|
210
|
+
```slim
|
|
211
|
+
= render FlexiAdmin::Components::Shared::TabsComponent.new(id: 'order') do |tabs|
|
|
212
|
+
- tabs.with_tab(key: 'prehled', label: 'Přehled')
|
|
213
|
+
= render Admin::Order::Show::EditFormComponent.new(resource)
|
|
214
|
+
- tabs.with_tab(key: 'kalkulace', label: 'Kalkulace', count: calculations.size)
|
|
215
|
+
= render Admin::OrderCalculation::ResourcesComponent.new(calculations, context_params:, parent: resource)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- `id:` prefixes the DOM ids (`order-tab-<key>`, `order-pane-<key>`). It must be unique on the page.
|
|
219
|
+
- `with_tab(key:, label:, count: nil, active: false)`: `count` renders a badge, and `active: true` opens that tab
|
|
220
|
+
(the first tab otherwise).
|
|
221
|
+
- `remember: false` turns off the URL-hash memory, for example for a second tab set on the same page.
|
|
222
|
+
- `nav_class:` sets the classes on the `ul.nav-tabs` (default `mb-4`).
|
|
223
|
+
|
|
224
|
+
Requires Bootstrap 5's JavaScript (`data-bs-toggle="tab"`).
|
|
225
|
+
|
|
226
|
+
### Verification
|
|
227
|
+
|
|
228
|
+
To verify the JavaScript is loading correctly:
|
|
229
|
+
|
|
230
|
+
1. Start your Rails server
|
|
231
|
+
2. Open browser console
|
|
232
|
+
3. Check for Stimulus registration:
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
// Should list all FlexiAdmin controllers
|
|
236
|
+
window.Stimulus.controllers
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Temporary Workarounds
|
|
240
|
+
|
|
241
|
+
Currently, the following manual steps are required:
|
|
242
|
+
|
|
243
|
+
### Modals Controller
|
|
244
|
+
|
|
245
|
+
Create a modals controller in your app:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# app/controllers/modals_controller.rb
|
|
33
249
|
class ModalsController < AdminController
|
|
34
250
|
include FlexiAdmin::Controllers::ModalsController
|
|
35
251
|
end
|
|
36
252
|
```
|
|
37
253
|
|
|
254
|
+
Add to routes:
|
|
255
|
+
|
|
38
256
|
```ruby
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
257
|
+
resources :modals, only: [] do
|
|
258
|
+
get :show, on: :collection
|
|
259
|
+
end
|
|
42
260
|
```
|
|
261
|
+
|
|
262
|
+
## Development
|
|
263
|
+
|
|
264
|
+
See [TESTING.md](TESTING.md) for information on:
|
|
265
|
+
- Running the test suite
|
|
266
|
+
- Setting up the dummy app for development
|
|
267
|
+
- JavaScript build process
|
|
268
|
+
- Test coverage
|
|
269
|
+
|
|
270
|
+
## Documentation
|
|
271
|
+
|
|
272
|
+
- [TESTING.md](TESTING.md) - Testing guide and infrastructure
|
|
273
|
+
- [CI_REQUIREMENTS.md](CI_REQUIREMENTS.md) - CI/CD setup for esbuild
|
|
274
|
+
- [spec/dummy/README.md](spec/dummy/README.md) - Dummy app documentation
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
This project is licensed under the MIT License.
|
|
@@ -9,4 +9,25 @@ module FlexiAdmin::Components::Helpers::ActionHelper
|
|
|
9
9
|
disabled:,
|
|
10
10
|
selection_dependent:)
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
def action_button(item, action_component, hide_icon: false, **options)
|
|
14
|
+
render FlexiAdmin::Components::Resources::BulkAction::RowActionButtonComponent.new(item,
|
|
15
|
+
context,
|
|
16
|
+
action_component,
|
|
17
|
+
hide_icon: hide_icon,
|
|
18
|
+
**options)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Renders a dropdown with multiple action buttons for a single row/item
|
|
22
|
+
# Use this in column blocks when you have multiple actions for an item
|
|
23
|
+
# Example:
|
|
24
|
+
# = actions_dropdown(item) do |dropdown|
|
|
25
|
+
# - dropdown.with_action_button(item, context, ChangeStatus)
|
|
26
|
+
# - dropdown.with_action_button(item, context, AssignUser)
|
|
27
|
+
def actions_dropdown(item, &block)
|
|
28
|
+
render FlexiAdmin::Components::Resources::BulkAction::RowActionsDropdownComponent.new(item, context) do |component|
|
|
29
|
+
block.call(component) if block
|
|
30
|
+
component
|
|
31
|
+
end
|
|
32
|
+
end
|
|
12
33
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_view"
|
|
4
|
+
|
|
5
|
+
module FlexiAdmin::Components::Helpers::FrontendHelper
|
|
6
|
+
def active(path, predicate = :ends_with)
|
|
7
|
+
css_match "active", request.path, match: path, use: predicate
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Example: css_match 'active', request.path, match: 'alternativy', use: :starts_with
|
|
11
|
+
def css_match(klasses, subject, match:, use: :includes)
|
|
12
|
+
case use
|
|
13
|
+
when :starts_with
|
|
14
|
+
klasses if subject.to_s.starts_with?(match)
|
|
15
|
+
when :ends_with
|
|
16
|
+
klasses if subject.to_s.ends_with?(match)
|
|
17
|
+
when :includes
|
|
18
|
+
klasses if subject.to_s.include?(match)
|
|
19
|
+
else
|
|
20
|
+
raise "Unknown predicate: #{predicate}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -2,7 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
module FlexiAdmin::Components::Helpers::LinkHelper
|
|
4
4
|
def navigate_to(title, resource)
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
parent = context&.options&.dig(:parent) || parent_from_params
|
|
6
|
+
namespace = FlexiAdmin::Config.configuration.namespace
|
|
7
|
+
if resource.is_a?(String)
|
|
8
|
+
helpers.link_to title, resource, "data-turbo-frame": "_top"
|
|
9
|
+
else
|
|
10
|
+
path_segments = if parent.present? && parent.class != resource.class
|
|
11
|
+
namespace.present? ? [namespace.to_sym, parent, resource] : [parent, resource]
|
|
12
|
+
elsif namespace.present?
|
|
13
|
+
[namespace.to_sym, resource]
|
|
14
|
+
else
|
|
15
|
+
resource
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
result_path = begin
|
|
19
|
+
helpers.polymorphic_path(path_segments)
|
|
20
|
+
rescue NoMethodError
|
|
21
|
+
without_parent = path_segments.without(parent)
|
|
22
|
+
raise if without_parent.empty?
|
|
23
|
+
|
|
24
|
+
helpers.polymorphic_path(without_parent)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
helpers.link_to title, result_path, "data-turbo-frame": "_top"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def parent_from_params
|
|
34
|
+
return nil if context&.params&.parent.blank?
|
|
35
|
+
|
|
36
|
+
gid = URI.decode_www_form_component(context.params.parent)
|
|
37
|
+
GlobalID::Locator.locate(gid)
|
|
38
|
+
rescue StandardError
|
|
39
|
+
nil
|
|
7
40
|
end
|
|
8
41
|
end
|
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
module FlexiAdmin::Components::Helpers::ResourceHelper
|
|
4
4
|
include FlexiAdmin::Components::Helpers::UrlHelper
|
|
5
5
|
|
|
6
|
-
def autocomplete_path(action:, fields:, parent: nil)
|
|
6
|
+
def autocomplete_path(action:, fields:, parent: nil, custom_scope: nil)
|
|
7
7
|
payload = {
|
|
8
8
|
ac_action: action,
|
|
9
9
|
ac_path: resource__path,
|
|
10
10
|
fa_parent: parent&.gid_param,
|
|
11
11
|
ac_fields: fields
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
payload[:fa_custom_scope] = custom_scope if custom_scope.present?
|
|
15
|
+
|
|
16
|
+
path = namespaced_path("autocomplete", "namespace", scope_plural)
|
|
17
|
+
route_exists_in_main_app?(path) ? main_app.send(path, params: payload) : helpers.send(path, params: payload)
|
|
18
|
+
# rescue => e
|
|
19
|
+
# binding.pry if Rails.env.development?
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def datalist_path(action:, fields:, parent: nil)
|
|
@@ -19,41 +25,99 @@ module FlexiAdmin::Components::Helpers::ResourceHelper
|
|
|
19
25
|
ac_fields: fields
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
path = namespaced_path("datalist", "namespace", scope_plural)
|
|
29
|
+
route_exists_in_main_app?(path) ? main_app.send(path, params: payload) : helpers.send(path, params: payload)
|
|
30
|
+
# rescue => e
|
|
31
|
+
# binding.pry if Rails.env.development?
|
|
23
32
|
end
|
|
24
33
|
|
|
25
34
|
def edit_resource_path(resource, **params)
|
|
26
|
-
|
|
35
|
+
path = namespaced_path("edit", "namespace", scope_singular)
|
|
36
|
+
if route_exists_in_main_app?(path)
|
|
37
|
+
main_app.send(path, resource,
|
|
38
|
+
params: params)
|
|
39
|
+
else
|
|
40
|
+
helpers.send(path, resource, params: params)
|
|
41
|
+
end
|
|
42
|
+
# rescue => e
|
|
43
|
+
# binding.pry if Rails.env.development?
|
|
27
44
|
end
|
|
28
45
|
|
|
29
46
|
def bulk_action_path(scope, **params)
|
|
30
|
-
raise
|
|
47
|
+
raise "Scope is not defined" if scope.blank?
|
|
31
48
|
|
|
32
|
-
|
|
49
|
+
path = namespaced_path("bulk_action", "namespace", scope)
|
|
50
|
+
route_exists_in_main_app?(path) ? main_app.send(path, params: params) : helpers.send(path, params: params)
|
|
51
|
+
# rescue => e
|
|
52
|
+
# binding.pry if Rails.env.development?
|
|
33
53
|
end
|
|
34
54
|
|
|
35
55
|
def resource_path(resource, **params)
|
|
36
|
-
|
|
56
|
+
path = namespaced_path("namespace", scope_singular)
|
|
57
|
+
if route_exists_in_main_app?(path)
|
|
58
|
+
main_app.send(path, resource,
|
|
59
|
+
params: params)
|
|
60
|
+
else
|
|
61
|
+
helpers.send(path, resource, params: params)
|
|
62
|
+
end
|
|
63
|
+
# rescue => e
|
|
64
|
+
# binding.pry if Rails.env.development?
|
|
37
65
|
end
|
|
38
66
|
|
|
39
67
|
def resource__path
|
|
40
|
-
"
|
|
68
|
+
namespaced_path("namespace", scope_singular)
|
|
69
|
+
# rescue => e
|
|
70
|
+
# binding.pry if Rails.env.development?
|
|
41
71
|
end
|
|
42
72
|
|
|
43
73
|
def resources_path(**params)
|
|
44
|
-
|
|
74
|
+
path = namespaced_path("namespace", scope_plural)
|
|
75
|
+
route_exists_in_main_app?(path) ? main_app.send(path, params: params) : helpers.send(path, params: params)
|
|
76
|
+
# rescue => e
|
|
77
|
+
# binding.pry if Rails.env.development?
|
|
45
78
|
end
|
|
46
79
|
|
|
47
80
|
def resource_input_name
|
|
48
81
|
"#{scope_singular}_id"
|
|
82
|
+
# rescue => e
|
|
83
|
+
# binding.pry if Rails.env.development?
|
|
49
84
|
end
|
|
50
85
|
|
|
51
86
|
def scope_plural
|
|
52
|
-
|
|
87
|
+
if instance_variable_defined?(:@target_controller) && @target_controller.present?
|
|
88
|
+
@target_controller.to_s.underscore.gsub("/", "_")
|
|
89
|
+
elsif instance_variable_defined?(:@name) && @name.present?
|
|
90
|
+
field_name = @name.to_s.gsub(/_id$/, "")
|
|
91
|
+
|
|
92
|
+
base_name = field_name.gsub(/^(primary_|secondary_|main_|default_)/, "")
|
|
93
|
+
|
|
94
|
+
base_name.pluralize
|
|
95
|
+
else
|
|
96
|
+
unless defined?(scope) && !scope.is_a?(Proc)
|
|
97
|
+
raise "Cannot infer controller from scope without target_controller or field name"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
scope.to_s.underscore.gsub("/", "_")
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
# rescue => e
|
|
104
|
+
# binding.pry if Rails.env.development?
|
|
53
105
|
end
|
|
54
106
|
|
|
55
107
|
def scope_singular
|
|
56
|
-
scope.
|
|
108
|
+
if scope.is_a?(Proc)
|
|
109
|
+
if instance_variable_defined?(:@target_controller) && @target_controller.present?
|
|
110
|
+
@target_controller.to_s.underscore.gsub("/", "_").singularize
|
|
111
|
+
elsif instance_variable_defined?(:@name) && @name.present?
|
|
112
|
+
@name.to_s.gsub(/_id$/, "").singularize
|
|
113
|
+
else
|
|
114
|
+
raise "Cannot infer singular scope from Proc without target_controller or field name"
|
|
115
|
+
end
|
|
116
|
+
else
|
|
117
|
+
scope.to_s.underscore.singularize
|
|
118
|
+
end
|
|
119
|
+
# rescue => e
|
|
120
|
+
# binding.pry if Rails.env.development?
|
|
57
121
|
end
|
|
58
122
|
|
|
59
123
|
def scope
|
|
@@ -61,11 +125,12 @@ module FlexiAdmin::Components::Helpers::ResourceHelper
|
|
|
61
125
|
return context.scope if defined?(context)
|
|
62
126
|
return resource.model_name.try(:plural) if defined?(resource)
|
|
63
127
|
|
|
64
|
-
raise
|
|
128
|
+
raise "Scope is not defined"
|
|
65
129
|
end
|
|
66
130
|
end
|
|
67
131
|
|
|
68
|
-
def paginate(resource, per_page:
|
|
69
|
-
resource.paginate(page: params[:page], per_page:)
|
|
132
|
+
def paginate(resource, per_page: WillPaginate.per_page)
|
|
133
|
+
resource.paginate(page: params[:page], per_page: per_page)
|
|
70
134
|
end
|
|
135
|
+
|
|
71
136
|
end
|
|
@@ -5,4 +5,19 @@ require "action_view"
|
|
|
5
5
|
module FlexiAdmin::Components::Helpers::UrlHelper
|
|
6
6
|
include ::ActionView::Helpers::UrlHelper
|
|
7
7
|
include ::ActionView::RoutingUrlFor
|
|
8
|
+
|
|
9
|
+
def namespaced_path(*segments)
|
|
10
|
+
namespace = FlexiAdmin::Config.configuration.namespace
|
|
11
|
+
|
|
12
|
+
segments.map! do |segment|
|
|
13
|
+
segment = segment.to_s.gsub("/", "_")
|
|
14
|
+
segment == "namespace" ? (namespace.presence || nil) : segment
|
|
15
|
+
end
|
|
16
|
+
segments.compact!
|
|
17
|
+
"#{segments.join("_")}_path"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def route_exists_in_main_app?(path)
|
|
21
|
+
main_app.respond_to?(path)
|
|
22
|
+
end
|
|
8
23
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
nav.breadcrumbs aria-label="breadcrumb"
|
|
2
|
+
ol.breadcrumb
|
|
3
|
+
- breadcrumbs.each_with_index do |crumb, idx|
|
|
4
|
+
- if idx < breadcrumbs.size - 1
|
|
5
|
+
li.breadcrumb-item
|
|
6
|
+
- if (main_app.routes.recognize_path("#{request.base_url}#{crumb[:url]}", method: :get) rescue false)
|
|
7
|
+
= helpers.link_to crumb[:label], crumb[:url], "data-turbo-frame": "_top"
|
|
8
|
+
- else
|
|
9
|
+
- puts "here"
|
|
10
|
+
= crumb[:label]
|
|
11
|
+
- else
|
|
12
|
+
li.breadcrumb-item.active aria-current="page"
|
|
13
|
+
= crumb[:label]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlexiAdmin::Components::Nav
|
|
4
|
+
class BreadcrumbsComponent < FlexiAdmin::Components::BaseComponent
|
|
5
|
+
include FlexiAdmin::Components::Helpers::UrlHelper
|
|
6
|
+
|
|
7
|
+
attr_reader :path
|
|
8
|
+
|
|
9
|
+
def initialize(path:)
|
|
10
|
+
super()
|
|
11
|
+
@path = path
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def breadcrumbs
|
|
15
|
+
segments = path.split("/").reject(&:blank?)
|
|
16
|
+
return [] if segments.size <= 1
|
|
17
|
+
|
|
18
|
+
crumbs = []
|
|
19
|
+
url = ""
|
|
20
|
+
segments.each_with_index do |segment, idx|
|
|
21
|
+
if idx.positive? && is_id?(segment)
|
|
22
|
+
# For instance, use singularized model name for lookup, but display uses instance title
|
|
23
|
+
model_class = infer_model_class(segments[idx - 1], segments[0..idx-2])
|
|
24
|
+
record = model_class&.find_by(id: segment)
|
|
25
|
+
label = record&.title if record.respond_to?(:title)
|
|
26
|
+
label ||= record&.try(:name)
|
|
27
|
+
label ||= "#{segments[idx - 1].singularize.camelize} ##{segment}"
|
|
28
|
+
url += "/#{segment}"
|
|
29
|
+
elsif !is_id?(segment)
|
|
30
|
+
# For collection, use plural segment for display and I18n lookup
|
|
31
|
+
plural_segment = segment
|
|
32
|
+
model_class = infer_model_class(segment, segments[0..idx-1])
|
|
33
|
+
label = breadcrumb_label(model_class, plural_segment)
|
|
34
|
+
url += "/#{segment}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
crumbs << { label: label, url: url.dup }
|
|
38
|
+
end
|
|
39
|
+
crumbs
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def is_id?(segment)
|
|
45
|
+
segment.match?(/\A\d+\z/) || segment.match?(/\A[\da-fA-F-]{36}\z/)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def breadcrumb_label(model_class, plural_segment)
|
|
49
|
+
if model_class.is_a?(Class) && model_class.respond_to?(:breadcrumb_title)
|
|
50
|
+
model_class.breadcrumb_title
|
|
51
|
+
elsif model_class.is_a?(Class) && model_class.respond_to?(:model_name)
|
|
52
|
+
I18n.t("activerecord.models.#{model_class.model_name.i18n_key}.few",
|
|
53
|
+
default: model_class.model_name.human)
|
|
54
|
+
elsif model_class.is_a?(Module)
|
|
55
|
+
# For namespaces/modules, try to translate
|
|
56
|
+
module_name = model_class.name.underscore
|
|
57
|
+
I18n.t("activerecord.models.#{module_name}.few", default: module_name.humanize)
|
|
58
|
+
else
|
|
59
|
+
I18n.t("breadcrumbs.#{plural_segment}", default: plural_segment.titleize)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def infer_model_class(segment, previous_segments = [])
|
|
64
|
+
singular = segment.singularize
|
|
65
|
+
candidates = [singular.camelize]
|
|
66
|
+
if singular.include?('_')
|
|
67
|
+
parts = singular.split('_')
|
|
68
|
+
namespace = parts[0].camelize
|
|
69
|
+
model = parts[1..-1].join('_').camelize
|
|
70
|
+
candidates << "#{namespace}::#{model}"
|
|
71
|
+
end
|
|
72
|
+
# Try with previous segments as potential namespaces
|
|
73
|
+
previous_segments.reverse.each do |prev|
|
|
74
|
+
next if is_id?(prev)
|
|
75
|
+
|
|
76
|
+
namespace = prev.singularize.camelize
|
|
77
|
+
candidates << "#{namespace}::#{singular.camelize}"
|
|
78
|
+
end
|
|
79
|
+
candidates.each do |name|
|
|
80
|
+
cls = name.safe_constantize
|
|
81
|
+
return cls if cls.is_a?(Class) && cls < ActiveRecord::Base
|
|
82
|
+
end
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|