super_settings 2.3.1 → 2.4.1

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.
@@ -5,29 +5,209 @@
5
5
  margin-right: auto;
6
6
  }
7
7
 
8
- #settings-table td p {
9
- margin-top: 0;
10
- margin-bottom: 0.5rem;
8
+ /* Card Layout Styles */
9
+ .super-settings-cards-container {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 0.75rem;
13
+ padding: 1rem 0;
14
+ }
15
+
16
+ .super-settings-card {
17
+ border: 2px solid var(--table-border-color);
18
+ border-radius: 0.5rem;
19
+ background-color: var(--card-bg-color);
20
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
21
+ transition: box-shadow 0.2s ease-in-out;
22
+ width: 100%;
11
23
  }
12
24
 
13
- #settings-table td p:last-of-type {
14
- margin-bottom: 0;
25
+ .super-settings-card:hover {
26
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
15
27
  }
16
28
 
17
- #settings-table input[type=text], #settings-table input[type=number], #settings-table input[type=date], #settings-table input[type=time], #settings-table textarea {
18
- width: 100%;
29
+ .super-settings-cards-container .super-settings-card:nth-child(even) {
30
+ background-color: var(--alt-row-color);
31
+ }
32
+
33
+ .super-settings-card-edit {
34
+ background-color: var(--edit-bg-color) !important;
35
+ border-color: var(--primary-border-color);
19
36
  }
20
37
 
21
- #settings-table tr[data-deleted] td {
38
+ .super-settings-card[data-deleted] {
22
39
  background-color: var(--deleted-row-bg-color) !important;
23
40
  color: var(--deleted-row-color);
41
+ }
42
+
43
+ .super-settings-card[data-deleted] .js-value-placeholder,
44
+ .super-settings-card[data-deleted] strong,
45
+ .super-settings-card[data-deleted] .super-settings-form-control {
24
46
  text-decoration: line-through;
25
47
  }
26
48
 
27
- #settings-table tr[data-newrecord] .js-show-history {
28
- display: none;
49
+ .super-settings-card[data-deleted] .super-settings-card-label {
50
+ text-decoration: none;
51
+ }
52
+
53
+ /* Card content layout for desktop - column alignment */
54
+ @media (min-width: 768px) {
55
+ .super-settings-card-content {
56
+ display: grid;
57
+ grid-template-columns: 2fr 2fr 1fr 3fr 1fr 1fr;
58
+ gap: 1rem;
59
+ align-items: start;
60
+ padding: 1rem;
61
+ }
62
+
63
+ .super-settings-card-key {
64
+ grid-column: 1;
65
+ }
66
+
67
+ .super-settings-card-value {
68
+ grid-column: 2;
69
+ margin-bottom: 0;
70
+ }
71
+
72
+ .super-settings-card-type {
73
+ grid-column: 3;
74
+ }
75
+
76
+ .super-settings-card-description {
77
+ grid-column: 4;
78
+ margin-bottom: 0;
79
+ }
80
+
81
+ .super-settings-card-modified {
82
+ grid-column: 5;
83
+ }
84
+
85
+ .super-settings-card-controls {
86
+ grid-column: 6;
87
+ margin-left: 0;
88
+ }
89
+ }
90
+
91
+ /* Mobile layout - stacked */
92
+ @media (max-width: 767px) {
93
+ .super-settings-card-content {
94
+ padding: 1rem;
95
+ }
96
+
97
+ .super-settings-card-key {
98
+ margin-bottom: 1rem;
99
+ }
100
+
101
+ .super-settings-card-value {
102
+ margin-bottom: 1rem;
103
+ }
104
+
105
+ .super-settings-card-meta {
106
+ display: grid;
107
+ grid-template-columns: 1fr 1fr;
108
+ gap: 1rem;
109
+ margin-bottom: 1rem;
110
+ }
111
+
112
+ .super-settings-card-description {
113
+ margin-bottom: 1rem;
114
+ }
115
+
116
+ .super-settings-card-controls {
117
+ display: flex;
118
+ gap: 0.25rem;
119
+ justify-content: flex-end;
120
+ }
29
121
  }
30
122
 
