mensa 0.6.4 → 0.6.5
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/components/mensa/add_filter/component.css +2 -2
- data/app/components/mensa/add_filter/component.html.erb +1 -1
- data/app/components/mensa/column_customizer/component.css +1 -1
- data/app/components/mensa/table/component.html.erb +6 -1
- data/app/components/mensa/table/component_controller.js +79 -28
- data/app/components/mensa/view/component.html.erb +1 -0
- data/app/controllers/mensa/tables/exports_controller.rb +13 -1
- data/app/tables/mensa/base.rb +6 -1
- data/app/tables/mensa/column.rb +1 -1
- data/app/tables/mensa/filter.rb +39 -38
- data/app/tables/mensa/scope.rb +1 -1
- data/lib/mensa/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 26fd935a365687d3697fba9e5e06f75c3a155d6e9422a747ef7d6b766833d411
|
|
4
|
+
data.tar.gz: 9032b4e163d6c5134ea150eded49257f6f2961d42ec86eeb75cddb19e6d23a1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd33a7ddc5b40a802400ea2502d7d8e92f8f4089db08916d44442c3aa58cc504cab655d91f4429899669184bf152a744d825f8b414a2463efc595ceb50699d8b
|
|
7
|
+
data.tar.gz: b471c31518be76619303f3e0ec763df42fba446b6ec0d2a2fa02c576ad941e050ad69a69569accd91bf13e7a98930a46e4576e155a84f9e998815ac7d86a4a2f
|
data/Gemfile.lock
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
&__popover_container {
|
|
28
|
-
@apply p-4 fixed z-50 w-
|
|
28
|
+
@apply p-4 fixed z-50 w-96 max-h-96 overflow-y-auto rounded-lg bg-white dark:bg-gray-800 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm;
|
|
29
29
|
|
|
30
30
|
&__heading {
|
|
31
31
|
@apply font-semibold text-gray-900 dark:text-gray-100 mb-2;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
&__values {
|
|
43
|
-
@apply mb-2
|
|
43
|
+
@apply mb-2;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
&__value {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<i class="<%= Mensa.config.icons[:add_filter_trigger] %>"></i>
|
|
10
10
|
</button>
|
|
11
11
|
|
|
12
|
-
<ul class="hidden fixed z-50 max-h-96 overflow-auto rounded-lg bg-white p-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-800 sm:text-sm"
|
|
12
|
+
<ul class="hidden fixed z-50 w-96 max-h-96 overflow-auto rounded-lg bg-white p-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-800 sm:text-sm"
|
|
13
13
|
data-mensa-add-filter-target="filterList">
|
|
14
14
|
<% table.columns.select(&:filter?).each do |column| %>
|
|
15
15
|
<li class="text-gray-900 hover:bg-gray-100 relative flex cursor-default select-none items-center gap-2 rounded-md px-3 py-2 dark:text-gray-300 dark:hover:bg-gray-700"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.mensa-table {
|
|
2
2
|
&__column_customizer {
|
|
3
3
|
&__popover {
|
|
4
|
-
@apply fixed z-50 w-
|
|
4
|
+
@apply fixed z-50 w-96 max-h-96 rounded-xl bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black ring-opacity-5 py-2;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
&__heading {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
data-mensa-table-views-url-value="<%= helpers.mensa.table_views_path(table.name) %>"
|
|
7
7
|
data-mensa-table-exports-url-value="<%= helpers.mensa.table_exports_path(table.name) %>"
|
|
8
8
|
data-controller="mensa-table"
|
|
9
|
+
data-action="click@document->mensa-table#saveDropdownOutsideHandler"
|
|
9
10
|
data-mensa-table-mensa-filter-pill-outlet="[data-controller='mensa-filter-pill']"
|
|
10
11
|
data-mensa-table-mensa-filter-pill-list-outlet="#mensa-filter-pill-list-<%= table.table_id %>"
|
|
11
12
|
data-mensa-table-mensa-column-customizer-outlet="[data-controller='mensa-column-customizer']">
|
|
@@ -25,5 +26,9 @@
|
|
|
25
26
|
<%= render Mensa::ControlBar::Component.new(table: table) %>
|
|
26
27
|
</div>
|
|
27
28
|
<%= render Mensa::Search::Component.new(table: table) %>
|
|
28
|
-
<turbo-frame id="<%= table.table_id %>"
|
|
29
|
+
<turbo-frame id="<%= table.table_id %>"
|
|
30
|
+
target="_top"
|
|
31
|
+
data-mensa-table-target="turboFrame"
|
|
32
|
+
data-action="turbo:frame-load->mensa-table#captureNavigation turbo:before-frame-render->mensa-table#beforeFrameRender turbo:before-fetch-request->mensa-table#captureScrollPosition click->mensa-table#captureScrollPosition:capture turbo:frame-load->mensa-table#restoreScrollPosition"
|
|
33
|
+
data-turbo-permanent></turbo-frame>
|
|
29
34
|
</div>
|
|
@@ -7,6 +7,7 @@ export default class TableComponentController extends ApplicationController {
|
|
|
7
7
|
"filterList",
|
|
8
8
|
"views",
|
|
9
9
|
"view",
|
|
10
|
+
"scrollContainer",
|
|
10
11
|
"turboFrame",
|
|
11
12
|
"saveViewDialog",
|
|
12
13
|
"saveViewName",
|
|
@@ -32,28 +33,7 @@ export default class TableComponentController extends ApplicationController {
|
|
|
32
33
|
super.connect();
|
|
33
34
|
|
|
34
35
|
this.frameLoadFallback = setTimeout(() => this.loadFrame(), 100);
|
|
35
|
-
|
|
36
|
-
if (this.hasTurboFrameTarget) {
|
|
37
|
-
this.captureNavigationHandler = () => this.captureNavigation();
|
|
38
|
-
this.turboFrameTarget.addEventListener(
|
|
39
|
-
"turbo:frame-load",
|
|
40
|
-
this.captureNavigationHandler,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Close save dropdown when clicking outside
|
|
45
|
-
this._saveDropdownOutsideHandler = (e) => {
|
|
46
|
-
if (
|
|
47
|
-
this.hasSaveDropdownTarget &&
|
|
48
|
-
!this.saveDropdownTarget.classList.contains("hidden")
|
|
49
|
-
) {
|
|
50
|
-
const saveArea = this.saveDropdownTarget.closest(".relative");
|
|
51
|
-
if (saveArea && !saveArea.contains(e.target)) {
|
|
52
|
-
this.saveDropdownTarget.classList.add("hidden");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
document.addEventListener("click", this._saveDropdownOutsideHandler);
|
|
36
|
+
this.scrollLeft = 0;
|
|
57
37
|
}
|
|
58
38
|
|
|
59
39
|
disconnect() {
|
|
@@ -62,14 +42,20 @@ export default class TableComponentController extends ApplicationController {
|
|
|
62
42
|
this.frameLoadFallback = null;
|
|
63
43
|
}
|
|
64
44
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
saveDropdownOutsideHandler(event) {
|
|
48
|
+
if (
|
|
49
|
+
!this.hasSaveDropdownTarget ||
|
|
50
|
+
this.saveDropdownTarget.classList.contains("hidden")
|
|
51
|
+
) {
|
|
52
|
+
return;
|
|
70
53
|
}
|
|
71
54
|
|
|
72
|
-
|
|
55
|
+
const saveArea = this.saveDropdownTarget.closest(".relative");
|
|
56
|
+
if (saveArea && !saveArea.contains(event.target)) {
|
|
57
|
+
this.saveDropdownTarget.classList.add("hidden");
|
|
58
|
+
}
|
|
73
59
|
}
|
|
74
60
|
|
|
75
61
|
captureNavigation() {
|
|
@@ -80,6 +66,69 @@ export default class TableComponentController extends ApplicationController {
|
|
|
80
66
|
this.mensaFilterPillListOutlet.captureNavigation(src);
|
|
81
67
|
}
|
|
82
68
|
|
|
69
|
+
beforeFrameRender() {
|
|
70
|
+
this.captureScrollPosition();
|
|
71
|
+
this.isReplacingFrame = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
scrollContainerTargetConnected(element) {
|
|
75
|
+
this.scrollContainer = this.scrollableElementFor(element);
|
|
76
|
+
this.restoreScrollPosition();
|
|
77
|
+
this.scrollHandler = () => this.captureScrollPosition();
|
|
78
|
+
this.scrollContainer.addEventListener("scroll", this.scrollHandler);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
scrollContainerTargetDisconnected(element) {
|
|
82
|
+
const scrollContainer =
|
|
83
|
+
this.scrollContainer || this.scrollableElementFor(element);
|
|
84
|
+
if (this.scrollHandler && scrollContainer) {
|
|
85
|
+
scrollContainer.removeEventListener("scroll", this.scrollHandler);
|
|
86
|
+
}
|
|
87
|
+
this.scrollContainer = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
captureScrollPosition(source = null) {
|
|
91
|
+
if (this.isReplacingFrame || this.isRestoringScroll) return;
|
|
92
|
+
const scrollContainer = this.scrollContainerFrom(source);
|
|
93
|
+
if (!scrollContainer) return;
|
|
94
|
+
this.scrollLeft = scrollContainer.scrollLeft;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
restoreScrollPosition() {
|
|
98
|
+
const scrollContainer = this.scrollContainerFrom();
|
|
99
|
+
if (!scrollContainer) return;
|
|
100
|
+
|
|
101
|
+
this.isRestoringScroll = true;
|
|
102
|
+
scrollContainer.scrollLeft = this.scrollLeft || 0;
|
|
103
|
+
requestAnimationFrame(() => {
|
|
104
|
+
this.isRestoringScroll = false;
|
|
105
|
+
this.isReplacingFrame = false;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
scrollContainerFrom(source = null) {
|
|
110
|
+
if (source?.currentTarget) {
|
|
111
|
+
return this.scrollableElementFor(source.currentTarget);
|
|
112
|
+
}
|
|
113
|
+
if (source?.scrollLeft !== undefined) return source;
|
|
114
|
+
if (this.scrollContainer) return this.scrollContainer;
|
|
115
|
+
if (this.hasScrollContainerTarget) {
|
|
116
|
+
return this.scrollableElementFor(this.scrollContainerTarget);
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
scrollableElementFor(element) {
|
|
122
|
+
if (!element) return null;
|
|
123
|
+
if (element.scrollWidth > element.clientWidth) return element;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
Array.from(element.querySelectorAll("*")).find(
|
|
127
|
+
(candidate) => candidate.scrollWidth > candidate.clientWidth,
|
|
128
|
+
) || element
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
83
132
|
loadFrame() {
|
|
84
133
|
if (this.frameLoadHandled) return;
|
|
85
134
|
this.frameLoadHandled = true;
|
|
@@ -520,6 +569,8 @@ export default class TableComponentController extends ApplicationController {
|
|
|
520
569
|
query: state.query || nav.query,
|
|
521
570
|
filters: state.filters,
|
|
522
571
|
order: state.order,
|
|
572
|
+
column_order: state.column_order,
|
|
573
|
+
hidden_columns: state.hidden_columns,
|
|
523
574
|
}),
|
|
524
575
|
contentType: "application/json",
|
|
525
576
|
responseKind: "turbo-stream",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<div data-mensa-table-target="view">
|
|
2
2
|
<div class="overflow-y-auto relative"
|
|
3
|
+
data-mensa-table-target="scrollContainer"
|
|
3
4
|
data-controller="mensa-selection"
|
|
4
5
|
data-mensa-selection-batch-url-value="<%= table.batch_actions? ? helpers.mensa.table_batch_actions_path(table.name) : "" %>">
|
|
5
6
|
<% if table.batch_actions? %>
|
|
@@ -24,7 +24,7 @@ module Mensa
|
|
|
24
24
|
format: params[:export_format].to_s.presence_in(Mensa::Export::FORMATS) || "csv_excel",
|
|
25
25
|
scope: params[:scope].to_s.presence_in(Mensa::Export::SCOPES) || "all",
|
|
26
26
|
repeat: params[:repeat].to_s.presence_in(Mensa::Export::REPEATS) || "",
|
|
27
|
-
config:
|
|
27
|
+
config: export_config,
|
|
28
28
|
status: "pending"
|
|
29
29
|
)
|
|
30
30
|
|
|
@@ -108,6 +108,18 @@ module Mensa
|
|
|
108
108
|
{table_name: params[:table_id], user: current_mensa_user, exports: exports}
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
def export_config
|
|
112
|
+
params.permit(
|
|
113
|
+
:query,
|
|
114
|
+
:page,
|
|
115
|
+
:table_view_id,
|
|
116
|
+
order: {},
|
|
117
|
+
filters: {},
|
|
118
|
+
column_order: [],
|
|
119
|
+
hidden_columns: []
|
|
120
|
+
).to_h
|
|
121
|
+
end
|
|
122
|
+
|
|
111
123
|
def current_mensa_user
|
|
112
124
|
Current.user if defined?(Current) && Current.respond_to?(:user)
|
|
113
125
|
end
|
data/app/tables/mensa/base.rb
CHANGED
|
@@ -120,7 +120,7 @@ module Mensa
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# Returns the current path with configuration
|
|
123
|
-
def path(order: {}, turbo_frame_id: current_turbo_frame_id, table_view_id: current_table_view_id, column_order: current_column_order, hidden_columns: current_hidden_columns, user_params: nil)
|
|
123
|
+
def path(order: {}, turbo_frame_id: current_turbo_frame_id, table_view_id: current_table_view_id, page: current_page, column_order: current_column_order, hidden_columns: current_hidden_columns, user_params: nil)
|
|
124
124
|
# FIXME: if someone doesn't use as: :mensa in the routes, it breaks
|
|
125
125
|
path = original_view_context.mensa.table_path(name)
|
|
126
126
|
query = {
|
|
@@ -128,6 +128,7 @@ module Mensa
|
|
|
128
128
|
order: order_hash(order),
|
|
129
129
|
turbo_frame_id: turbo_frame_id,
|
|
130
130
|
table_view_id: table_view_id,
|
|
131
|
+
page: page,
|
|
131
132
|
column_order: column_order,
|
|
132
133
|
hidden_columns: hidden_columns
|
|
133
134
|
}.compact.to_query
|
|
@@ -179,6 +180,10 @@ module Mensa
|
|
|
179
180
|
config[:table_view_id]
|
|
180
181
|
end
|
|
181
182
|
|
|
183
|
+
def current_page
|
|
184
|
+
config[:page]
|
|
185
|
+
end
|
|
186
|
+
|
|
182
187
|
def current_column_order
|
|
183
188
|
config[:column_order]
|
|
184
189
|
end
|
data/app/tables/mensa/column.rb
CHANGED
|
@@ -72,7 +72,7 @@ module Mensa
|
|
|
72
72
|
return @attribute_for_condition if @attribute_for_condition
|
|
73
73
|
|
|
74
74
|
@attribute_for_condition = if config[:attribute].present?
|
|
75
|
-
raw_attribute
|
|
75
|
+
Arel.sql(raw_attribute)
|
|
76
76
|
elsif table.model.column_names.include? name.to_s
|
|
77
77
|
Arel.sql("\"#{table.model.table_name}\".\"#{name}\"")
|
|
78
78
|
end
|
data/app/tables/mensa/filter.rb
CHANGED
|
@@ -79,43 +79,9 @@ module Mensa
|
|
|
79
79
|
if scope
|
|
80
80
|
record_scope.instance_exec(normalize(value), &scope)
|
|
81
81
|
else
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
record_scope.where("#{column.attribute_for_condition} IS NULL OR #{column.attribute_for_condition} = ''")
|
|
86
|
-
else
|
|
87
|
-
record_scope.where("#{column.attribute_for_condition} IS NULL")
|
|
88
|
-
end
|
|
89
|
-
when :isnt_empty
|
|
90
|
-
if column.type == :string
|
|
91
|
-
record_scope.where("#{column.attribute_for_condition} IS NOT NULL AND #{column.attribute_for_condition} != ''")
|
|
92
|
-
else
|
|
93
|
-
record_scope.where("#{column.attribute_for_condition} IS NOT NULL")
|
|
94
|
-
end
|
|
95
|
-
when :is_current
|
|
96
|
-
record_scope.where("#{column.attribute_for_condition} = ?", Current.send(column.name))
|
|
97
|
-
when :matches
|
|
98
|
-
record_scope.where("#{column.attribute_for_condition} LIKE ?", "%#{normalize(value)}%")
|
|
99
|
-
when :does_not_match
|
|
100
|
-
record_scope.where("#{column.attribute_for_condition} NOT LIKE ?", "%#{normalize(value)}%")
|
|
101
|
-
when :is
|
|
102
|
-
val = value.is_a?(Array) ? value : normalize(value)
|
|
103
|
-
record_scope.where(column.attribute_for_condition => val)
|
|
104
|
-
when :isnt
|
|
105
|
-
val = value.is_a?(Array) ? value : normalize(value)
|
|
106
|
-
record_scope.where.not(column.attribute_for_condition => val)
|
|
107
|
-
when :gt
|
|
108
|
-
record_scope.where(":column > :value", column: column.attribute_for_condition, value: normalize(value))
|
|
109
|
-
when :lt
|
|
110
|
-
record_scope.where(":column < :value", column: column.attribute_for_condition, value: normalize(value))
|
|
111
|
-
when :gteq
|
|
112
|
-
record_scope.where(":column >= :value", column: column.attribute_for_condition, value: normalize(value))
|
|
113
|
-
when :lteq
|
|
114
|
-
record_scope.where(":column <= :value", column: column.attribute_for_condition, value: normalize(value))
|
|
115
|
-
else
|
|
116
|
-
# Ignore unknown operators
|
|
117
|
-
record_scope
|
|
118
|
-
end
|
|
82
|
+
query, hash = query_and_hash_for_operator
|
|
83
|
+
record_scope = record_scope.where(query, hash) if query.present?
|
|
84
|
+
record_scope
|
|
119
85
|
end
|
|
120
86
|
end
|
|
121
87
|
|
|
@@ -133,6 +99,41 @@ module Mensa
|
|
|
133
99
|
operators
|
|
134
100
|
end
|
|
135
101
|
|
|
102
|
+
def query_and_hash_for_operator
|
|
103
|
+
hash = { column: column.attribute_for_condition, value: normalize(value) }
|
|
104
|
+
|
|
105
|
+
query = case operator
|
|
106
|
+
when :is_empty
|
|
107
|
+
column.type == :string ? ":column IS NULL OR :column = ''" : ":column IS NULL"
|
|
108
|
+
when :isnt_empty
|
|
109
|
+
column.type == :string ? ":column IS NOT NULL AND :column != ''" : ":column IS NOT NULL"
|
|
110
|
+
when :is_current
|
|
111
|
+
":column = :value"
|
|
112
|
+
when :matches
|
|
113
|
+
":column LIKE :value"
|
|
114
|
+
when :does_not_match
|
|
115
|
+
":column NOT LIKE :value"
|
|
116
|
+
when :is
|
|
117
|
+
hash[:value] = value if hash[:value].is_a?(Array)
|
|
118
|
+
":column = :value"
|
|
119
|
+
when :isnt
|
|
120
|
+
hash[:value] = value if hash[:value].is_a?(Array)
|
|
121
|
+
":column != :value"
|
|
122
|
+
when :gt
|
|
123
|
+
":column > :value"
|
|
124
|
+
when :lt
|
|
125
|
+
":column < :value"
|
|
126
|
+
when :gteq
|
|
127
|
+
":column >= :value"
|
|
128
|
+
when :lteq
|
|
129
|
+
":column <= :value"
|
|
130
|
+
else
|
|
131
|
+
# Ignore unknown operators
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
[query, hash]
|
|
135
|
+
end
|
|
136
|
+
|
|
136
137
|
def operator_label
|
|
137
138
|
Mensa::Filter.OPERATORS.find { |op| op[0] == operator }[1]
|
|
138
139
|
end
|
|
@@ -172,7 +173,7 @@ module Mensa
|
|
|
172
173
|
|
|
173
174
|
# Unused at the moment
|
|
174
175
|
def normalize(query)
|
|
175
|
-
query
|
|
176
|
+
query
|
|
176
177
|
end
|
|
177
178
|
end
|
|
178
179
|
end
|
data/app/tables/mensa/scope.rb
CHANGED
|
@@ -76,7 +76,7 @@ module Mensa
|
|
|
76
76
|
def effective_order
|
|
77
77
|
result = current_order_provided? ? (current_order || {}) : (config[:order] || {})
|
|
78
78
|
result = result.symbolize_keys.compact_blank.transform_values(&:to_sym)
|
|
79
|
-
result.
|
|
79
|
+
result.map { |k,v| "#{k} #{v} NULLS LAST" }.join(", ")
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# Builds an order hash for URL generation. Merges current order with overrides;
|
data/lib/mensa/version.rb
CHANGED