keystone_ui 0.36.0 → 0.37.0

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: d7a0b5685fe1dc1107e8e75d274e80401826ed52616ae95c20d6a288fdc5c647
4
- data.tar.gz: 8220957d8d617f0c32c71b264c84c5bf8b992a3cc2e3bb512f3f4e0a1cbf8bb0
3
+ metadata.gz: f1534a3f721376c82b75b22a653e8bcee3a0509bcd3b09cd137c93c78e15110f
4
+ data.tar.gz: 0df6e2e29387563688ba68bc53d8c29dc69aa322aa86c84b6416c0e3af36f703
5
5
  SHA512:
6
- metadata.gz: 58889e18335df436f8b13caad57648caf6ed3e13e17b50dc5d6011ae0f8fa85ceb98a9e9fd682f90cd10729ed3d3f20603ed34e438afb09a33090ec4d7493c95
7
- data.tar.gz: c79c624b0a0a09e83b1f6fcee1566df17c7adf96f8141a20c8ee3ed20b361ab7faa1135be33bcf41f780e68e663e78d9ba833d27af09e72c782d4763a09463da
6
+ metadata.gz: 1869494c71af590cd1fa49fcd1062b024df02950f571e5dcbd7deaaa508574ad0f0aa2bfc8e23b0dc10d35ca440a50bc12e5d18063c62888de9eb29c42c09b45
7
+ data.tar.gz: 45563016cb89280c2e97cca008236c9fc50ba4681486a53f0d82fc4066e7db68368ad412c43b512cb2ca4ccd4f359dd29339890e24854eae2ead04ed9db7f6db
@@ -1,10 +1,11 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ["menu", "option"]
4
+ static targets = ["menu", "option", "error"]
5
5
  static values = { saveUrl: String }
6
6
 
7
7
  connect() {
8
+ this.shown = this.optionTargets.map(option => [ option, this.checkboxIn(option).checked ])
8
9
  this._close = this.close.bind(this)
9
10
  document.addEventListener("click", this._close)
10
11
  }
@@ -66,6 +67,10 @@ export default class extends Controller {
66
67
 
67
68
  moved() {
68
69
  this.changed = true
70
+ this.refreshMoveButtons()
71
+ }
72
+
73
+ refreshMoveButtons() {
69
74
  const options = this.optionTargets
70
75
  options.forEach((option, index) => {
71
76
  option.querySelector('[data-action="click->column-picker#moveUp"]').disabled = index === 0
@@ -99,8 +104,20 @@ export default class extends Controller {
99
104
  "X-CSRF-Token": token
100
105
  },
101
106
  body: JSON.stringify({ hidden_columns: this.hiddenColumns(), column_order: this.columnOrder() })
102
- }).then(() => {
107
+ }).then((response) => {
108
+ if (!response.ok) return this.failed()
109
+
103
110
  Turbo.visit(window.location.href, { action: "replace" })
111
+ }).catch(() => this.failed())
112
+ }
113
+
114
+ failed() {
115
+ this.errorTarget.classList.remove("hidden")
116
+ this.shown.forEach(([ option, checked ]) => {
117
+ option.parentNode.insertBefore(option, null)
118
+ this.checkboxIn(option).checked = checked
119
+ option.querySelector("label").classList.toggle("ks-menu-option-hidden", !checked)
104
120
  })
121
+ this.refreshMoveButtons()
105
122
  }
106
123
  }
@@ -29,4 +29,5 @@
29
29
  </div>
30
30
  <% end %>
31
31
  </div>
32
+ <p data-column-picker-target="error" role="alert" class="<%= ERROR_CLASSES %>">Your column changes were not saved.</p>
32
33
  </div>
@@ -11,6 +11,7 @@ module Keystone
11
11
  OPTION_HIDDEN_CLASSES = "ks-menu-option-hidden"
12
12
  OPTION_ROW_CLASSES = "flex items-center"
13
13
  MOVE_BUTTON_CLASSES = "ks-menu-move text-sm"
14
+ ERROR_CLASSES = "ks-error hidden"
14
15
 
15
16
  COLUMNS_ICON = <<~SVG.freeze
16
17
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
@@ -20,18 +21,17 @@ module Keystone
20
21
 
21
22
  attr_reader :save_url
22
23
 
23
- def initialize(columns:, hidden_columns: [], save_url: nil)
24
- @columns = columns
25
- @hidden_keys = Array(hidden_columns).map(&:to_sym).to_set
24
+ def initialize(columns:, hidden_columns: [], save_url: nil, layout: nil)
25
+ @layout = layout || SavedLayout.new(columns: columns, value: nil, default_hidden: hidden_columns)
26
26
  @save_url = save_url
