keystone_ui-preferences 0.4.1 → 0.6.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: c77e689992104b9ce8801267235141abba7ff559cd1aace5578115b757e3347a
4
- data.tar.gz: 759ed45c55451ea970607bad67065807c739e6b625dc15c83497d0166f3bc5cd
3
+ metadata.gz: 4f636363927260121283af376ea874a464189878bc0a338840fe5c3a228b13dd
4
+ data.tar.gz: c6e10c6b5e2b4442e74aa0448f7cbd68c23e292b5c21dfd7455291847c12e8c1
5
5
  SHA512:
6
- metadata.gz: dae10a0b401a87cbacc883c0f73c5278c61a7ea867130eaa3f519a10384e3231fc1435569d363c7fc009b6b4647c497e2a9e717083eecd0fdc49a5d9f31bc898
7
- data.tar.gz: 0311250c39244319a91819c87af47e98fe1c4d7a4c896001f68374fa2da5a7c321eadc8042e11cf9ba9aa913963516221debaeceb4edeb79948a8c9904fdbdfe
6
+ metadata.gz: c1ac4cd559b34527ff83fa954a793b8b9433d2c8caf7a76bfb581b8417ad6f6e9bc1c69c1e91b3aaece6466b5529b8124d6466e050e67022b7aef1ecc97a7ffd
7
+ data.tar.gz: f8d94f6d19ce5788906d4eb8ab48c827bc38f631e5e1f3f2b7325931c2d83671efd6d03e6b610ddcb1dbd641599667a3da11050bc9350f32a4fcaaf70a38654c
data/README.md CHANGED
@@ -101,6 +101,38 @@ end
101
101
 
102
102
  Each key has the members-choose switch and, when the admin has a saved layout of their own, a button that makes it the account's layout.
103
103
 
104
+ ## Navigation placement
105
+
106
+ A person chooses where keystone_ui draws the desktop navigation, at the top or as a sidebar on the left or the right, in a settings_hub section the app registers:
107
+
108
+ ```ruby
109
+ SettingsHub.section :navigation_placement, area: :user, title: "Navigation",
110
+ renders: "keystone_ui/preferences/settings/navigation_placement",
111
+ runs: "KeystoneUi::Preferences::PickNavigationPlacement"
112
+ ```
113
+
114
+ The section offers Top, Left and Right, with the placement that applies to the person selected, or Top when nothing is saved. Saving keeps `"placement": "left"` for that person under the `navigation` key, leaving any saved tab order as it was, and keystone_ui's navigation reads it on the next page they open. A placement other than `top`, `left` or `right` is refused with a message, and nothing is saved.
115
+
116
+ The `navigation` key follows the account layouts above, so an account that turns off members-choose for it draws its own placement and tab order for every member.
117
+
118
+ While it is off, the placement and tab order sections say the account sets the navigation for everyone and offer no save. A placement, tab order or reset sent anyway is refused with a message, and nothing is saved.
119
+
120
+ ## Tab order
121
+
122
+ A person sets the order of the navigation's groups, and of the tabs in each group, in a settings_hub section the app registers:
123
+
124
+ ```ruby
125
+ SettingsHub.section :navigation_order, area: :user, title: "Tab order",
126
+ renders: "keystone_ui/preferences/settings/navigation_order",
127
+ runs: "KeystoneUi::Preferences::PickNavigationOrder"
128
+ ```
129
+
130
+ The section lists the groups declared with keystone_ui's `navigation_group`, each with the tabs the person may see, in the order that applies to them. A group with no tab they may see is left out. Each group and tab has an Up and a Down button, and pressing one saves the order with it moved one place, so the page works in a Hotwire Native web view with no script and nothing to drag. At phone width each row's buttons sit under its name.
131
+
132
+ A tab declared outside any group with keystone_ui's `navigation_tab` is one row in the list of groups, named by its label, with the same Up and Down buttons as a group. It has no tab rows under it, and no button moves it into a group.
133
+
134
+ Saving keeps `"order": [{ "group": "Admin" }, { "group": "Sales", "tabs": ["orders", "quotes"] }, { "group": "Console" }]` for that person under the `navigation` key, naming groups by label and tabs by key, and a tab declared outside any group by its label alone. It leaves their saved placement as it was. keystone_ui draws the navigation in that order on the next page they open, with anything the order does not name after it in its declared order. Reset removes the saved order, so the navigation goes back to the declared order. An order that is not a list of groups is refused with a message, and nothing is saved.
135
+
104
136
  ## What is stored
105
137
 