123
+ .super-settings-card-key input[type="text"] {
124
+ margin: 0;
125
+ width: 100%;
126
+ }
127
+
128
+ .super-settings-card-controls {
129
+ display: flex;
130
+ gap: 0.25rem;
131
+ flex-shrink: 0;
132
+ }
133
+
134
+ .super-settings-card-label {
135
+ display: block;
136
+ font-size: 0.75rem;
137
+ font-weight: 600;
138
+ color: var(--muted-color);
139
+ margin-bottom: 0.25rem;
140
+ text-transform: uppercase;
141
+ letter-spacing: 0.025em;
142
+ }
143
+
144
+ /* Sort Controls */
145
+ @media (min-width: 768px) {
146
+ .super-settings-sort-controls {
147
+ display: grid;
148
+ grid-template-columns: 2fr 2fr 1fr 3fr 1fr 1fr;
149
+ gap: 1rem;
150
+ align-items: center;
151
+ padding: 0.125rem 1rem 0.125rem 1rem;
152
+ background-color: transparent;
153
+ border: none;
154
+ margin-bottom: 0.25rem;
155
+ }
156
+
157
+ .super-settings-sort-label {
158
+ display: none;
159
+ }
160
+
161
+ .super-settings-sort-controls .super-settings-sort-control {
162
+ padding: 0.125rem 0;
163
+ margin: 0;
164
+ margin-top: 1rem; /* Align with card content that has labels */
165
+ text-align: left;
166
+ font-weight: 600;
167
+ color: var(--form-control-color);
168
+ }
169
+
170
+ .super-settings-sort-controls > span {
171
+ font-weight: 600;
172
+ color: var(--form-control-color);
173
+ }
174
+ }
175
+
176
+ /* Mobile sort controls */
177
+ @media (max-width: 767px) {
178
+ .super-settings-sort-controls {
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 1rem;
182
+ margin-bottom: 0.5rem;
183
+ padding: 0.25rem 1rem;
184
+ background-color: transparent;
185
+ border: none;
186
+ }
187
+
188
+ .super-settings-sort-label {
189
+ font-weight: 600;
190
+ color: var(--form-control-color);
191
+ }
192
+
193
+ .super-settings-sort-control {
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 0.25rem;
197
+ padding: 0.25rem 0.5rem;
198
+ border: 1px solid transparent;
199
+ border-radius: 0.25rem;
200
+ transition: all 0.2s ease-in-out;
201
+ }
202
+
203
+ .super-settings-sort-control[data-selected="true"] {
204
+ background-color: var(--primary-color);
205
+ color: var(--primary-contrast-color);
206
+ border-color: var(--primary-border-color);
207
+ }
208
+ }
209
+
210
+ /* Icons and Button Controls */
31
211
  .super-settings-icon {
32
212
  display: inline-block;
33
213
  }
@@ -47,8 +227,6 @@
47
227
  cursor: pointer;
48
228
  }
49
229
 
50
-
51
-
52
230
  .super-settings-sort-control {
53
231
  color: var(--unselected-color);
54
232
  }
@@ -57,74 +235,94 @@
57
235
  vertical-align: middle;
58
236
  }
59
237
 
60
- .super-settings-edit-row {
61
- background-color: var(--edit-bg-color) !important;
62
- }
63
-
64
- .super-settings-key {
65
- overflow-wrap: break-word;
66
- max-width: 30rem;
67
- min-width: 8rem;
68
- }
69
-
70
- .super-settings-value {
71
- overflow-wrap: break-word;
72
- max-width: 30rem;
73
- min-width: 8rem;
74
- }
75
-
76
- .super-settings-value-type {
77
- width: 7rem;
78
- }
79
-
80
- .super-settings-description {
81
- min-width: 20rem;
82
- }
83
-
238
+ /* Utility Classes */
84
239
  .super-settings-max-height-text {
85
240
  max-height: 15rem;
86
241
  overflow-y: scroll;
87
242
  }
88
243
 
89
- .super-settings-controls {
90
- width: 6rem;
91
- white-space: nowrap;
92
- text-align: right;
93
- text-decoration: none !important;
94
- }
95
-
96
244
  .super-settings-history-key {
97
245
  font-weight: normal;
98
246
  color: var(--history-key-color);
99
247
  }
100
248
 