27
27
  end
28
28
 
29
29
  def hideable_columns
30
- @columns.select(&:hideable?)
30
+ @layout.columns.select(&:hideable?)
31
31
  end
32
32
 
33
33
  def hidden?(key)
34
- @hidden_keys.include?(key.to_sym)
34
+ @layout.hidden?(key)
35
35
  end
36
36
 
37
37
  def option_classes(key)
@@ -32,8 +32,9 @@ module Keystone
32
32
  def initialize(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [], key: nil)
33
33
  @items = items.to_a
34
34
  @all_columns = columns.map { |col| normalize_column(col) }
35
- @hidden_columns = hidden_columns
36
- @columns = visible_columns(hidden_columns)
35
+ @default_hidden = hidden_columns
36
+ @layout = SavedLayout.new(columns: @all_columns, value: nil, default_hidden: hidden_columns)
37
+ @columns = @layout.visible_columns
37
38
  @key = key
38
39
  @empty_message = empty_message
39
40
  @sort = sort&.to_sym
@@ -63,7 +64,7 @@ module Keystone
63
64
  def column_picker
64
65
  return unless @save_url
65
66
 
66
- @column_picker ||= ColumnPickerComponent.new(columns: @all_columns, hidden_columns: @hidden_columns, save_url: @save_url)
67
+ @column_picker ||= ColumnPickerComponent.new(columns: @all_columns, layout: @layout, save_url: @save_url)
67
68
  end
68
69
 
69
70
  def column_keys
@@ -151,25 +152,11 @@ module Keystone
151
152
  saved = KeystoneUi.configuration.supplied_preference(helpers, @key)
152
153
  return unless saved
153
154
 
154
- @hidden_columns = saved[:value].to_h.fetch("hidden_columns", @hidden_columns)
155
- @all_columns = ordered_columns(saved[:value].to_h["column_order"])
156
- @columns = visible_columns(@hidden_columns)
155
+ @layout = SavedLayout.new(columns: @all_columns, value: saved[:value], default_hidden: @default_hidden)
156
+ @columns = @layout.visible_columns
157
157
  @save_url = saved[:save_url]
158
158
  end
159
159
 
160
- def ordered_columns(column_order)
161
- return @all_columns unless column_order
162
-
163
- positions = Array(column_order).map(&:to_sym).each_with_index.to_h
164
- hideable = @all_columns.select(&:hideable?).sort_by.with_index { |col, index| [ positions.fetch(col.key, positions.size + index) ] }
165
- @all_columns.map { |col| col.hideable? ? hideable.shift : col }
166
- end
167
-
168
- def visible_columns(hidden_columns)
169
- hidden_keys = Array(hidden_columns).map(&:to_sym).to_set
170
- @all_columns.reject { |col| col.hideable? && hidden_keys.include?(col.key) }
171
- end
172
-
173
160
  def visual_column_count
174
161
  @columns.length + (actions? ? 1 : 0)
175
162
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keystone
4
+ module Ui
5
+ class SavedLayout
6
+ def initialize(columns:, value:, default_hidden:)
7
+ @columns = columns
8
+ @value = value.is_a?(Hash) ? value : {}
9
+ @default_hidden = default_hidden
10
+ end
11
+
12
+ def columns
13
+ @ordered_columns ||= ordered_columns
14
+ end
15
+
16
+ def visible_columns
17
+ columns.reject { |column| hidden?(column.key) }
18
+ end
19
+
20
+ def hidden?(key)
21
+ column = @columns.find { |col| col.key == key.to_sym }
22
+ column&.hideable? && hidden_keys.include?(key.to_sym)
23
+ end
24
+
25
+ private
26
+
27
+ def ordered_columns
28
+ column_order = saved_list("column_order")
29
+ return @columns unless column_order
30
+
31
+ positions = Array(column_order).map(&:to_sym).each_with_index.to_h
32
+ hideable = @columns.select(&:hideable?).sort_by.with_index { |col, index| positions.fetch(col.key, positions.size + index) }
33
+ @columns.map { |col| col.hideable? ? hideable.shift : col }
34
+ end
35
+
36
+ def hidden_keys
37
+ @hidden_keys ||= Array(saved_list("hidden_columns") || @default_hidden).map(&:to_sym).to_set
38
+ end
39
+
40
+ def saved_list(name)
41
+ list = @value[name]
42
+ list if list.is_a?(Array)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeystoneUi
4
- VERSION = "0.36.0"
4
+ VERSION = "0.37.0"
5
5
  end