106
138
  One row per owner and component key, in `keystone_ui_preferences_component_preferences`:
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ module Preferences
5
+ class NavigationArrangement
6
+ def initialize(arranged)
7
+ @arranged = arranged
8
+ end
9
+
10
+ def group_moved(index, step)
11
+ moved(order, index, step)
12
+ end
13
+
14
+ def tab_moved(group_index, tab_index, step)
15
+ order.tap { |groups| moved(groups[group_index]["tabs"], tab_index, step) }
16
+ end
17
+
18
+ def order
19
+ @arranged.map { |group, tabs| entry(group, tabs) }
20
+ end
21
+
22
+ private
23
+
24
+ def entry(group, tabs)
25
+ return { "group" => group.label } if group.standalone?
26
+
27
+ { "group" => group.label, "tabs" => tabs.map { |tab| tab.key.to_s } }
28
+ end
29
+
30
+ def moved(items, index, step)
31
+ items.insert(index + step, items.delete_at(index))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module KeystoneUi
6
+ module Preferences
7
+ class PickNavigationOrder
8
+ def initialize(values:, person: nil, account: nil)
9
+ @person = person
10
+ @account = account
11
+ @values = values
12
+ end
13
+
14
+ def call
15
+ return Refusal.new("Your account sets the navigation for everyone, so this was not saved.") unless members_choose?
16
+ return reset if @values[:reset] == "1"
17
+ return Refusal.new("That navigation order is too large to save.") if @values[:order].to_s.bytesize > KeystoneUi::Preferences.configuration.max_value_bytes
18
+
19
+ order = submitted_order
20
+ return Refusal.new("Choose an order for the navigation's groups and tabs.") unless order.is_a?(Array)
21
+
22
+ preference.update!(value: preference.value.to_h.merge("order" => order))
23
+ Kept.new
24
+ end
25
+
26
+ private
27
+
28
+ def members_choose?
29
+ LayoutChoice.new(person: @person, account: @account, component_key: "navigation").members_choose?
30
+ end
31
+
32
+ def reset
33
+ preference.update!(value: preference.value.to_h.except("order"))
34
+ Kept.new
35
+ end
36
+
37
+ def preference
38
+ @preference ||= ComponentPreference.find_or_initialize_by(owner: @person, component_key: "navigation")
39
+ end
40
+
41
+ def submitted_order
42
+ JSON.parse(@values[:order].to_s)
43
+ rescue JSON::ParserError
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ module Preferences
5
+ class PickNavigationPlacement
6
+ PLACEMENTS = %w[top left right].freeze
7
+
8
+ def initialize(values:, person: nil, account: nil)
9
+ @person = person
10
+ @account = account
11
+ @values = values
12
+ end
13
+
14
+ def call
15
+ return Refusal.new("Your account sets the navigation for everyone, so this was not saved.") unless members_choose?
16
+ return Refusal.new("Choose Top, Left or Right for the navigation.") unless PLACEMENTS.include?(@values[:placement])
17
+
18
+ preference = ComponentPreference.find_or_initialize_by(owner: @person, component_key: "navigation")
19
+ preference.update!(value: preference.value.to_h.merge("placement" => @values[:placement]))
20
+ Kept.new
21
+ end
22
+
23
+ private
24
+
25
+ def members_choose?
26
+ LayoutChoice.new(person: @person, account: @account, component_key: "navigation").members_choose?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,59 @@
1
+ <% choice = KeystoneUi::Preferences::LayoutChoice.new(person: person, account: account, component_key: "navigation") %>
2
+ <% saved_order = choice.value&.dig("order") || [] %>
3
+
4
+ <% if choice.members_choose? %>
5
+ <% permitted = KeystoneUi.configuration.navigation_groups.map { |group| [ group, group.tabs_permitted_for(self) ] }.reject { |_group, tabs| tabs.empty? } %>
6
+ <% arranged = KeystoneUi::NavigationOrder.new(saved_order).arrange(permitted) %>
7
+ <% arrangement = KeystoneUi::Preferences::NavigationArrangement.new(arranged) %>
8
+
9
+ <% arranged.each_with_index do |(group, tabs), group_index| %>
10
+ <div data-navigation-group="<%= group.label %>">
11
+ <%= ui_grid(cols: { default: 1, sm: 2 }, gap: :sm) do %>
12
+ <p data-group-label><strong><%= group.label %></strong></p>
13
+ <%= ui_grid(cols: { default: 2 }, gap: :sm) do %>
14
+ <% if group_index.positive? %>
15
+ <%= form_with(url: submit_url, method: :patch, data: { turbo: false, group_move: "up" }) do %>
16
+ <%= hidden_field_tag "order", arrangement.group_moved(group_index, -1).to_json, id: nil %>
17
+ <%= ui_button(label: "Up", type: :submit, variant: :secondary, size: :sm) %>
18
+ <% end %>
19
+ <% end %>
20
+ <% if group_index < arranged.size - 1 %>
21
+ <%= form_with(url: submit_url, method: :patch, data: { turbo: false, group_move: "down" }) do %>
22
+ <%= hidden_field_tag "order", arrangement.group_moved(group_index, 1).to_json, id: nil %>
23
+ <%= ui_button(label: "Down", type: :submit, variant: :secondary, size: :sm) %>
24
+ <% end %>
25
+ <% end %>
26
+ <% end %>
27
+ <% end %>
28
+ <% tabs.each_with_index do |tab, tab_index| %>
29
+ <% next if group.standalone? %>
30
+ <div data-navigation-tab="<%= tab.key %>">
31
+ <%= ui_grid(cols: { default: 1, sm: 2 }, gap: :sm) do %>
32
+ <span data-tab-label><%= tab.label %></span>
33
+ <%= ui_grid(cols: { default: 2 }, gap: :sm) do %>
34
+ <% if tab_index.positive? %>
35
+ <%= form_with(url: submit_url, method: :patch, data: { turbo: false, tab_move: "up" }) do %>
36
+ <%= hidden_field_tag "order", arrangement.tab_moved(group_index, tab_index, -1).to_json, id: nil %>
37
+ <%= ui_button(label: "Up", type: :submit, variant: :secondary, size: :sm) %>
38
+ <% end %>
39
+ <% end %>
40
+ <% if tab_index < tabs.size - 1 %>
41
+ <%= form_with(url: submit_url, method: :patch, data: { turbo: false, tab_move: "down" }) do %>
42
+ <%= hidden_field_tag "order", arrangement.tab_moved(group_index, tab_index, 1).to_json, id: nil %>
43
+ <%= ui_button(label: "Down", type: :submit, variant: :secondary, size: :sm) %>
44
+ <% end %>
45
+ <% end %>
46
+ <% end %>
47
+ <% end %>
48
+ </div>
49
+ <% end %>
50
+ </div>
51
+ <% end %>
52
+
53
+ <%= form_with(url: submit_url, method: :patch, data: { turbo: false }) do %>
54
+ <%= hidden_field_tag "reset", "1", id: nil %>
55
+ <%= ui_button(label: "Reset", type: :submit, variant: :secondary) %>
56
+ <% end %>
57
+ <% else %>
58
+ <%= ui_alert(message: "Your account sets the navigation for everyone.") %>
59
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <% choice = KeystoneUi::Preferences::LayoutChoice.new(person: person, account: account, component_key: "navigation") %>
2
+ <% current = choice.value&.dig("placement") || "top" %>
3
+
4
+ <% if choice.members_choose? %>
5
+ <%= form_with(url: submit_url, method: :patch, data: { turbo: false }) do %>
6
+ <%= ui_grid(cols: { default: 1, sm: 3 }, gap: :sm) do %>
7
+ <% { "top" => "Top", "left" => "Left", "right" => "Right" }.each do |placement, label| %>
8
+ <%= ui_radio_card(name: "placement", value: placement, label: label, checked: placement == current) %>
9
+ <% end %>
10
+ <% end %>
11
+ <%= ui_button(label: "Save", type: :submit) %>
12
+ <% end %>
13
+ <% else %>
14
+ <%= ui_alert(message: "Your account sets the navigation for everyone.") %>
15
+ <% end %>
@@ -2,6 +2,6 @@
2
2
 
