avo 4.0.0.beta.2 → 4.0.0.beta.4
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/Gemfile.lock +1 -1
- data/app/assets/builds/avo/application.css +355 -51
- data/app/assets/builds/avo/application.js +166 -166
- data/app/assets/builds/avo/application.js.map +4 -4
- data/app/assets/stylesheets/application.css +2 -0
- data/app/assets/stylesheets/css/components/hotkey.css +50 -0
- data/app/assets/stylesheets/css/components/input.css +0 -6
- data/app/assets/stylesheets/css/components/ui/state.css +129 -0
- data/app/assets/stylesheets/css/layout.css +3 -4
- data/app/assets/stylesheets/css/pagination.css +12 -6
- data/app/assets/stylesheets/css/typography.css +18 -1
- data/app/assets/svgs/avo/circle-minus.svg +3 -0
- data/app/components/avo/alert_component.rb +4 -4
- data/app/components/avo/backtrace_alert_component.html.erb +1 -1
- data/app/components/avo/base_component.rb +9 -0
- data/app/components/avo/button_component.rb +2 -1
- data/app/components/avo/debug/status_component.html.erb +2 -2
- data/app/components/avo/empty_state_component.html.erb +15 -4
- data/app/components/avo/empty_state_component.rb +9 -0
- data/app/components/avo/fields/common/files/view_type/grid_item_component.html.erb +1 -1
- data/app/components/avo/fields/common/key_value_component.html.erb +1 -1
- data/app/components/avo/fields/common/stars_component.html.erb +1 -1
- data/app/components/avo/fields/common/status_viewer_component.html.erb +3 -3
- data/app/components/avo/fields/preview_field/index_component.rb +1 -1
- data/app/components/avo/fields/stars_field/edit_component.html.erb +1 -1
- data/app/components/avo/filters_component.html.erb +1 -1
- data/app/components/avo/items/switcher_component.html.erb +1 -1
- data/app/components/avo/keyboard_shortcuts_component.html.erb +29 -0
- data/app/components/avo/keyboard_shortcuts_component.rb +127 -0
- data/app/components/avo/media_library/item_details_component.html.erb +2 -2
- data/app/components/avo/media_library/list_component.html.erb +1 -1
- data/app/components/avo/media_library/list_item_component.html.erb +2 -2
- data/app/components/avo/modal_component.html.erb +39 -15
- data/app/components/avo/modal_component.rb +10 -0
- data/app/components/avo/paginator_component.html.erb +23 -17
- data/app/components/avo/paginator_component.rb +18 -0
- data/app/components/avo/resource_component.rb +14 -7
- data/app/components/avo/sidebar/group_component.html.erb +1 -1
- data/app/components/avo/sidebar/link_component.html.erb +13 -5
- data/app/components/avo/sidebar/link_component.rb +17 -0
- data/app/components/avo/sidebar/section_component.html.erb +1 -1
- data/app/components/avo/sidebar_component.html.erb +2 -2
- data/app/components/avo/sidebar_profile_component.html.erb +1 -1
- data/app/components/avo/u_i/search_input_component.html.erb +2 -2
- data/app/components/avo/views/resource_index_component.rb +1 -1
- data/app/javascript/application.js +12 -28
- data/app/javascript/js/controllers/base_modal_controller.js +65 -0
- data/app/javascript/js/controllers/confirm_dialog_controller.js +18 -0
- data/app/javascript/js/controllers/modal_controller.js +18 -26
- data/app/javascript/js/controllers/persistent_modal_controller.js +50 -0
- data/app/javascript/js/controllers/search_controller.js +0 -4
- data/app/javascript/js/controllers/sidebar_controller.js +22 -0
- data/app/javascript/js/controllers.js +4 -0
- data/app/javascript/js/global_hotkeys.js +77 -0
- data/app/javascript/js/helpers/toggle_hidden.js +7 -0
- data/app/views/avo/actions/show.html.erb +1 -1
- data/app/views/avo/base/_date_time_filter.html.erb +1 -1
- data/app/views/avo/base/preview.html.erb +1 -1
- data/app/views/avo/debug/_valid_indicator.html.erb +2 -2
- data/app/views/avo/home/failed_to_load.html.erb +40 -13
- data/app/views/avo/media_library/_form.html.erb +1 -1
- data/app/views/avo/partials/_color_scheme_switcher.html.erb +4 -4
- data/app/views/avo/partials/_confirm_dialog.html.erb +3 -3
- data/app/views/avo/partials/_custom_tools_alert.html.erb +3 -3
- data/app/views/avo/partials/_sortable_component.html.erb +3 -3
- data/app/views/avo/partials/_table_header.html.erb +1 -1
- data/app/views/avo/private/_links_and_buttons.html.erb +2 -2
- data/app/views/avo/private/design.html.erb +4 -4
- data/app/views/avo/sidebar/_license_warning.html.erb +2 -2
- data/app/views/layouts/avo/application.html.erb +1 -0
- data/lib/avo/resources/base.rb +1 -0
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/resource_generator.rb +3 -3
- data/lib/generators/avo/templates/initializer/avo.tt +4 -4
- data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
- metadata +11 -15
- data/app/assets/svgs/avo/arrow-circle-right.svg +0 -1
- data/app/assets/svgs/avo/bell.svg +0 -3
- data/app/assets/svgs/avo/color-swatch.svg +0 -1
- data/app/assets/svgs/avo/dashboards.svg +0 -6
- data/app/assets/svgs/avo/exclamation.svg +0 -1
- data/app/assets/svgs/avo/filter.svg +0 -1
- data/app/assets/svgs/avo/logout.svg +0 -3
- data/app/assets/svgs/avo/resources.svg +0 -13
- data/app/assets/svgs/avo/save.svg +0 -8
- data/app/assets/svgs/avo/selector.svg +0 -1
- data/app/assets/svgs/avo/sort-ascending.svg +0 -1
- data/app/assets/svgs/avo/sort-descending.svg +0 -1
- data/app/assets/svgs/avo/times.svg +0 -3
- data/app/assets/svgs/avo/tools.svg +0 -3
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Avo::KeyboardShortcutsComponent < Avo::BaseComponent
|
|
4
|
+
def sections
|
|
5
|
+
@sections ||= [
|
|
6
|
+
build_section(
|
|
7
|
+
"Navigation",
|
|
8
|
+
[
|
|
9
|
+
shortcut(action: "Show keyboard shortcuts", keys: ["?"]),
|
|
10
|
+
shortcut(action: "Focus resource search", keys: {mac: ["Cmd", "K"], other: ["Ctrl", "K"]}),
|
|
11
|
+
shortcut(action: "Toggle sidebar", keys: {mac: ["Cmd", "\\"], other: ["Ctrl", "\\"]}),
|
|
12
|
+
shortcut(action: "Close modal", keys: ["Esc"]),
|
|
13
|
+
shortcut(
|
|
14
|
+
action: "Navigate options in the modal",
|
|
15
|
+
any_of: [["↑"], ["↓"]],
|
|
16
|
+
keys_aria_label: "Up arrow or down arrow"
|
|
17
|
+
),
|
|
18
|
+
shortcut(action: "Go back", keys: ["B"])
|
|
19
|
+
]
|
|
20
|
+
),
|
|
21
|
+
build_section(
|
|
22
|
+
"Edit view",
|
|
23
|
+
[
|
|
24
|
+
shortcut(action: "Submit form", keys: {mac: ["Cmd", "↵"], other: ["Ctrl", "↵"]}),
|
|
25
|
+
shortcut(action: "Unfocus field", keys: ["Esc"])
|
|
26
|
+
]
|
|
27
|
+
),
|
|
28
|
+
build_section(
|
|
29
|
+
"Show view",
|
|
30
|
+
[
|
|
31
|
+
shortcut(action: "Delete record", keys: ["D"]),
|
|
32
|
+
shortcut(action: "Edit record", keys: ["E"])
|
|
33
|
+
]
|
|
34
|
+
),
|
|
35
|
+
build_section(
|
|
36
|
+
"Index view",
|
|
37
|
+
[
|
|
38
|
+
shortcut(action: "Create new record", keys: ["C"])
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_shortcut_keys(shortcut)
|
|
45
|
+
if shortcut[:any_of].present?
|
|
46
|
+
render_shortcut_alternatives(shortcut[:any_of])
|
|
47
|
+
else
|
|
48
|
+
render_chord(shortcut[:keys])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def build_section(title, shortcuts)
|
|
55
|
+
{
|
|
56
|
+
id: "hotkey-group-#{title.parameterize.underscore}",
|
|
57
|
+
title: title,
|
|
58
|
+
shortcuts: shortcuts
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def shortcut(action:, keys: nil, any_of: nil, keys_aria_label: nil)
|
|
63
|
+
{
|
|
64
|
+
action: action,
|
|
65
|
+
keys: keys,
|
|
66
|
+
any_of: any_of,
|
|
67
|
+
keys_aria_label: keys_aria_label
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render_shortcut_alternatives(chords)
|
|
72
|
+
helpers.safe_join(
|
|
73
|
+
chords.flat_map.with_index do |chord, index|
|
|
74
|
+
[
|
|
75
|
+
(tag.span("or", class: "hotkey__or", aria: {hidden: true}) if index.positive?),
|
|
76
|
+
render_chord(chord)
|
|
77
|
+
].compact
|
|
78
|
+
end
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def render_chord(chord)
|
|
83
|
+
tag.span class: "hotkey__combo" do
|
|
84
|
+
helpers.safe_join(chord_fragments(chord))
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def chord_fragments(chord)
|
|
89
|
+
return platform_split_fragments(chord) if platform_split_chord?(chord)
|
|
90
|
+
|
|
91
|
+
key_fragments(Array(chord))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def platform_split_fragments(chord)
|
|
95
|
+
[
|
|
96
|
+
platform_modifier_key,
|
|
97
|
+
*key_fragments(chord[:mac].drop(1), leading_separator: true)
|
|
98
|
+
]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def key_fragments(keys, leading_separator: false)
|
|
102
|
+
keys.flat_map.with_index do |key, index|
|
|
103
|
+
[
|
|
104
|
+
(tag.span("+", class: "hotkey__key-separator") if leading_separator || index.positive?),
|
|
105
|
+
tag.kbd(key)
|
|
106
|
+
].compact
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def platform_modifier_key
|
|
111
|
+
tag.kbd do
|
|
112
|
+
helpers.safe_join([
|
|
113
|
+
tag.abbr("⌘", title: "Command", class: "no-underline os-pc:hidden"),
|
|
114
|
+
tag.abbr("CTRL", title: "CTRL", class: "no-underline os-mac:hidden")
|
|
115
|
+
])
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def platform_split_chord?(chord)
|
|
120
|
+
chord.is_a?(Hash) &&
|
|
121
|
+
chord[:mac].is_a?(Array) &&
|
|
122
|
+
chord[:other].is_a?(Array) &&
|
|
123
|
+
chord[:mac].size == chord[:other].size &&
|
|
124
|
+
chord[:mac].first.to_s == "Cmd" &&
|
|
125
|
+
chord[:other].first.to_s == "Ctrl"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="relative flex flex-col w-full max @container/details">
|
|
2
|
-
<%= link_to helpers.svg('
|
|
2
|
+
<%= link_to helpers.svg('tabler/outline/x', class: "size-6"), helpers.avo.media_library_index_path,
|
|
3
3
|
class: "absolute z-10 inset-auto end-0 top-0 mt-2 me-2 block bg-white p-1 rounded-lg text-slate-600 hover:text-slate-900",
|
|
4
4
|
title: t('avo.close'),
|
|
5
5
|
data: {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<%= video_tag(helpers.main_app.url_for(@blob), controls: true, preload: false, class: 'w-full') %>
|
|
16
16
|
<% else %>
|
|
17
17
|
<div class="relative h-full flex flex-col justify-center items-center w-full bg-slate-100">
|
|
18
|
-
<%= helpers.svg "
|
|
18
|
+
<%= helpers.svg "tabler/outline/file-text", class: 'h-10 text-gray-600 mb-2' %>
|
|
19
19
|
</div>
|
|
20
20
|
<% end %>
|
|
21
21
|
<div class="flex justify-center w-full text-sm gap-4">
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
media_library_attach_target: 'dropzone',
|
|
29
29
|
action: 'click->media-library-attach#triggerFileBrowser',
|
|
30
30
|
} do %>
|
|
31
|
-
<%= helpers.svg '
|
|
31
|
+
<%= helpers.svg 'tabler/outline/cloud-upload', class: 'size-6 text-gray-400' %> Upload a file
|
|
32
32
|
<small>Click to browse or drag and drop</small>
|
|
33
33
|
<% end %>
|
|
34
34
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
class: "relative group min-h-full max-w-full flex-1 flex flex-col justify-between gap-2 border border-slate-200 p-1.5 rounded-xl hover:border-blue-500 hover:outline data-[selected=true]:border-blue-500 data-[selected=true]:outline outline-blue-500",
|
|
4
4
|
data: do %>
|
|
5
5
|
<% if false && @attaching %>
|
|
6
|
-
<div class="absolute bg-blue-500 group-hover:opacity-100 group-data-[selected=true]:opacity-100 opacity-0 inset-auto start-0 top-0 text-white rounded-ss-xl rounded-ee-xl -ms-px -mt-px p-2"><div class="border border-white"><%= helpers.svg "
|
|
6
|
+
<div class="absolute bg-blue-500 group-hover:opacity-100 group-data-[selected=true]:opacity-100 opacity-0 inset-auto start-0 top-0 text-white rounded-ss-xl rounded-ee-xl -ms-px -mt-px p-2"><div class="border border-white"><%= helpers.svg "tabler/outline/check", class: 'group-data-[selected=true]:opacity-100 opacity-0 size-4' %></div></div>
|
|
7
7
|
<% end %>
|
|
8
8
|
<div class="flex flex-col h-full aspect-video overflow-hidden rounded-lg justify-center items-center">
|
|
9
9
|
<% if blob.image? %>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<%= video_tag(helpers.main_app.url_for(blob), controls: true, preload: false, class: 'w-full') %>
|
|
15
15
|
<% else %>
|
|
16
16
|
<div class="relative h-full flex flex-col justify-center items-center w-full bg-slate-100">
|
|
17
|
-
<%= helpers.svg "
|
|
17
|
+
<%= helpers.svg "tabler/outline/file-text", class: 'h-10 text-gray-600 mb-2' %>
|
|
18
18
|
</div>
|
|
19
19
|
<% end %>
|
|
20
20
|
</div>
|
|
@@ -1,25 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
<%= tag.div class: class_names("modal", @class, {"modal--width-#{@width}": @width.present?, "modal--height-#{@height}": @height.present?}), tabindex: -1, data: {
|
|
2
|
+
controller: "#{ctrl} modal-size",
|
|
3
|
+
"#{ctrl}-target": "modal",
|
|
4
|
+
"#{ctrl}-close-modal-on-backdrop-click-value": close_modal_on_backdrop_click,
|
|
5
|
+
"modal-size-current-width-value": @width,
|
|
6
|
+
"modal-size-current-height-value": @height,
|
|
7
|
+
**@data
|
|
8
|
+
}, **(@hidden ? { hidden: true } : {}) do %>
|
|
9
|
+
<%= tag.div(
|
|
10
|
+
aria: { expanded: true },
|
|
11
|
+
class: "modal__overlay",
|
|
12
|
+
data: {
|
|
13
|
+
"#{ctrl}-target": "backdrop",
|
|
14
|
+
action: "click->#{ctrl}#close"
|
|
15
|
+
}
|
|
16
|
+
) %>
|
|
17
|
+
<div
|
|
18
|
+
aria-expanded="true"
|
|
19
|
+
role="dialog"
|
|
20
|
+
aria-modal="true"
|
|
21
|
+
class="modal__card-container"
|
|
22
|
+
data-modal-size-target="container"
|
|
23
|
+
>
|
|
10
24
|
<div class="modal__card">
|
|
11
25
|
<div class="card">
|
|
12
26
|
<div class="card__wrapper">
|
|
13
27
|
<% if has_header? %>
|
|
14
28
|
<div class="card__header">
|
|
15
|
-
<div class="flex
|
|
16
|
-
<div class="flex grow items-center
|
|
17
|
-
<div class="flex flex-col gap-0.5
|
|
29
|
+
<div class="flex w-full items-start gap-4">
|
|
30
|
+
<div class="flex min-w-0 grow items-center">
|
|
31
|
+
<div class="flex flex-col items-start gap-0.5">
|
|
18
32
|
<% if @title.present? || heading? %>
|
|
19
33
|
<p class="modal__title">
|
|
20
34
|
<%= @title.presence || heading %>
|
|
21
35
|
</p>
|
|
22
36
|
<% end %>
|
|
37
|
+
|
|
23
38
|
<% if @description.present? %>
|
|
24
39
|
<p class="card__description">
|
|
25
40
|
<%= @description %>
|
|
@@ -27,20 +42,28 @@
|
|
|
27
42
|
<% end %>
|
|
28
43
|
</div>
|
|
29
44
|
</div>
|
|
45
|
+
|
|
30
46
|
<div class="flex items-center gap-2">
|
|
31
47
|
<% if false && Rails.env.development? %>
|
|
32
48
|
<%= render partial: "avo/modal/size_selector", locals: { type: 'width', width: @width, height: @height } %>
|
|
33
49
|
<%= render partial: "avo/modal/size_selector", locals: { type: 'height', width: @width, height: @height } %>
|
|
34
50
|
<% end %>
|
|
51
|
+
|
|
35
52
|
<% if @show_close_button %>
|
|
36
|
-
<button
|
|
37
|
-
|
|
53
|
+
<button
|
|
54
|
+
class="modal__close-button"
|
|
55
|
+
data-action="click-><%= ctrl %>#closeModal"
|
|
56
|
+
aria-label="<%= t('avo.close') %>"
|
|
57
|
+
tabindex="-1"
|
|
58
|
+
>
|
|
59
|
+
<%= helpers.svg "tabler/outline/x", class: "size-5" %>
|
|
38
60
|
</button>
|
|
39
61
|
<% end %>
|
|
40
62
|
</div>
|
|
41
63
|
</div>
|
|
42
64
|
</div>
|
|
43
65
|
<% end %>
|
|
66
|
+
|
|
44
67
|
<div class="card__body <%= @body_class %>">
|
|
45
68
|
<% if content? %>
|
|
46
69
|
<div class="modal__body-content overflow-auto">
|
|
@@ -48,6 +71,7 @@
|
|
|
48
71
|
</div>
|
|
49
72
|
<% end %>
|
|
50
73
|
</div>
|
|
74
|
+
|
|
51
75
|
<% if controls? %>
|
|
52
76
|
<div class="card__footer modal__controls">
|
|
53
77
|
<%= controls %>
|
|
@@ -57,4 +81,4 @@
|
|
|
57
81
|
</div>
|
|
58
82
|
</div>
|
|
59
83
|
</div>
|
|
60
|
-
|
|
84
|
+
<% end %>
|
|
@@ -12,6 +12,14 @@ class Avo::ModalComponent < Avo::BaseComponent
|
|
|
12
12
|
prop :title
|
|
13
13
|
prop :description
|
|
14
14
|
prop :show_close_button, default: true
|
|
15
|
+
prop :behavior, default: :ephemeral # :ephemeral removes from DOM on close, :persistent toggles hidden attribute
|
|
16
|
+
prop :class, default: ""
|
|
17
|
+
prop :data, default: {}.freeze
|
|
18
|
+
prop :hidden, default: false
|
|
19
|
+
|
|
20
|
+
def stimulus_controller
|
|
21
|
+
(@behavior == :persistent) ? "persistent-modal" : "modal"
|
|
22
|
+
end
|
|
15
23
|
|
|
16
24
|
def height_classes
|
|
17
25
|
"max-h-[calc(100dvh-5rem)] min-h-1/4"
|
|
@@ -20,4 +28,6 @@ class Avo::ModalComponent < Avo::BaseComponent
|
|
|
20
28
|
def has_header?
|
|
21
29
|
@title.present? || @description.present? || heading? || @show_close_button
|
|
22
30
|
end
|
|
31
|
+
|
|
32
|
+
def ctrl = stimulus_controller
|
|
23
33
|
end
|
|
@@ -29,23 +29,29 @@
|
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
31
|
<%# Right: info + navigation grouped pill %>
|
|
32
|
-
<div class="pagination__controls">
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
<div class="pagination__controls-wrap">
|
|
33
|
+
<div class="pagination__controls">
|
|
34
|
+
<% if @resource.pagination_type.default? %>
|
|
35
|
+
<div class="pagination__info">
|
|
36
|
+
<span class="pagination__info-number">
|
|
37
|
+
<%= "#{formatted_number(@pagy.from)}-#{formatted_number(@pagy.to)}" %>
|
|
38
|
+
</span>
|
|
39
|
+
of
|
|
40
|
+
<% if @pagy.count >= 10_000 %>
|
|
41
|
+
<span title="<%= formatted_count %>" data-tippy="tooltip">
|
|
42
|
+
<%= number_to_social(@pagy.count, start_at: 10_000) %>
|
|
43
|
+
</span>
|
|
44
|
+
<% else %>
|
|
45
|
+
<span><%= formatted_count %></span>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
<% if @pagy.pages > 1 %>
|
|
51
|
+
<div class="pagination__nav">
|
|
52
|
+
<%== formatted_series_nav %>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
50
56
|
</div>
|
|
51
57
|
</div>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Avo::PaginatorComponent < Avo::BaseComponent
|
|
4
|
+
NUMBER_DELIMITER = "."
|
|
5
|
+
|
|
4
6
|
prop :resource
|
|
5
7
|
prop :parent_record
|
|
6
8
|
prop :parent_resource
|
|
@@ -52,4 +54,20 @@ class Avo::PaginatorComponent < Avo::BaseComponent
|
|
|
52
54
|
num = helpers.content_tag(:span, option, class: "pagination__per-page-option-num")
|
|
53
55
|
"#{num} #{t("avo.per_page").downcase}".html_safe
|
|
54
56
|
end
|
|
57
|
+
|
|
58
|
+
def formatted_count
|
|
59
|
+
formatted_number(@pagy.count)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def formatted_series_nav
|
|
63
|
+
@pagy.series_nav(anchor_string: %(data-turbo-frame="#{@turbo_frame}"))
|
|
64
|
+
.gsub(/>(\d{4,})</) { |match| match.sub($1, formatted_number($1)) }
|
|
65
|
+
.html_safe
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def formatted_number(number)
|
|
71
|
+
helpers.number_with_delimiter(number, delimiter: NUMBER_DELIMITER)
|
|
72
|
+
end
|
|
55
73
|
end
|
|
@@ -114,10 +114,11 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
114
114
|
style: :text,
|
|
115
115
|
title: control.title,
|
|
116
116
|
data: {
|
|
117
|
-
|
|
118
|
-
action:
|
|
117
|
+
hotkey: "b",
|
|
118
|
+
action: ("click->modal#close" if via_belongs_to),
|
|
119
|
+
tippy: control.title ? :tooltip : nil
|
|
119
120
|
}.compact,
|
|
120
|
-
icon: "
|
|
121
|
+
icon: "tabler/outline/arrow-left" do
|
|
121
122
|
control.label
|
|
122
123
|
end
|
|
123
124
|
end
|
|
@@ -157,12 +158,13 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
157
158
|
title: control.title,
|
|
158
159
|
aria_label: control.title,
|
|
159
160
|
data: {
|
|
161
|
+
hotkey: "d",
|
|
160
162
|
turbo_confirm: t("avo.are_you_sure", item: @resource.record.model_name.name.downcase),
|
|
161
163
|
turbo_method: :delete,
|
|
162
164
|
target: "control:destroy",
|
|
163
165
|
control: :destroy,
|
|
164
166
|
tippy: control.title ? :tooltip : nil,
|
|
165
|
-
"resource-id": @resource.record_param
|
|
167
|
+
"resource-id": @resource.record_param
|
|
166
168
|
} do
|
|
167
169
|
control.label
|
|
168
170
|
end
|
|
@@ -172,8 +174,9 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
172
174
|
return unless can_see_the_save_button?
|
|
173
175
|
|
|
174
176
|
data_attributes = {
|
|
177
|
+
hotkey: "Mod+Enter",
|
|
175
178
|
turbo_confirm: @resource.confirm_on_save ? t("avo.are_you_sure") : nil
|
|
176
|
-
}
|
|
179
|
+
}.compact
|
|
177
180
|
|
|
178
181
|
add_stimulus_attributes_for(@resource, data_attributes, "saveButton")
|
|
179
182
|
|
|
@@ -181,7 +184,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
181
184
|
style: :primary,
|
|
182
185
|
loading: true,
|
|
183
186
|
type: :submit,
|
|
184
|
-
icon: "
|
|
187
|
+
icon: "tabler/outline/device-floppy",
|
|
185
188
|
data: data_attributes do
|
|
186
189
|
control.label
|
|
187
190
|
end
|
|
@@ -194,7 +197,10 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
194
197
|
color: :accent,
|
|
195
198
|
style: :primary,
|
|
196
199
|
title: control.title,
|
|
197
|
-
data: {
|
|
200
|
+
data: {
|
|
201
|
+
hotkey: "e",
|
|
202
|
+
tippy: control.title ? :tooltip : nil
|
|
203
|
+
}.compact,
|
|
198
204
|
icon: "tabler/outline/edit" do
|
|
199
205
|
control.label
|
|
200
206
|
end
|
|
@@ -222,6 +228,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
|
|
|
222
228
|
style: :primary,
|
|
223
229
|
icon: "tabler/outline/plus",
|
|
224
230
|
data: {
|
|
231
|
+
hotkey: "c",
|
|
225
232
|
target: :create
|
|
226
233
|
} do
|
|
227
234
|
control.label
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<span class="sidebar-icon <%= 'sidebar-icon--collapsed' if collapsed %>"
|
|
15
15
|
data-menu-target="svg"
|
|
16
16
|
>
|
|
17
|
-
<%= helpers.svg '
|
|
17
|
+
<%= helpers.svg 'tabler/outline/chevron-down', class: 'h-4' %>
|
|
18
18
|
</span>
|
|
19
19
|
</button>
|
|
20
20
|
<% else %>
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<% if @path.present? %>
|
|
2
|
-
<%= link_caller.send link_method, @path, class: "sidebar-link", active: @active, target: @target, data:
|
|
2
|
+
<%= link_caller.send link_method, @path, class: "sidebar-link", active: @active, target: @target, data: link_data, disabled: @disabled, "aria-disabled": @disabled, **@args do %>
|
|
3
3
|
<% if @reserve_icon_space || link_icon.present? %>
|
|
4
|
-
<span class="sidebar-link__icon-wrapper sidebar-icon<%= '
|
|
4
|
+
<span class="sidebar-link__icon-wrapper sidebar-icon <%= 'sidebar-link__icon-wrapper--placeholder' if link_icon.blank? %>">
|
|
5
5
|
<%= helpers.svg link_icon, class: "sidebar-link__icon sidebar-icon" if link_icon.present? %>
|
|
6
6
|
</span>
|
|
7
7
|
<% end %>
|
|
8
|
+
|
|
8
9
|
<span><%= @label %></span>
|
|
10
|
+
|
|
11
|
+
<%= hotkey_badge(@hotkey, class: "ms-auto") if @hotkey.present? %>
|
|
12
|
+
|
|
9
13
|
<% if @target == :_blank %>
|
|
10
14
|
<%= helpers.svg("tabler/outline/external-link", class: "sidebar-link__external-icon sidebar-icon") %>
|
|
11
15
|
<% end %>
|
|
@@ -13,22 +17,26 @@
|
|
|
13
17
|
<% else %>
|
|
14
18
|
<%= content_tag :div, class: "sidebar-link", active: @active, target: @target, data: @data do %>
|
|
15
19
|
<% if @reserve_icon_space || link_icon.present? %>
|
|
16
|
-
<span class="sidebar-link__icon-wrapper sidebar-icon<%= '
|
|
20
|
+
<span class="sidebar-link__icon-wrapper sidebar-icon <%= 'sidebar-link__icon-wrapper--placeholder' if link_icon.blank? %>">
|
|
17
21
|
<%= helpers.svg link_icon, class: "sidebar-link__icon sidebar-icon" if link_icon.present? %>
|
|
18
22
|
</span>
|
|
19
23
|
<% end %>
|
|
24
|
+
|
|
20
25
|
<span><%= @label %></span>
|
|
21
26
|
<% end %>
|
|
22
27
|
<% end %>
|
|
28
|
+
|
|
23
29
|
<% if @items.present? && parent_link_active? %>
|
|
24
30
|
<div class="sidebar-subitem__items">
|
|
25
31
|
<% @items.each_with_index do |item, index| %>
|
|
26
32
|
<% if item.path.present? %>
|
|
27
|
-
<%= link_caller.send link_method, item.path, class: "sidebar-subitem #{subitem_bar_class(index)}", active: @active, target: item.target, data: item
|
|
33
|
+
<%= link_caller.send link_method, item.path, class: "sidebar-subitem #{subitem_bar_class(index)}", active: @active, target: item.target, data: subitem_data(item), disabled: @disabled, "aria-disabled": @disabled, **item.args do %>
|
|
28
34
|
<span><%= item.try(:label).presence || item.try(:name).presence %></span>
|
|
35
|
+
|
|
36
|
+
<%= hotkey_badge(item.hotkey, class: "ms-auto") if item.try(:hotkey).present? %>
|
|
29
37
|
<% end %>
|
|
30
38
|
<% else %>
|
|
31
|
-
<%= content_tag :div, class: "sidebar-subitem #{subitem_bar_class(index)}", active: @active, target: item.target, data: item
|
|
39
|
+
<%= content_tag :div, class: "sidebar-subitem #{subitem_bar_class(index)}", active: @active, target: item.target, data: subitem_data(item) do %>
|
|
32
40
|
<span><%= item.try(:label).presence || item.try(:name).presence %></span>
|
|
33
41
|
<% end %>
|
|
34
42
|
<% end %>
|
|
@@ -16,6 +16,15 @@ class Avo::Sidebar::LinkComponent < Avo::BaseComponent
|
|
|
16
16
|
prop :reserve_icon_space, default: false
|
|
17
17
|
prop :args, kind: :**, default: {}.freeze
|
|
18
18
|
prop :items
|
|
19
|
+
prop :hotkey, default: nil
|
|
20
|
+
|
|
21
|
+
def link_data
|
|
22
|
+
build_link_data(@data, @hotkey)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def subitem_data(item)
|
|
26
|
+
build_link_data(item.data, item.hotkey)
|
|
27
|
+
end
|
|
19
28
|
|
|
20
29
|
def is_external?
|
|
21
30
|
# If the path contains the scheme, check if it includes the root path or not
|
|
@@ -67,4 +76,12 @@ class Avo::Sidebar::LinkComponent < Avo::BaseComponent
|
|
|
67
76
|
""
|
|
68
77
|
end
|
|
69
78
|
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def build_link_data(data, hotkey)
|
|
83
|
+
return data if hotkey.blank?
|
|
84
|
+
|
|
85
|
+
data.merge(hotkey: hotkey.to_s.first)
|
|
86
|
+
end
|
|
70
87
|
end
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<span class="sidebar-section__icon sidebar-icon <%= 'sidebar-icon--collapsed' if collapsed %>"
|
|
16
16
|
data-menu-target="svg"
|
|
17
17
|
>
|
|
18
|
-
<%= helpers.svg '
|
|
18
|
+
<%= helpers.svg 'tabler/outline/chevron-down' %>
|
|
19
19
|
</span>
|
|
20
20
|
</button>
|
|
21
21
|
<% else %>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<div>
|
|
21
21
|
<div class="sidebar__nav-list">
|
|
22
22
|
<% dashboards.sort_by { |r| r.navigation_label }.each do |dashboard| %>
|
|
23
|
-
<%= render Avo::Sidebar::LinkComponent.new label: dashboard.navigation_label, path: helpers.avo_dashboards.dashboard_path(dashboard) %>
|
|
23
|
+
<%= render Avo::Sidebar::LinkComponent.new label: dashboard.navigation_label, path: helpers.avo_dashboards.dashboard_path(dashboard), hotkey: dashboard.try(:hotkey).presence %>
|
|
24
24
|
<% end %>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
<div class="sidebar__nav-list">
|
|
30
30
|
<% resources.sort_by { |r| r.navigation_label }.each do |resource| %>
|
|
31
|
-
<%= render Avo::Sidebar::LinkComponent.new label: resource.navigation_label, path: helpers.resources_path(resource: resource), icon: resource.icon %>
|
|
31
|
+
<%= render Avo::Sidebar::LinkComponent.new label: resource.navigation_label, path: helpers.resources_path(resource: resource), icon: resource.icon, hotkey: resource.try(:hotkey).presence %>
|
|
32
32
|
<% end %>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
class: 'sidebar-profile__form' do |form| %>
|
|
43
43
|
<%= form.button turbo_confirm: t('avo.are_you_sure'), class: "sidebar-profile__sign-out" do %>
|
|
44
|
-
<%= helpers.svg "
|
|
44
|
+
<%= helpers.svg "tabler/outline/logout", class: 'sidebar-profile__sign-out-icon sidebar-icon' %> <%= t('avo.sign_out') %>
|
|
45
45
|
<% end %>
|
|
46
46
|
<% end %>
|
|
47
47
|
<% end %>
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
<% if @with_shortcut %>
|
|
20
20
|
<span class="search-input__suffix" aria-hidden="true">
|
|
21
|
-
<kbd
|
|
21
|
+
<kbd>
|
|
22
22
|
<abbr title="Command" class="no-underline os-pc:hidden">⌘</abbr>
|
|
23
23
|
<abbr title="CTRL" class="no-underline os-mac:hidden">CTRL</abbr>
|
|
24
24
|
</kbd>
|
|
25
|
-
<kbd
|
|
25
|
+
<kbd>K</kbd>
|
|
26
26
|
</span>
|
|
27
27
|
<% end %>
|
|
28
28
|
<% end %>
|
|
@@ -136,7 +136,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
|
|
|
136
136
|
|
|
137
137
|
a_button size: :sm,
|
|
138
138
|
color: :primary,
|
|
139
|
-
icon: "
|
|
139
|
+
icon: "tabler/outline/filter",
|
|
140
140
|
data: {
|
|
141
141
|
controller: "avo-filters",
|
|
142
142
|
action: "click->avo-filters#toggleFiltersArea",
|
|
@@ -5,41 +5,29 @@ import 'chartkick/chart.js/chart.esm'
|
|
|
5
5
|
import 'mapkick/bundle'
|
|
6
6
|
import 'regenerator-runtime/runtime'
|
|
7
7
|
import * as ActiveStorage from '@rails/activestorage'
|
|
8
|
-
import * as Mousetrap from 'mousetrap'
|
|
9
8
|
import { Turbo } from '@hotwired/turbo-rails'
|
|
9
|
+
import { install } from '@github/hotkey'
|
|
10
10
|
import tippy from 'tippy.js'
|
|
11
11
|
|
|
12
12
|
import { LocalStorageService } from './js/local-storage-service'
|
|
13
|
+
import { installGlobalHotkeys } from './js/global_hotkeys'
|
|
13
14
|
|
|
14
15
|
import './js/active-storage'
|
|
15
16
|
import './js/controllers'
|
|
16
17
|
import './js/custom-confirm'
|
|
17
18
|
import './js/custom-stream-actions'
|
|
18
19
|
|
|
20
|
+
function installHotkeys(root = document) {
|
|
21
|
+
root.querySelectorAll('[data-hotkey]').forEach((el) => {
|
|
22
|
+
install(el)
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
window.Avo.localStorage = new LocalStorageService()
|
|
20
27
|
|
|
21
28
|
window.Turbolinks = Turbo
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
Mousetrap.bind('r r r', () => {
|
|
25
|
-
// Capture scroll position
|
|
26
|
-
scrollTop = document.scrollingElement.scrollTop
|
|
27
|
-
|
|
28
|
-
window.StreamActions.turbo_reload()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
// Add the shift-pressed class to the body when the shift key is pressed
|
|
32
|
-
document.addEventListener('keydown', (event) => {
|
|
33
|
-
if (event.shiftKey) {
|
|
34
|
-
document.body.classList.add('shift-pressed')
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
// Remove the shift-pressed class from the body when the shift key is released
|
|
38
|
-
document.addEventListener('keyup', (event) => {
|
|
39
|
-
if (!event.shiftKey) {
|
|
40
|
-
document.body.classList.remove('shift-pressed')
|
|
41
|
-
}
|
|
42
|
-
})
|
|
30
|
+
installGlobalHotkeys()
|
|
43
31
|
|
|
44
32
|
function initTippy() {
|
|
45
33
|
tippy('[data-tippy="tooltip"]', {
|
|
@@ -79,16 +67,12 @@ document.addEventListener('turbo:before-stream-render', () => {
|
|
|
79
67
|
}, 1)
|
|
80
68
|
})
|
|
81
69
|
|
|
70
|
+
document.addEventListener('turbo:frame-render', (e) => installHotkeys(e.target))
|
|
71
|
+
|
|
82
72
|
document.addEventListener('turbo:load', () => {
|
|
73
|
+
installHotkeys()
|
|
83
74
|
initTippy()
|
|
84
75
|
|
|
85
|
-
// Restore scroll position after r r r turbo reload
|
|
86
|
-
if (scrollTop) {
|
|
87
|
-
setTimeout(() => {
|
|
88
|
-
document.scrollingElement.scrollTo(0, scrollTop)
|
|
89
|
-
scrollTop = 0
|
|
90
|
-
}, 50)
|
|
91
|
-
}
|
|
92
76
|
setTimeout(() => {
|
|
93
77
|
document.body.classList.remove('turbo-loading')
|
|
94
78
|
}, 1)
|