101
- .super-settings-table {
249
+ /* History Display Layout */
250
+ .super-settings-history-container {
102
251
  width: 100%;
103
- max-width: 100%;
104
252
  margin-bottom: 1rem;
105
- border-collapse: collapse;
106
253
  }
107
254
 
108
- .super-settings-table thead th {
109
- vertical-align: bottom;
255
+ .super-settings-history-header {
256
+ display: grid;
257
+ grid-template-columns: 2fr 2fr 3fr;
258
+ gap: 1rem;
259
+ padding: 0.75rem;
260
+ font-weight: 600;
110
261
  border-bottom: 2px solid var(--table-border-color);
111
- white-space: nowrap;
262
+ background-color: var(--table-header-bg-color);
263
+ }
264
+
265
+ .super-settings-history-items {
266
+ display: flex;
267
+ flex-direction: column;
112
268
  }
113
269
 
114
- .super-settings-table td, .super-settings-table th {
270
+ .super-settings-history-item {
271
+ display: grid;
272
+ grid-template-columns: 2fr 2fr 3fr;
273
+ gap: 1rem;
115
274
  padding: 0.75rem;
116
- vertical-align: top;
117
- border-top: 1px solid var(--table-border-color);
275
+ border-bottom: 1px solid var(--table-border-color);
276
+ align-items: start;
118
277
  }
119
278
 
120
- .super-settings-table-striped tbody tr:nth-of-type(odd) {
279
+ .super-settings-history-item:nth-of-type(even) {
121
280
  background-color: var(--alt-row-color);
122
281
  }
123
282
 
283
+ .super-settings-history-time {
284
+ white-space: nowrap;
285
+ }
286
+
287
+ .super-settings-history-user {
288
+ word-break: break-word;
289
+ }
290
+
291
+ .super-settings-history-value {
292
+ word-break: break-word;
293
+ }
294
+
295
+ /* Mobile responsive layout for history */
296
+ @media (max-width: 767px) {
297
+ .super-settings-history-header {
298
+ display: none;
299
+ }
300
+
301
+ .super-settings-history-item {
302
+ display: block;
303
+ padding: 1rem;
304
+ margin-bottom: 0.5rem;
305
+ border: 1px solid var(--table-border-color);
306
+ border-radius: 0.5rem;
307
+ background-color: var(--form-control-bg-color);
308
+ }
309
+
310
+ .super-settings-history-item:nth-of-type(odd) {
311
+ background-color: var(--form-control-bg-color);
312
+ }
313
+
314
+ .super-settings-history-time,
315
+ .super-settings-history-user,
316
+ .super-settings-history-value {
317
+ margin-bottom: 0.5rem;
318
+ }
319
+ }
320
+
124
321
  .super-settings-align-center {
125
322
  text-align: center;
126
323
  }
127
324
 
325
+ /* Modal Dialog */
128
326
  .super-settings-modal {
129
327
  z-index: 1000000000000;
130
328
  display: none;
@@ -167,6 +365,7 @@
167
365
  font-weight: 500;
168
366
  }
169
367
 
368
+ /* Screen Reader and Accessibility */
170
369
  .super-settings-sr-only {
171
370
  position: absolute;
172
371
  width: 1px;
@@ -178,6 +377,7 @@
178
377
  border: 0;
179
378
  }
180
379
 
380
+ /* Utility Classes */
181
381
  .super-settings-text-nowrap {
182
382
  white-space: nowrap !important;
183
383
  }
@@ -189,6 +389,24 @@
189
389
  background-color: var(--table-header-bg-color);
190
390
  }
191
391
 
392
+ .super-settings-align-center {
393
+ text-align: center;
394
+ }
395
+
396
+ .super-settings-text-success {
397
+ color: var(--success-color) !important;
398
+ }
399
+
400
+ .super-settings-text-danger {
401
+ color: var(--danger-color) !important;
402
+ }
403
+
404
+ .super-settings-text-muted {
405
+ color: var(--muted-color) !important;
406
+ }
407
+
408
+ /* Form Controls */
409
+
192
410
  .super-settings-form-control {
193
411
  font-family: inherit;
194
412
  margin: 0;
@@ -240,43 +458,43 @@ select.super-settings-form-control:not([size]):not([multiple]) {
240
458
  resize: vertical;
241
459
  }
242
460
 
243
- .super-settings-text-success {
244
- color: var(--success-color) !important;
245
- }
246
-
247
- .super-settings-text-danger {
248
- color: var(--danger-color) !important;
461
+ /* Ensure textareas use full width in edit mode */
462
+ .super-settings-card-edit .super-settings-card-description textarea {
463
+ width: 100%;
464
+ box-sizing: border-box;
465
+ min-width: 0; /* Allow shrinking in grid */
249
466
  }
250
467
 
251
- .super-settings-text-muted {
252
- color: var(--muted-color) !important;
253
- }
468
+ /* Buttons */
254
469
 
255
470
  .super-settings-btn {
256
- display: inline-block;
471
+ display: inline-block;
257
472
  vertical-align: middle;
258
473
  padding: 0.375rem 0.75rem;
259
- border-radius: 0.375rem;
260
- border: 1px solid #dcdcdc;
261
- background-color:#f9f9f9;
262
- text-decoration: none;
474
+ border-radius: 0.375rem;
475
+ color: var(--form-control-color);
476
+ border: 1px solid var(--form-control-border-color);
477
+ background-color: var(--form-control-bg-color);
478
+ text-decoration: none;
263
479
  text-align: center;
264
- font-family: Arial, sans-serif;
480
+ font-family: Arial, sans-serif;
265
481
  font-size: 1rem;
266
- font-weight: 400;
482
+ font-weight: 400;
267
483
  line-height: 1.5;
268
484
  user-select: none;
269
485
  cursor: pointer;
270
- transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out, box-shadow .15s ease-in-out;
486
+ transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
271
487
  }
272
488
 
273
489
  .super-settings-btn:hover:not(:disabled) {
274
- background-color:#e9e9e9;
490
+ background-color: var(--btn-hover-color);
275
491
  }
492
+
276
493
  .super-settings-btn:active {
277
- position:relative;
278
- top:1px;
494
+ position: relative;
495
+ top: 1px;
279
496
  }
497
+
280
498
  .super-settings-btn:disabled {
281
499
  opacity: 0.8;
282
500
  cursor: not-allowed;
@@ -285,10 +503,11 @@ select.super-settings-form-control:not([size]):not([multiple]) {
285
503
  .super-settings-btn-primary {
286
504
  background-color: var(--primary-color);
287
505
  border-color: var(--primary-border-color);
288
- color: var(--primary-contrast-color);
506
+ color: var(--primary-contrast-color);
289
507
  }
508
+
290
509
  .super-settings-btn-primary:hover:not(:disabled) {
291
- background-color: var(--primary-hover-color);
510
+ background-color: var(--primary-hover-color);
292
511
  }
293
512
 
294
513
  .super-settings-btn-primary:disabled {
@@ -29,7 +29,7 @@ module SuperSettings
29
29
 
30
30
  # Render the web UI application HTML.
31
31
  #
32
- # @return [void]
32
+ # @return [String] the rendered HTML
33
33
  def render
34
34
  template = ERB.new(File.read(File.expand_path(File.join("application", "index.html.erb"), __dir__)))
35
35
  html = template.result(binding)
@@ -5,6 +5,7 @@ require "set"
5
5
  module SuperSettings
6
6
  # Utility functions for coercing values to other data types.
7
7
  class Coerce
8
+ # Set of values that should be considered false when converting to boolean.
8
9
  # rubocop:disable Lint/BooleanSymbol
9
10
  FALSE_VALUES = Set.new([
10
11
  "0", :"0",
@@ -2,28 +2,51 @@
2
2
 
3
3
  module SuperSettings
4
4
  module Context
5
+ # Current context for maintaining consistent setting values and random numbers
6
+ # within a specific execution context.
5
7
  class Current
6
8
  def initialize
7
9
  @context = {}
8
10
  @seed = nil
9
11
  end
10
12
 
13
+ # Check if the context includes a specific key.
14
+ #
15
+ # @param key [String] the key to check
16
+ # @return [Boolean] true if the key exists in the context
11
17
  def include?(key)
12
18
  @context.include?(key)
13
19
  end
14
20
 
21
+ # Get a value from the context.
22
+ #
23
+ # @param key [String] the key to retrieve
24
+ # @return [Object] the value associated with the key
15
25
  def [](key)
16
26
  @context[key]
17
27
  end
18
28
 
29
+ # Set a value in the context.
30
+ #
31
+ # @param key [String] the key to set
32
+ # @param value [Object] the value to store
33
+ # @return [Object] the stored value
19
34
  def []=(key, value)
20
35
  @context[key] = value
21
36
  end
22
37
 
38
+ # Delete a value from the context.
39
+ #
40
+ # @param key [String] the key to delete
41
+ # @return [Object] the deleted value
23
42
  def delete(key)
24
43
  @context.delete(key)
25
44
  end
26
45
 
46
+ # Generate a consistent random number for this context.
47
+ #
48
+ # @param max [Integer, Float, Range] the maximum value or range
49
+ # @return [Integer, Float] the random number
27
50
  def rand(max = nil)
28
51
  @seed ||= Random.new_seed
29
52
  Random.new(@seed).rand(max || 1.0)
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SuperSettings
4
+ # Context classes for maintaining consistent state during execution blocks.
5
+ # These contexts ensure that settings and random values remain constant
6
+ # within a specific execution scope.
4
7
  module Context
5
8
  autoload :Current, File.join(__dir__, "context/current")
6
9
  autoload :RackMiddleware, File.join(__dir__, "context/rack_middleware")
@@ -60,6 +60,7 @@ module SuperSettings
60
60
  end
61
61
 
62
62
  return nil if value == NOT_DEFINED
63
+
63
64
  value
64
65
  end
65
66
 
@@ -105,11 +106,15 @@ module SuperSettings
105
106
  end
106
107
 
107
108
  # Load all the settings from the database into the cache.
109
+ #
110
+ # @param asynchronous [Boolean] whether to load settings in a background thread
111
+ # @return [void]
108
112
  def load_settings(asynchronous = false)
109
113
  return if @refreshing
110
114
 
111
115
  @lock.synchronize do
112
116
  return if @refreshing
117
+
113
118
  @refreshing = true
114
119
  @next_check_at = Time.now + @refresh_interval
115
120
  end
@@ -135,6 +140,9 @@ module SuperSettings
135
140
  end
136
141
 
137
142
  # Load only settings that have changed since the last load.
143
+ #
144
+ # @param asynchronous [Boolean] whether to refresh settings in a background thread
145
+ # @return [void]
138
146
  def refresh(asynchronous = false)
139
147
  last_refresh_time = @last_refreshed
140
148
  return if last_refresh_time.nil?
@@ -142,8 +150,10 @@ module SuperSettings
142
150
 
143
151
  @lock.synchronize do
144
152
  return if @refreshing
153
+
145
154
  @next_check_at = Time.now + @refresh_interval
146
155
  return if @cache.empty?
156
+
147
157
  @refreshing = true
148
158
  end
149
159
 
@@ -166,6 +176,8 @@ module SuperSettings
166
176
  end
167
177
 
168
178
  # Reset the cache to an unloaded state.
179
+ #
180
+ # @return [void]
169
181
  def reset
170
182
  @lock.synchronize do
171
183
  @cache = {}.freeze
@@ -189,6 +201,7 @@ module SuperSettings
189
201
  # @api private
190
202
  def update_setting(setting)
191
203
  return if Coerce.blank?(setting.key)
204
+
192
205
  @lock.synchronize do
193
206
  @cache = @cache.merge(setting.key => setting.value)
194
207
  end
@@ -199,6 +212,7 @@ module SuperSettings
199
212
  def wait_for_load
200
213
  loop do
201
214
  return unless @refreshing
215
+
202
216
  sleep(0.001)
203
217
  end
204
218
  end
@@ -11,7 +11,7 @@ module SuperSettings
11
11
  #
12
12
  # You are also responsible for implementing any CSRF protection if your authentication method
13
13
  # uses stateful requests (i.e. cookies or Basic auth where browser automatically include the
14
- # credentials on every reqeust). There are other gems available that can be integrated into
14
+ # credentials on every request). There are other gems available that can be integrated into
15
15
  # your middleware stack to provide this feature. If you need to inject meta elements into
16
16
  # the page, you can do so with the +add_to_head+ method.
17
17
  class RackApplication
@@ -225,8 +225,10 @@ module SuperSettings
225
225
  def check_authorization(request, write_required: false)
226
226
  user = current_user(request)
227
227
  return json_response(401, error: "Authentiation required") unless authenticated?(user)
228
+
228
229
  allowed = (write_required ? allow_write?(user) : allow_read?(user))
229
230
  return json_response(403, error: "Access denied") unless allowed
231
+
230
232
  yield(user)
231
233
  end
232
234