3
3
  module KeystoneUi
4
4
  module Preferences
5
- VERSION = "0.4.1"
5
+ VERSION = "0.6.0"
6
6
  end
7
7
  end
@@ -1,4 +1,5 @@
1
1
  require "keystone_ui"
2
+ require "keystone_ui/navigation_order"
2
3
  require "keystone_ui/preferences/version"
3
4
  require "keystone_ui/preferences/configuration"
4
5
  require "keystone_ui/preferences/engine"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keystone_ui-preferences
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 0.33.1
18
+ version: 0.41.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 0.33.1
25
+ version: 0.41.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: railties
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -83,9 +83,14 @@ files:
83
83
  - app/models/keystone_ui/preferences/component_preference.rb
84
84
  - app/models/keystone_ui/preferences/kept.rb
85
85
  - app/models/keystone_ui/preferences/layout_choice.rb
86
+ - app/models/keystone_ui/preferences/navigation_arrangement.rb
86
87
  - app/models/keystone_ui/preferences/pick_account_layouts.rb
88
+ - app/models/keystone_ui/preferences/pick_navigation_order.rb
89
+ - app/models/keystone_ui/preferences/pick_navigation_placement.rb
87
90
  - app/models/keystone_ui/preferences/refusal.rb
88
91
  - app/views/keystone_ui/preferences/settings/_account_layouts.html.erb
92
+ - app/views/keystone_ui/preferences/settings/_navigation_order.html.erb
93
+ - app/views/keystone_ui/preferences/settings/_navigation_placement.html.erb
89
94
  - config/routes.rb
90
95
  - lib/generators/keystone_ui/preferences/install/install_generator.rb
91
96
  - lib/generators/keystone_ui/preferences/install/templates/create_keystone_ui_preferences_component_preferences.rb.erb