uchi 0.1.5 → 0.1.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/app/assets/javascripts/uchi/application.js +2655 -24
- data/app/assets/javascripts/uchi.js +11 -0
- data/app/assets/stylesheets/uchi/application.css +982 -1311
- data/app/assets/tailwind/uchi.css +4 -4
- data/app/components/uchi/field/belongs_to/edit.html.erb +1 -1
- data/app/components/uchi/field/belongs_to/index.html.erb +1 -1
- data/app/components/uchi/field/belongs_to/show.html.erb +3 -5
- data/app/components/uchi/field/belongs_to.rb +30 -7
- data/app/components/uchi/field/boolean/edit.html.erb +1 -1
- data/app/components/uchi/field/date/edit.html.erb +1 -1
- data/app/components/uchi/field/date_time/edit.html.erb +1 -1
- data/app/components/uchi/field/file/edit.html.erb +1 -1
- data/app/components/uchi/field/file/index.html.erb +2 -1
- data/app/components/uchi/field/file/show.html.erb +3 -2
- data/app/components/uchi/field/has_and_belongs_to_many/show.html.erb +5 -3
- data/app/components/uchi/field/has_many/show.html.erb +5 -3
- data/app/components/uchi/field/id/index.html.erb +1 -1
- data/app/components/uchi/field/id/show.html.erb +1 -1
- data/app/components/uchi/field/image/edit.html.erb +1 -1
- data/app/components/uchi/field/image/index.html.erb +2 -1
- data/app/components/uchi/field/image/show.html.erb +2 -1
- data/app/components/uchi/field/number/edit.html.erb +1 -1
- data/app/components/uchi/field/string/edit.html.erb +1 -1
- data/app/components/uchi/field/text/edit.html.erb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/breadcrumb.rb +5 -5
- data/app/components/{flowbite → uchi/flowbite}/breadcrumb_home.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/breadcrumb_item/current.rb +3 -3
- data/app/components/{flowbite → uchi/flowbite}/breadcrumb_item/first.rb +4 -4
- data/app/components/{flowbite → uchi/flowbite}/breadcrumb_item.rb +4 -4
- data/app/components/{flowbite → uchi/flowbite}/breadcrumb_separator.rb +7 -5
- data/app/components/uchi/flowbite/button/outline.rb +37 -0
- data/app/components/uchi/flowbite/button/pill.rb +40 -0
- data/app/components/uchi/flowbite/button.rb +93 -0
- data/app/components/uchi/flowbite/card/card.html.erb +7 -0
- data/app/components/uchi/flowbite/card/title.rb +37 -0
- data/app/components/uchi/flowbite/card.rb +84 -0
- data/app/components/{flowbite → uchi/flowbite}/input/checkbox.rb +7 -7
- data/app/components/{flowbite → uchi/flowbite}/input/date.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/input/date_time.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/input/email.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/input/field.rb +11 -10
- data/app/components/uchi/flowbite/input/file.rb +30 -0
- data/app/components/{flowbite → uchi/flowbite}/input/hint.rb +6 -5
- data/app/components/{flowbite → uchi/flowbite}/input/label.rb +8 -7
- data/app/components/{flowbite → uchi/flowbite}/input/number.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/input/password.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/input/phone.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/input/radio_button.rb +7 -7
- data/app/components/{flowbite → uchi/flowbite}/input/select.rb +16 -7
- data/app/components/uchi/flowbite/input/textarea.rb +42 -0
- data/app/components/{flowbite → uchi/flowbite}/input/url.rb +1 -1
- data/app/components/uchi/flowbite/input/validation_error.rb +39 -0
- data/app/components/uchi/flowbite/input_field/checkbox.html.erb +9 -0
- data/app/components/{flowbite → uchi/flowbite}/input_field/checkbox.rb +10 -6
- data/app/components/{flowbite → uchi/flowbite}/input_field/date.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/input_field/date_time.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/input_field/email.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/input_field/file.rb +2 -2
- data/app/components/uchi/flowbite/input_field/input_field.html.erb +9 -0
- data/app/components/{flowbite → uchi/flowbite}/input_field/number.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/input_field/password.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/input_field/phone.rb +2 -2
- data/app/components/uchi/flowbite/input_field/radio_button.html.erb +9 -0
- data/app/components/{flowbite → uchi/flowbite}/input_field/radio_button.rb +13 -9
- data/app/components/{flowbite → uchi/flowbite}/input_field/select.rb +7 -3
- data/app/components/{flowbite → uchi/flowbite}/input_field/text.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/input_field/textarea.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/input_field/url.rb +2 -2
- data/app/components/{flowbite → uchi/flowbite}/input_field.rb +19 -5
- data/app/components/uchi/flowbite/link.rb +41 -0
- data/app/components/{flowbite → uchi/flowbite}/style.rb +1 -1
- data/app/components/{flowbite → uchi/flowbite}/toast/icon.rb +1 -1
- data/app/components/uchi/flowbite/toast/toast.html.erb +40 -0
- data/app/components/{flowbite → uchi/flowbite}/toast.rb +2 -2
- data/app/components/uchi/ui/actions/dropdown/dropdown.html.erb +59 -0
- data/app/components/uchi/ui/actions/dropdown.rb +32 -0
- data/app/components/uchi/ui/breadcrumb/breadcrumb.html.erb +4 -4
- data/app/components/uchi/ui/form/input/collection_checkboxes.html.erb +2 -1
- data/app/components/uchi/ui/form/input/collection_checkboxes.rb +12 -17
- data/app/components/uchi/ui/frame/frame.html.erb +6 -1
- data/app/components/uchi/ui/index/records_table/records_table.html.erb +109 -18
- data/app/components/uchi/ui/index/records_table/search_form/search_form.html.erb +21 -5
- data/app/components/uchi/ui/navigation/navigation.html.erb +9 -0
- data/app/components/uchi/ui/navigation.rb +37 -0
- data/app/components/uchi/ui/no_value/no_value.html.erb +1 -0
- data/app/components/uchi/ui/no_value.rb +8 -0
- data/app/components/uchi/ui/page_header/page_header.html.erb +7 -2
- data/app/components/uchi/ui/pagination/current_link.html.erb +1 -1
- data/app/components/uchi/ui/pagination/gap.html.erb +7 -1
- data/app/components/uchi/ui/pagination/link.html.erb +1 -1
- data/app/components/uchi/ui/pagination/next_link.html.erb +18 -3
- data/app/components/uchi/ui/pagination/pagination.html.erb +3 -1
- data/app/components/uchi/ui/pagination/previous_link.html.erb +18 -3
- data/app/components/uchi/ui/pagination.rb +1 -1
- data/app/components/uchi/ui/show/attribute_fields/attribute_fields.html.erb +4 -3
- data/app/components/uchi/ui/spinner/spinner.html.erb +17 -3
- data/app/controllers/uchi/actions/executions_controller.rb +107 -0
- data/app/controllers/uchi/repository_controller.rb +21 -1
- data/app/views/layouts/uchi/_flash_messages.html.erb +1 -1
- data/app/views/layouts/uchi/application.html.erb +10 -16
- data/app/views/uchi/navigation/_main.html.erb +9 -0
- data/app/views/uchi/repository/edit.html.erb +2 -2
- data/app/views/uchi/repository/index.html.erb +12 -3
- data/app/views/uchi/repository/new.html.erb +2 -2
- data/app/views/uchi/repository/show.html.erb +13 -2
- data/lib/generators/uchi/controller/templates/controller.rb.tt +0 -5
- data/lib/generators/uchi/scaffold/scaffold_generator.rb +15 -0
- data/lib/uchi/action.rb +84 -0
- data/lib/uchi/action_response.rb +108 -0
- data/lib/uchi/repository/translate.rb +14 -0
- data/lib/uchi/repository.rb +25 -0
- data/lib/uchi/version.rb +1 -1
- data/lib/uchi.rb +2 -0
- metadata +63 -50
- data/app/components/flowbite/button/outline.rb +0 -22
- data/app/components/flowbite/button/pill.rb +0 -40
- data/app/components/flowbite/button.rb +0 -92
- data/app/components/flowbite/card.rb +0 -45
- data/app/components/flowbite/input/file.rb +0 -30
- data/app/components/flowbite/input/textarea.rb +0 -42
- data/app/components/flowbite/input/validation_error.rb +0 -11
- data/app/components/flowbite/input_field/checkbox.html.erb +0 -14
- data/app/components/flowbite/input_field/input_field.html.erb +0 -8
- data/app/components/flowbite/input_field/radio_button.html.erb +0 -14
- data/app/components/flowbite/link.rb +0 -21
- data/app/components/flowbite/toast/toast.html.erb +0 -11
- /data/app/components/{flowbite → uchi/flowbite}/toast/icon.html.erb +0 -0
|
@@ -1,31 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<%# Styles from https://flowbite.com/docs/components/tables/#default-table %>
|
|
2
|
+
<table class="w-full text-sm text-left text-body rtl:text-right">
|
|
3
|
+
<thead
|
|
4
|
+
class="
|
|
5
|
+
text-sm text-body bg-neutral-secondary-soft border-b rounded-base
|
|
6
|
+
border-default
|
|
7
|
+
"
|
|
8
|
+
>
|
|
3
9
|
<tr>
|
|
4
10
|
<% columns.each do |field| %>
|
|
5
11
|
<% component_class = field.index_component_class %>
|
|
6
|
-
|
|
12
|
+
|
|
13
|
+
<th
|
|
14
|
+
scope="col"
|
|
15
|
+
class="px-6 py-3 font-medium whitespace-nowrap <%= component_class.classes_for_table_cell.join(" ") %>"
|
|
16
|
+
>
|
|
7
17
|
<% if field.sortable? %>
|
|
8
18
|
<% if field.name == sort_order&.name %>
|
|
9
19
|
<% if sort_order&.ascending? %>
|
|
10
20
|
<%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :desc})) do %>
|
|
11
21
|
<%= repository.translate.field_label(field) %>
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
|
|
23
|
+
<svg
|
|
24
|
+
class="inline w-4 h-4 text-heading"
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
27
|
+
width="24"
|
|
28
|
+
height="24"
|
|
29
|
+
fill="none"
|
|
30
|
+
viewBox="0 0 24 24"
|
|
31
|
+
>
|
|
32
|
+
<path
|
|
33
|
+
stroke="currentColor"
|
|
34
|
+
stroke-linecap="round"
|
|
35
|
+
stroke-linejoin="round"
|
|
36
|
+
stroke-width="2"
|
|
37
|
+
d="M12 19V5m0 14-4-4m4 4 4-4"
|
|
38
|
+
/>
|
|
14
39
|
</svg>
|
|
15
40
|
<% end %>
|
|
16
41
|
<% else %>
|
|
17
42
|
<%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :asc})) do %>
|
|
18
43
|
<%= repository.translate.field_label(field) %>
|
|
19
|
-
|
|
20
|
-
|
|
44
|
+
|
|
45
|
+
<svg
|
|
46
|
+
class="inline w-4 h-4 text-heading"
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
49
|
+
width="24"
|
|
50
|
+
height="24"
|
|
51
|
+
fill="none"
|
|
52
|
+
viewBox="0 0 24 24"
|
|
53
|
+
>
|
|
54
|
+
<path
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
stroke-linecap="round"
|
|
57
|
+
stroke-linejoin="round"
|
|
58
|
+
stroke-width="2"
|
|
59
|
+
d="M12 6v13m0-13 4 4m-4-4-4 4"
|
|
60
|
+
/>
|
|
21
61
|
</svg>
|
|
22
62
|
<% end %>
|
|
23
63
|
<% end %>
|
|
24
64
|
<% else %>
|
|
25
65
|
<%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :asc})) do %>
|
|
26
66
|
<%= repository.translate.field_label(field) %>
|
|
27
|
-
|
|
28
|
-
|
|
67
|
+
|
|
68
|
+
<svg
|
|
69
|
+
class="inline w-4 h-4 text-heading"
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
72
|
+
width="24"
|
|
73
|
+
height="24"
|
|
74
|
+
fill="none"
|
|
75
|
+
viewBox="0 0 24 24"
|
|
76
|
+
>
|
|
77
|
+
<path
|
|
78
|
+
stroke="currentColor"
|
|
79
|
+
stroke-linecap="round"
|
|
80
|
+
stroke-linejoin="round"
|
|
81
|
+
stroke-width="2"
|
|
82
|
+
d="M8 20V7m0 13-4-4m4 4 4-4m4-12v13m0-13 4 4m-4-4-4 4"
|
|
83
|
+
/>
|
|
29
84
|
</svg>
|
|
30
85
|
<% end %>
|
|
31
86
|
<% end %>
|
|
@@ -34,29 +89,65 @@
|
|
|
34
89
|
<% end %>
|
|
35
90
|
</th>
|
|
36
91
|
<% end %>
|
|
37
|
-
|
|
92
|
+
|
|
93
|
+
<th scope="col" class="px-6 py-3"></th>
|
|
38
94
|
</tr>
|
|
39
95
|
</thead>
|
|
96
|
+
|
|
40
97
|
<tbody>
|
|
41
98
|
<% records.each do |record| %>
|
|
42
|
-
<tr class="bg-
|
|
99
|
+
<tr class="bg-neutral-primary border-b border-default">
|
|
43
100
|
<% columns.each do |field| %>
|
|
44
101
|
<% component_class = field.index_component_class %>
|
|
45
|
-
|
|
102
|
+
|
|
103
|
+
<td class="px-6 py-4 <%= component_class.classes_for_table_cell.join(" ") %>">
|
|
46
104
|
<%= render(field.index_component(record: record, repository: repository)) %>
|
|
47
105
|
</td>
|
|
48
106
|
<% end %>
|
|
49
|
-
|
|
107
|
+
|
|
108
|
+
<td class="px-6 py-4 text-right">
|
|
50
109
|
<div class="flex justify-end space-x-2">
|
|
51
110
|
<%= link_to(repository.routes.path_for(:show, id: record.id), class: "inline-block hover:text-blue-600 hover:dark:text-blue-500", :data => {:"turbo-frame" => "_top"}) do %>
|
|
52
|
-
<svg
|
|
53
|
-
|
|
54
|
-
|
|
111
|
+
<svg
|
|
112
|
+
class="w-6 h-6"
|
|
113
|
+
aria-hidden="true"
|
|
114
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
115
|
+
width="24"
|
|
116
|
+
height="24"
|
|
117
|
+
fill="none"
|
|
118
|
+
viewBox="0 0 24 24"
|
|
119
|
+
>
|
|
120
|
+
<path
|
|
121
|
+
stroke="currentColor"
|
|
122
|
+
stroke-width="2"
|
|
123
|
+
d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
<path
|
|
127
|
+
stroke="currentColor"
|
|
128
|
+
stroke-width="2"
|
|
129
|
+
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
|
130
|
+
/>
|
|
55
131
|
</svg>
|
|
56
132
|
<% end %>
|
|
133
|
+
|
|
57
134
|
<%= link_to(path_for_edit(record), class: "inline-block hover:text-blue-600 hover:dark:text-blue-500", :data => {:"turbo-frame" => "_top"}) do %>
|
|
58
|
-
<svg
|
|
59
|
-
|
|
135
|
+
<svg
|
|
136
|
+
class="w-6 h-6"
|
|
137
|
+
aria-hidden="true"
|
|
138
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
139
|
+
width="24"
|
|
140
|
+
height="24"
|
|
141
|
+
fill="none"
|
|
142
|
+
viewBox="0 0 24 24"
|
|
143
|
+
>
|
|
144
|
+
<path
|
|
145
|
+
stroke="currentColor"
|
|
146
|
+
stroke-linecap="round"
|
|
147
|
+
stroke-linejoin="round"
|
|
148
|
+
stroke-width="2"
|
|
149
|
+
d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"
|
|
150
|
+
/>
|
|
60
151
|
</svg>
|
|
61
152
|
<% end %>
|
|
62
153
|
</div>
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
<%= form_tag(repository.routes.path_for(:index), class: "flex items-center max-w-sm", method: :get) do %>
|
|
1
|
+
<%= form_tag(repository.routes.path_for(:index), class: "flex items-center max-w-sm space-x-2", method: :get) do %>
|
|
2
2
|
<%= label_tag(:query, repository.translate.search_label, class: "sr-only") %>
|
|
3
|
+
|
|
3
4
|
<div class="relative w-full">
|
|
4
|
-
<%= search_field_tag(:query, query, class: "bg-
|
|
5
|
+
<%= search_field_tag(:query, query, class: "px-3 py-2.5 bg-neutral-secondary-medium border border-default-medium rounded-base text-heading text-sm focus:ring-brand focus:border-brand block w-full placeholder:text-body") %>
|
|
5
6
|
</div>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
<%= button_tag(class: "inline-flex items-center justify-center shrink-0 text-white bg-brand hover:bg-brand-strong focus:ring-4 focus:ring-brand-medium shadow-xs rounded-base w-10 h-10 focus:outline-none") do %>
|
|
9
|
+
<svg
|
|
10
|
+
class="w-4 h-4"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
fill="none"
|
|
14
|
+
viewBox="0 0 20 20"
|
|
15
|
+
>
|
|
16
|
+
<path
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
stroke-linecap="round"
|
|
19
|
+
stroke-linejoin="round"
|
|
20
|
+
stroke-width="2"
|
|
21
|
+
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
|
|
22
|
+
/>
|
|
9
23
|
</svg>
|
|
24
|
+
|
|
10
25
|
<span class="sr-only"><%= repository.translate.search_button %></span>
|
|
11
26
|
<% end %>
|
|
12
27
|
|
|
@@ -16,6 +31,7 @@
|
|
|
16
31
|
<%= hidden_field_tag("scope[inverse_of]", scope[:inverse_of]) %>
|
|
17
32
|
<%= hidden_field_tag("scope[model]", scope[:model]) %>
|
|
18
33
|
<% end %>
|
|
34
|
+
|
|
19
35
|
<%= hidden_field_tag("sort[by]", sort_by) if sort_by? %>
|
|
20
36
|
<%= hidden_field_tag("sort[direction]", sort_direction) if sort_direction? %>
|
|
21
37
|
<% end %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<ul class="space-y-2 font-medium">
|
|
2
|
+
<% items.each do |item| %>
|
|
3
|
+
<li>
|
|
4
|
+
<%= link_to(item[:path], :class => "flex items-center px-2 py-1.5 text-body rounded-base hover:bg-neutral-tertiary hover:text-fg-brand group") do %>
|
|
5
|
+
<span class="ms-3"><%= item[:label] %></span>
|
|
6
|
+
<% end %>
|
|
7
|
+
</li>
|
|
8
|
+
<% end %>
|
|
9
|
+
</ul>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
# Renders the main navigation menu visible at the side of the viewport.
|
|
6
|
+
#
|
|
7
|
+
# The template for this component has access to the following methods:
|
|
8
|
+
#
|
|
9
|
+
# - `items`: Returns an `Array` of `Hashes` containing :label and :path keys
|
|
10
|
+
# for each item to render in the navigation.
|
|
11
|
+
# - `navigatable_repositories`: Returns an `Array` of `Uchi::Repository`
|
|
12
|
+
# instances. The `Array` is sorted alphanumerically by the
|
|
13
|
+
# `navigation_label` for each repository.
|
|
14
|
+
# - `repositories`: Returns an `Array` with an instance of each
|
|
15
|
+
# `Uchi::Repository` in the application.
|
|
16
|
+
class Navigation < ViewComponent::Base
|
|
17
|
+
protected
|
|
18
|
+
|
|
19
|
+
def items
|
|
20
|
+
navigatable_repositories.map do |repository|
|
|
21
|
+
{
|
|
22
|
+
label: repository.translate.navigation_label,
|
|
23
|
+
path: repository.routes.path_for(:index)
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def navigatable_repositories
|
|
29
|
+
repositories.sort_by { |repository| repository.translate.navigation_label.downcase }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def repositories
|
|
33
|
+
Uchi::Repository.all.map(&:new)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<span class="text-body-subtle">—</span>
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
<div class="items-start justify-between space-x-6 md:flex md:px-0">
|
|
9
9
|
<div>
|
|
10
|
-
<h1 class="text-3xl font-semibold tracking-tight text-
|
|
10
|
+
<h1 class="text-3xl font-semibold tracking-tight text-heading group">
|
|
11
|
+
<%= title %>
|
|
12
|
+
</h1>
|
|
13
|
+
|
|
11
14
|
<% if description.present? %>
|
|
12
|
-
<div class="text-lg text-
|
|
15
|
+
<div class="text-lg text-body-subtle lg:mb-0 lg:max-w-2xl">
|
|
16
|
+
<%= description %>
|
|
17
|
+
</div>
|
|
13
18
|
<% end %>
|
|
14
19
|
</div>
|
|
15
20
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<li>
|
|
2
|
-
<%= link_to(@paginator.page, page_url(page_number), aria: { current: "page" }, class: "
|
|
2
|
+
<%= link_to(@paginator.page, page_url(page_number), aria: { current: "page" }, class: "flex items-center justify-center text-fg-brand bg-neutral-tertiary-medium box-border border border-default-medium hover:text-fg-brand font-medium text-sm w-9 h-9 focus:outline-none") %>
|
|
3
3
|
</li>
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
<li
|
|
1
|
+
<li
|
|
2
|
+
class="
|
|
3
|
+
flex items-center justify-center text-body bg-neutral-secondary-medium
|
|
4
|
+
box-border border border-default-medium hover:bg-neutral-tertiary-medium
|
|
5
|
+
hover:text-heading font-medium text-sm w-9 h-9 focus:outline-none
|
|
6
|
+
"
|
|
7
|
+
>
|
|
2
8
|
…
|
|
3
9
|
</li>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<li>
|
|
2
|
-
<%= link_to(page_number, page_url(page_number), class: "flex items-center justify-center
|
|
2
|
+
<%= link_to(page_number, page_url(page_number), class: "flex items-center justify-center text-body bg-neutral-secondary-medium box-border border border-default-medium hover:bg-neutral-tertiary-medium hover:text-heading font-medium text-sm w-9 h-9 focus:outline-none") %>
|
|
3
3
|
</li>
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
<li>
|
|
2
|
-
<%= link_to(url, class: "flex items-center justify-center
|
|
2
|
+
<%= link_to(url, class: "flex items-center justify-center text-body bg-neutral-secondary-medium box-border border border-default-medium hover:bg-neutral-tertiary-medium hover:text-heading font-medium rounded-e-base text-sm w-9 h-9 focus:outline-none") do %>
|
|
3
3
|
<span class="sr-only">Next</span>
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
class="w-4 h-4 rtl:rotate-180"
|
|
7
|
+
aria-hidden="true"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
width="24"
|
|
10
|
+
height="24"
|
|
11
|
+
fill="none"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
d="m9 5 7 7-7 7"
|
|
20
|
+
/>
|
|
6
21
|
</svg>
|
|
7
22
|
<% end %>
|
|
8
23
|
</li>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<nav aria-label="<%= aria_label %>">
|
|
2
|
-
<ul class="flex
|
|
2
|
+
<ul class="flex -space-x-px text-sm">
|
|
3
3
|
<%= render(Uchi::Ui::Pagination::PreviousLink.new(paginator: @paginator)) %>
|
|
4
|
+
|
|
4
5
|
<% pages.each do |page_number| %>
|
|
5
6
|
<% if page_number.nil? %>
|
|
6
7
|
<%= render(Uchi::Ui::Pagination::Gap.new(page_number: page_number, paginator: @paginator)) %>
|
|
@@ -10,6 +11,7 @@
|
|
|
10
11
|
<%= render(Uchi::Ui::Pagination::Link.new(page_number: page_number, paginator: @paginator)) %>
|
|
11
12
|
<% end %>
|
|
12
13
|
<% end %>
|
|
14
|
+
|
|
13
15
|
<%= render(Uchi::Ui::Pagination::NextLink.new(paginator: @paginator)) %>
|
|
14
16
|
</ul>
|
|
15
17
|
</nav>
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
<li>
|
|
2
|
-
<%= link_to(url, class: "flex items-center justify-center
|
|
2
|
+
<%= link_to(url, class: "flex items-center justify-center text-body bg-neutral-secondary-medium box-border border border-default-medium hover:bg-neutral-tertiary-medium hover:text-heading font-medium rounded-s-base text-sm w-9 h-9 focus:outline-none") do %>
|
|
3
3
|
<span class="sr-only">Previous</span>
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
class="w-4 h-4 rtl:rotate-180"
|
|
7
|
+
aria-hidden="true"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
width="24"
|
|
10
|
+
height="24"
|
|
11
|
+
fill="none"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
d="m15 19-7-7 7-7"
|
|
20
|
+
/>
|
|
6
21
|
</svg>
|
|
7
22
|
<% end %>
|
|
8
23
|
</li>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<%= render(Uchi::Ui::Frame.new) do %>
|
|
2
|
-
<dl class="divide-y divide-
|
|
2
|
+
<dl class="divide-y divide-default">
|
|
3
3
|
<% attribute_fields.each do |field| %>
|
|
4
4
|
<div class="px-4 py-4 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
|
|
5
|
-
<dt class="font-semibold text-
|
|
5
|
+
<dt class="font-semibold text-heading">
|
|
6
6
|
<%= repository.translate.field_label(field) %>
|
|
7
7
|
</dt>
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
<dd class="font-light text-body col-span-3">
|
|
9
10
|
<%= render(field.show_component(record: record, repository: repository)) %>
|
|
10
11
|
</dd>
|
|
11
12
|
</div>
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
<div role="status">
|
|
2
|
-
<svg
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
<svg
|
|
3
|
+
aria-hidden="true"
|
|
4
|
+
class="w-8 h-8 text-neutral-tertiary animate-spin fill-brand"
|
|
5
|
+
viewBox="0 0 100 101"
|
|
6
|
+
fill="none"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<path
|
|
15
|
+
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
|
16
|
+
fill="currentFill"
|
|
17
|
+
/>
|
|
5
18
|
</svg>
|
|
19
|
+
|
|
6
20
|
<span class="sr-only"><%= message %></span>
|
|
7
21
|
</div>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Actions
|
|
5
|
+
# Controller for creating action executions on repository records.
|
|
6
|
+
#
|
|
7
|
+
# This controller handles POST requests to execute actions registered on
|
|
8
|
+
# repositories. It finds the appropriate action, loads the records, executes
|
|
9
|
+
# the action, and handles the response.
|
|
10
|
+
class ExecutionsController < Uchi::Controller
|
|
11
|
+
def create
|
|
12
|
+
repository = find_repository
|
|
13
|
+
action = find_action(repository: repository)
|
|
14
|
+
input = build_input(action: action)
|
|
15
|
+
records = find_records(repository: repository)
|
|
16
|
+
|
|
17
|
+
response = action.perform(records, input)
|
|
18
|
+
response = default_response unless response.is_a?(Uchi::ActionResponse)
|
|
19
|
+
handle_response(response: response, repository: repository)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_response
|
|
25
|
+
ActionResponse.success
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Finds and instantiates the repository based on the model param.
|
|
29
|
+
#
|
|
30
|
+
# @return [Uchi::Repository]
|
|
31
|
+
# @raise [NameError] if the repository is not found
|
|
32
|
+
def find_repository
|
|
33
|
+
model_name = params[:model]
|
|
34
|
+
repository_class = Uchi::Repository.for_model(model_name)
|
|
35
|
+
|
|
36
|
+
unless repository_class
|
|
37
|
+
raise NameError, "Repository for model '#{model_name}' not found"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
repository_class.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Finds the action instance on the repository.
|
|
44
|
+
#
|
|
45
|
+
# @param repository [Uchi::Repository] - The repository instance
|
|
46
|
+
# @return [Uchi::Action]
|
|
47
|
+
# @raise [NameError] if the action is not found
|
|
48
|
+
def find_action(repository:)
|
|
49
|
+
action_name = params[:action_name]
|
|
50
|
+
action = repository.actions.find { |a| a.class.name.to_s == action_name }
|
|
51
|
+
return action if action
|
|
52
|
+
|
|
53
|
+
raise \
|
|
54
|
+
NameError,
|
|
55
|
+
"Action #{action_name.inspect} not found on " \
|
|
56
|
+
"#{repository.class.name}. Expected one of " \
|
|
57
|
+
"#{repository.actions.map { |a| a.class.name }.join(", ")}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Loads the records to operate on based on the ids or id params.
|
|
61
|
+
#
|
|
62
|
+
# @param repository [Uchi::Repository] - The repository instance
|
|
63
|
+
# @return [ActiveRecord::Relation]
|
|
64
|
+
def find_records(repository:)
|
|
65
|
+
ids = Array(params[:ids] || params[:id])
|
|
66
|
+
repository.find_many(ids)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Builds the input hash from params for actions with fields.
|
|
70
|
+
#
|
|
71
|
+
# @param action [Uchi::Action] - The action instance
|
|
72
|
+
# @return [Hash]
|
|
73
|
+
def build_input(action:)
|
|
74
|
+
return {} unless action.requires_input?
|
|
75
|
+
|
|
76
|
+
action.fields.each_with_object({}) do |field, hash|
|
|
77
|
+
key = field.name
|
|
78
|
+
hash[key] = params[key] if params.key?(key)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Handles the action response by redirecting, sending a file, rendering
|
|
83
|
+
# turbo stream, or falling back to the repository index.
|
|
84
|
+
#
|
|
85
|
+
# @param response [Uchi::ActionResponse] - The action response
|
|
86
|
+
# @param repository [Uchi::Repository] - The repository instance
|
|
87
|
+
def handle_response(response:, repository:)
|
|
88
|
+
flash_key = response.success? ? :success : :alert
|
|
89
|
+
|
|
90
|
+
if response.redirect?
|
|
91
|
+
flash[flash_key] = response.message_text if response.message_text
|
|
92
|
+
redirect_to(response.redirect_path, status: :see_other)
|
|
93
|
+
elsif response.download?
|
|
94
|
+
send_file(response.file_path, filename: response.filename)
|
|
95
|
+
elsif response.turbo_stream?
|
|
96
|
+
render(turbo_stream: response.turbo_stream_block.call)
|
|
97
|
+
else
|
|
98
|
+
flash[flash_key] = response.message_text if response.message_text
|
|
99
|
+
redirect_back_or_to(
|
|
100
|
+
repository.routes.path_for(:index),
|
|
101
|
+
status: :see_other
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -134,8 +134,28 @@ module Uchi
|
|
|
134
134
|
.permit(*permitted_params)
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
# Returns the repository class associated with this controller.
|
|
138
|
+
#
|
|
139
|
+
# For example, Uchi::AuthorsController would return
|
|
140
|
+
# Uchi::Repositories::Author.
|
|
141
|
+
#
|
|
142
|
+
# @return [Class<Uchi::Repository>]
|
|
137
143
|
def repository_class
|
|
138
|
-
|
|
144
|
+
return @repository_class if defined?(@repository_class)
|
|
145
|
+
|
|
146
|
+
controller_name = self.class.name.demodulize
|
|
147
|
+
base_name = controller_name.sub(/Controller$/, "")
|
|
148
|
+
repository_name = base_name.singularize
|
|
149
|
+
begin
|
|
150
|
+
@repository_class = Uchi::Repositories.const_get(repository_name)
|
|
151
|
+
rescue NameError
|
|
152
|
+
raise \
|
|
153
|
+
NameError,
|
|
154
|
+
"No repository found for controller #{self.class.name}. Expected " \
|
|
155
|
+
"Uchi::Repositories::#{repository_name} to exist. If you want to " \
|
|
156
|
+
"a repository with a different name, override #repository_class in " \
|
|
157
|
+
"your controller."
|
|
158
|
+
end
|
|
139
159
|
end
|
|
140
160
|
|
|
141
161
|
helper_method def scope_params
|
|
@@ -1,31 +1,25 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
+
|
|
2
3
|
<html>
|
|
3
4
|
<head>
|
|
4
|
-
<title
|
|
5
|
+
<title>
|
|
6
|
+
<%= content_for?(:page_title) ? yield(:page_title) : "Uchi" %>
|
|
7
|
+
</title>
|
|
8
|
+
|
|
5
9
|
<%= csrf_meta_tags %>
|
|
6
10
|
<%= csp_meta_tag %>
|
|
11
|
+
|
|
7
12
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
13
|
|
|
9
14
|
<%= javascript_include_tag "uchi/application", "data-turbo-track" => "reload" %>
|
|
10
15
|
<%= stylesheet_link_tag "uchi/application", media: "all", "data-turbo-track": "reload" %>
|
|
11
16
|
</head>
|
|
12
|
-
|
|
17
|
+
|
|
18
|
+
<body class="antialiased bg-neutral-secondary-medium p-2">
|
|
13
19
|
<div class="md:flex">
|
|
14
|
-
|
|
15
|
-
<ul class="space-y-2 font-medium">
|
|
16
|
-
<% Uchi::Repository.all.map { |repository| repository.new }.sort_by { |repository| repository.translate.navigation_label.downcase }.each do |repository| %>
|
|
17
|
-
<li>
|
|
18
|
-
<%= link_to(repository.routes.path_for(:index), :class => "flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group") do %>
|
|
19
|
-
<span class="ms-3"><%= repository.translate.navigation_label %></span>
|
|
20
|
-
<% end %>
|
|
21
|
-
</li>
|
|
22
|
-
<% end %>
|
|
23
|
-
</ul>
|
|
24
|
-
</nav>
|
|
20
|
+
<%= render(partial: "uchi/navigation/main") %>
|
|
25
21
|
|
|
26
|
-
<main class="py-6 overflow-x-hidden grow md:px-6">
|
|
27
|
-
<%= yield %>
|
|
28
|
-
</main>
|
|
22
|
+
<main class="py-6 overflow-x-hidden grow md:px-6"><%= yield %></main>
|
|
29
23
|
</div>
|
|
30
24
|
|
|
31
25
|
<%= render "layouts/uchi/flash_messages" %>
|