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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43d7fc2be92ed19588de7ea2ea168ae560e8ac5c5b09fd5e13600a561ad8dc0a
4
- data.tar.gz: bb86cb4e8403f99dfbd9aa1ea2acfe596c2764430be5bc64a0951795c14a7059
3
+ metadata.gz: 26fd935a365687d3697fba9e5e06f75c3a155d6e9422a747ef7d6b766833d411
4
+ data.tar.gz: 9032b4e163d6c5134ea150eded49257f6f2961d42ec86eeb75cddb19e6d23a1a
5
5
  SHA512:
6
- metadata.gz: 6c6abafca7fd4e75772c146277e1623ad2acc4947914d6cf7158232500dc6f07480e2a8b725ac1e041b896ebfbd60fde468392b6ebfa7892eb74a593b1af2d57
7
- data.tar.gz: ad3b6c8c7d2e9844e51959f7bc2365094d17a07cdf996945fc002569a134f64382c5cd136cea1306d61f3b9697fb9fa4377a763367935f62a69e1b8a7afbe8a4
6
+ metadata.gz: bd33a7ddc5b40a802400ea2502d7d8e92f8f4089db08916d44442c3aa58cc504cab655d91f4429899669184bf152a744d825f8b414a2463efc595ceb50699d8b
7
+ data.tar.gz: b471c31518be76619303f3e0ec763df42fba446b6ec0d2a2fa02c576ad941e050ad69a69569accd91bf13e7a98930a46e4576e155a84f9e998815ac7d86a4a2f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mensa (0.6.3)
4
+ mensa (0.6.4)
5
5
  csv
6
6
  importmap-rails
7
7
  pagy (>= 43)
@@ -25,7 +25,7 @@
25
25
  }
26
26
 
27
27
  &__popover_container {
28
- @apply p-4 fixed z-50 w-72 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;
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 max-h-[20rem] overflow-y-scroll;
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-64 rounded-xl bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black ring-opacity-5 py-2;
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 %>" target="_top" data-mensa-table-target="turboFrame" data-turbo-permanent=""></turbo-frame>
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
- if (this.hasTurboFrameTarget && this.captureNavigationHandler) {
66
- this.turboFrameTarget.removeEventListener(
67
- "turbo:frame-load",
68
- this.captureNavigationHandler,
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
- document.removeEventListener("click", this._saveDropdownOutsideHandler);
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: params.permit(:query, :page, order: {}, filters: {}).to_h,
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
@@ -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
@@ -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
@@ -79,43 +79,9 @@ module Mensa
79
79
  if scope
80
80
  record_scope.instance_exec(normalize(value), &scope)
81
81
  else
82
- case operator
83
- when :is_empty
84
- if column.type == :string
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 # .to_s.gsub(/\s(?![&!|])/, '\\\\ ')
176
+ query
176
177
  end
177
178
  end
178
179
  end
@@ -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.transform_keys { |column_name| column(column_name).attribute_for_condition }
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
@@ -1,3 +1,3 @@
1
1
  module Mensa
2
- VERSION = "0.6.4"
2
+ VERSION = "0.6.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mensa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom de Grunt