@@ -240,8 +240,11 @@ outer element. See Conventions before using it.
240
240
  and the hideable ones fill the remaining places. With no `"column_order"` the
241
241
  columns keep their declared order. Whenever the supplier
242
242
  returns a save address, the table renders a "Columns" menu in a row above
243
- itself, aligned right, that saves to it as `ui_column_picker` does, including
244
- for a person with nothing saved yet. When the
243
+ itself, aligned right, that saves to it as `ui_column_picker` does and shows
244
+ the same message when a save fails, including for a person with nothing saved
245
+ yet. That menu lists the hideable columns in
246
+ the order the table shows them and leaves ticked exactly the ones the table
247
+ shows. When the
245
248
  supplier returns nothing, when no supplier is set, or when no `key:` is
246
249
  passed, the table renders from `hidden_columns:` with no Columns menu. In
247
250
  every case `hidden_columns:` is the table's default layout.
@@ -269,7 +272,11 @@ outer element. See Conventions before using it.
269
272
  `X-CSRF-Token` header, then reloads the page. A menu closed with nothing
270
273
  changed sends nothing. `column_order` lists every hideable column's key in
271
274
  the menu's order when it closes. With no `save_url:` it sends nothing. The app must provide that endpoint and persist
272
- both lists. The picker does not reorder the table: beside a table without
275
+ both lists. The endpoint must answer with a success status when it has saved
276
+ them. When it answers with an error status, or the request cannot reach the
277
+ server, the menu does not reload the page: it shows "Your column changes were
278
+ not saved." under the Columns button, and puts its boxes and its order back
279
+ to what the table shows. The picker does not reorder the table: beside a table without
273
280
  `key:`, the app must pass the table and the picker its columns in the saved
274
281
  order itself. A table
275
282
  given `key:` renders its own Columns menu when the supplier gives a save
@@ -150,11 +150,15 @@ holds the catalog.
150
150
  table, at its right, and saves the user's choice there, even before anything
151
151
  has been saved. The menu lists the hideable columns in the order the table
152
152
  shows them, each with a box to show or hide it and buttons to move it up or
153
- down. An unticked column's name is greyed at once. Ticking boxes and moving
153
+ down. The menu and the table read the same saved layout, so the menu's
154
+ ticked boxes and order always match the columns the table shows. An unticked column's name is greyed at once. Ticking boxes and moving
154
155
  columns sends nothing while the menu is open. Closing the menu, with its
155
156
  Columns button or by clicking anywhere outside it, saves the hidden columns
156
157
  and the order together once, then reloads the page. Closing it with nothing
157
- changed saves nothing.
158
+ changed saves nothing. When the save is refused or cannot reach the server,
159
+ the page does not reload. The Columns menu shows the message "Your
160
+ column changes were not saved." and puts its boxes and order back to what
161
+ the table shows.
158
162
  A table with no key, or an app with no supplier, renders from its own call
159
163
  alone. Setting up the supplier belongs to the install local.
160
164
  - **Suggestions.** A form field can carry a list of suggested values. The
@@ -282,6 +282,11 @@ built on ViewComponent; hook it in before building any screen with those helpers
282
282
  string keys, `"hidden_columns"` and `"column_order"`, or the saved order
283
283
  is not applied. With no `save_url`, the saved layout applies and no menu
284
284
  is shown.
285
+ - The action at `save_url` must answer a stored layout with a 2xx status.
286
+ Any other status, or a request that cannot reach the server, leaves the
287
+ page unreloaded. The table then shows "Your column changes were not
288
+ saved." beside its Columns button, and the menu's boxes and order go back
289
+ to what the table shows.
285
290
  - A companion preferences gem may set this supplier for the app. Ask the
286
291
  developer whether the app uses one, or which code stores each user's table
287
292
  layouts, before writing the callable.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keystone_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.36.0
4
+ version: 0.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider
@@ -175,6 +175,7 @@ files:
175
175
  - app/components/keystone/ui/progress_component.rb
176
176
  - app/components/keystone/ui/radio_card_component.html.erb
177
177
  - app/components/keystone/ui/radio_card_component.rb
178
+ - app/components/keystone/ui/saved_layout.rb
178
179
  - app/components/keystone/ui/section_component.html.erb
179
180
  - app/components/keystone/ui/section_component.rb
180
181
  - app/components/keystone/ui/select_component.html.erb