ultra_settings 2.6.1 → 2.8.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 +4 -4
- data/CHANGELOG.md +22 -1
- data/README.md +6 -4
- data/VERSION +1 -1
- data/app/_config_description.html.erb +30 -0
- data/app/_config_list.html.erb +14 -0
- data/app/_select_menu.html.erb +53 -0
- data/app/application.css +172 -34
- data/app/application.js +108 -25
- data/app/configuration.html.erb +57 -24
- data/app/index.html.erb +21 -16
- data/lib/ultra_settings/application_view.rb +20 -10
- data/lib/ultra_settings/coerce.rb +3 -0
- data/lib/ultra_settings/config_helper.rb +12 -8
- data/lib/ultra_settings/configuration.rb +41 -8
- data/lib/ultra_settings/configuration_view.rb +12 -6
- data/lib/ultra_settings/field.rb +3 -2
- data/lib/ultra_settings/rack_app.rb +7 -0
- data/lib/ultra_settings/render_helper.rb +28 -0
- data/lib/ultra_settings/uninitialized_runtime_settings.rb +12 -7
- data/lib/ultra_settings/view_helper.rb +19 -0
- data/lib/ultra_settings/web_view.rb +9 -1
- data/lib/ultra_settings/yaml_config.rb +7 -0
- data/lib/ultra_settings.rb +49 -3
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 846f2c54ef9d4019e4f97ad02dcc962813a29fccc6128fe1e18ef9f263c01c2e
|
|
4
|
+
data.tar.gz: 7d28f71ae610362fd8e2c4e0ffe2ae885d7b9f270fc0e942f41fd7e8daff18fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8829e070b1f37474fc6673804d219d1485b2cb36c5d81f9dcd4b88ff6623bd1f99a5a135adedf4cc2a2f1865ac27d74b8f405b01896aabc3da2b5e41226333f3
|
|
7
|
+
data.tar.gz: 0e906fddfea5764d653beb41c99d534c30b44974a83b8f7e3de1d76dd1656e367fe160743f4ac65ec422bf0e1c7d0c35b28ff2d5e3ce4252bf70d53d0f367709
|
data/CHANGELOG.md
CHANGED
|
@@ -4,17 +4,38 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 2.8.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added support for setting a description on configuration classes. The class description can serve to document the purpose of the configuration and will be shown in the web UI.
|
|
12
|
+
- Improved menu for selecting configuration classes in the web UI by adding a search box to filter the list of configurations.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed filtering of data sources for configuration classes in the web UI.
|
|
17
|
+
- The YAML source will not be shown if the configuration file is set to nil or false.
|
|
18
|
+
- The runtime settings source will not be shown if the runtime settings engine is not set up.
|
|
19
|
+
- Fields that override the class default for a data source will now correctly show that source.
|
|
20
|
+
|
|
21
|
+
## 2.7.0
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Added new setting to indicate if the runtime settings engine is secure. If the engine is marked as not secure, then runtime settings will be disabled for all fields marked as secret. This protects sensitive information from being exposed in the web UI or through the API. The default value is `true` to maintain backwards compatibility.
|
|
26
|
+
|
|
7
27
|
## 2.6.1
|
|
8
28
|
|
|
9
29
|
### Added
|
|
10
30
|
|
|
11
31
|
- Show icons on the web UI that open a dialog with the current value for each data source.
|
|
32
|
+
- Added support for passing the field description in `UltraSettings.runtime_settings_url` using the `${description}` placeholder in the URL.
|
|
12
33
|
|
|
13
34
|
## 2.6.0
|
|
14
35
|
|
|
15
36
|
### Added
|
|
16
37
|
|
|
17
|
-
- Added support for passing the type in `UltraSettings.runtime_settings_url`
|
|
38
|
+
- Added support for passing the type in `UltraSettings.runtime_settings_url` using the `${type}` placeholder in the URL.
|
|
18
39
|
|
|
19
40
|
### Changed
|
|
20
41
|
|
data/README.md
CHANGED
|
@@ -81,7 +81,6 @@ class MyServiceConfiguration < UltraSettings::Configuration
|
|
|
81
81
|
field :auth_token,
|
|
82
82
|
type: :string,
|
|
83
83
|
env_var: "MY_SERVICE_TOKEN",
|
|
84
|
-
runtime_setting: false,
|
|
85
84
|
yaml_key: false,
|
|
86
85
|
description: "Bearer token for accessing the service",
|
|
87
86
|
secret: true
|
|
@@ -114,7 +113,7 @@ You can customize the behavior of each field using various options:
|
|
|
114
113
|
|
|
115
114
|
- `:default_if` - Provides a condition for when the default should be used. This should be a Proc or the name of a method within the class. Useful for ensuring values meet specific constraints. This can provide protection from misconfiguration that can break the application. In the above example, the default value for `timeout` will be used if the value is less than or equal to 0.
|
|
116
115
|
|
|
117
|
-
- `:secret` - Marks the field as secret. Secret fields are not displayed in the web UI. By default, all fields are considered secret to avoid accidentally exposing sensitive values. You can change this default behavior by setting `fields_secret_by_default` to `false` either globally or per configuration.
|
|
116
|
+
- `:secret` - Marks the field as secret. Secret fields are not displayed in the web UI. By default, all fields are considered secret to avoid accidentally exposing sensitive values. You can change this default behavior by setting `fields_secret_by_default` to `false` either globally or per configuration. Additionaly, if you set `UltraSettings.runtime_settings_secure` to false, then runtime settings will be disabled on secret fields.
|
|
118
117
|
|
|
119
118
|
- `:env_var` - Overrides the environment variable name used to populate the field. This is useful if the variable name does not follow the conventional pattern. Set this to `false` to disable loading the field from an environment variable.
|
|
120
119
|
|
|
@@ -172,6 +171,9 @@ UltraSettings.runtime_settings = RedisRuntimeSettings.new
|
|
|
172
171
|
|
|
173
172
|
The runtime settings implementation may also define an `array` method that takes a single parameter to return an array value. If this method is not implemented, then array values must be returned as single line CSV strings.
|
|
174
173
|
|
|
174
|
+
> [!TIP]
|
|
175
|
+
> If your runtime settings implementation does not securely store values, you should set `UltraSettings.runtime_settings_secure` to `false`. This will disable runtime settings on fields marked as secret to prevent leaking sensitive information.
|
|
176
|
+
|
|
175
177
|
#### Using the `super_settings` gem
|
|
176
178
|
|
|
177
179
|
There is a companion gem [super_settings](https://github.com/bdurand/super_settings) that can be used as a drop in implementation for the runtime settings. You just need to set the runtime settings to the `SuperSettings` object.
|
|
@@ -211,7 +213,7 @@ You can customize the behavior of runtime setting names with the following optio
|
|
|
211
213
|
|
|
212
214
|
- **Disabling Runtime Settings:** You can disable runtime settings as a default source for fields by setting `runtime_settings_disabled` to `true` in your configuration class. You can disable runtime settings on individual fields by setting `runtime_setting` on the field to `false`.
|
|
213
215
|
|
|
214
|
-
- **Editing Links** You can specify a URL for editing runtime settings from the web UI by setting `UltraSettings.runtime_settings_url` to the desired URL. This will add links to the runtime settings in the web UI. You can use the placeholders `${name}
|
|
216
|
+
- **Editing Links** You can specify a URL for editing runtime settings from the web UI by setting `UltraSettings.runtime_settings_url` to the desired URL. This will add links to the runtime settings in the web UI. You can use the placeholders `${name}`, `${type}`, and `${description}` in the URL which will be replaced with the name, type, and description of the field respectively. If you are using the `super_settings` gem for runtime settings, then you can target a setting by adding `#edit=${name}&type=${type}&description=${description}` to the root URL where `super_settings` is mounted.
|
|
215
217
|
|
|
216
218
|
If a setting value cannot be loaded from the runtime settings, then it's value will attempt to be loaded from a YAML file.
|
|
217
219
|
|
|
@@ -422,7 +424,7 @@ If you prefer to embed the settings view directly into your own admin tools or d
|
|
|
422
424
|
```erb
|
|
423
425
|
<h1>Configuration</h1>
|
|
424
426
|
|
|
425
|
-
<%= UltraSettings::ApplicationView.new.render
|
|
427
|
+
<%= UltraSettings::ApplicationView.new.render %>
|
|
426
428
|
```
|
|
427
429
|
|
|
428
430
|
This approach allows for seamless integration of the settings UI into your application's admin interface, leveraging your existing authentication and authorization mechanisms. The settings are rendered with navigation handled by an HTML select element. You can specify the CSS classes for the select element to match your own application styles..
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.8.0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<% config_class = configuration.class
|
|
2
|
+
has_config_file = config_class.configuration_file.is_a?(Pathname) && config_class.fields.any?(&:yaml_key) %>
|
|
3
|
+
|
|
4
|
+
<% if config_class.description || has_config_file %>
|
|
5
|
+
<div class="ultra-settings-description-container">
|
|
6
|
+
<% if config_class.description %>
|
|
7
|
+
<div class="ultra-settings-description">
|
|
8
|
+
<%= html_escape(config_class.description).gsub("\n", "<br>") %>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<% if has_config_file %>
|
|
13
|
+
<div class="ultra-settings-description">
|
|
14
|
+
<span class="ultra-settings-config-file-label">
|
|
15
|
+
Configuration file:
|
|
16
|
+
</span>
|
|
17
|
+
|
|
18
|
+
<code class="ultra-settings-config-file-path">
|
|
19
|
+
<%= html_escape(relative_path(configuration.class.configuration_file)) %>
|
|
20
|
+
</code>
|
|
21
|
+
|
|
22
|
+
<% unless configuration.class.configuration_file&.exist? %>
|
|
23
|
+
<span class="ultra-settings-file-not-found">
|
|
24
|
+
(file does not exist)
|
|
25
|
+
</span>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
<% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="ultra-settings-configuration-list" style="display:none;">
|
|
2
|
+
<% configurations.each do |configuration| %>
|
|
3
|
+
<div class="ultra-settings-configuration-summary">
|
|
4
|
+
<a
|
|
5
|
+
href="#<%= html_escape(configuration.class.name) %>"
|
|
6
|
+
class="ultra-settings-configuration-title"
|
|
7
|
+
>
|
|
8
|
+
<%= html_escape(configuration.class.name) %>
|
|
9
|
+
</a>
|
|
10
|
+
|
|
11
|
+
<p><%= html_escape(configuration.class.description) %></p>
|
|
12
|
+
</div>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<div class="ultra-settings-dropdown" id="config-dropdown">
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class="ultra-settings-dropdown-button"
|
|
5
|
+
id="config-dropdown-button"
|
|
6
|
+
>
|
|
7
|
+
Select Configuration
|
|
8
|
+
</button>
|
|
9
|
+
|
|
10
|
+
<div
|
|
11
|
+
class="ultra-settings-dropdown-menu"
|
|
12
|
+
id="config-dropdown-menu"
|
|
13
|
+
style="display: none;"
|
|
14
|
+
>
|
|
15
|
+
<div class="ultra-settings-search-container">
|
|
16
|
+
<input
|
|
17
|
+
type="text"
|
|
18
|
+
id="config-search"
|
|
19
|
+
placeholder="Search..."
|
|
20
|
+
autocomplete="off"
|
|
21
|
+
>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<ul class="ultra-settings-dropdown-list" id="config-list">
|
|
25
|
+
<% configurations.each do |config| %>
|
|
26
|
+
<% config_text = [
|
|
27
|
+
config.class.name,
|
|
28
|
+
config.class.description
|
|
29
|
+
]
|
|
30
|
+
config.class.fields.each do |field|
|
|
31
|
+
config_text << field.name
|
|
32
|
+
config_text << field.description
|
|
33
|
+
config_text << field.env_var
|
|
34
|
+
config_text << field.runtime_setting
|
|
35
|
+
end
|
|
36
|
+
search_text = config_text.compact.join(" ").downcase %>
|
|
37
|
+
|
|
38
|
+
<li
|
|
39
|
+
class="ultra-settings-dropdown-item"
|
|
40
|
+
data-value="config-<%= html_escape(config.class.name) %>"
|
|
41
|
+
data-label="<%= html_escape(config.class.name) %>"
|
|
42
|
+
data-search="<%= html_escape(search_text) %>"
|
|
43
|
+
>
|
|
44
|
+
<span class="ultra-settings-check-icon"></span>
|
|
45
|
+
|
|
46
|
+
<span class="ultra-settings-config-name">
|
|
47
|
+
<%= html_escape(config.class.name) %>
|
|
48
|
+
</span>
|
|
49
|
+
</li>
|
|
50
|
+
<% end %>
|
|
51
|
+
</ul>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
data/app/application.css
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
margin-bottom: 1rem;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
.ultra-settings-
|
|
6
|
-
margin: 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.ultra-settings-config-file {
|
|
5
|
+
.ultra-settings-description-container {
|
|
10
6
|
margin-bottom: 1.5rem;
|
|
11
|
-
|
|
12
|
-
background-color: var(--config-file-bg-color);
|
|
7
|
+
line-height: 1.5;
|
|
13
8
|
border: 1px solid var(--config-file-border-color);
|
|
14
9
|
border-radius: 0.375rem;
|
|
10
|
+
padding: 1rem 1rem 0 1rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ultra-settings-description {
|
|
14
|
+
margin-bottom: 1rem;
|
|
15
|
+
color: var(--description-color);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
.ultra-settings-config-file-label {
|
|
18
|
-
font-weight:
|
|
19
|
-
color: var(--
|
|
20
|
-
margin-right: 0.5rem;
|
|
19
|
+
font-weight: 500;
|
|
20
|
+
color: var(--description-color);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.ultra-settings-config-file-path {
|
|
@@ -184,9 +184,9 @@ code.ultra-settings-field-data-value {
|
|
|
184
184
|
letter-spacing: 0.025em;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
.ultra-settings-source-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
.ultra-settings-source-indicator::before {
|
|
188
|
+
content: "● ";
|
|
189
|
+
font-size: 1rem;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
.ultra-settings-icon-info {
|
|
@@ -218,27 +218,6 @@ code.ultra-settings-field-data-value {
|
|
|
218
218
|
opacity: 1;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
.ultra-settings-select {
|
|
222
|
-
display: inline-block;
|
|
223
|
-
padding: .375rem 2.25rem .375rem .75rem;
|
|
224
|
-
-moz-padding-start: calc(0.75rem - 3px);
|
|
225
|
-
font-size: 1rem;
|
|
226
|
-
font-weight: 400;
|
|
227
|
-
line-height: 1.5;
|
|
228
|
-
color: var(--form-control-color);
|
|
229
|
-
background-color: var(--form-control-bg-color);
|
|
230
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
|
|
231
|
-
background-repeat: no-repeat;
|
|
232
|
-
background-position: right .75rem center;
|
|
233
|
-
background-size: 16px 12px;
|
|
234
|
-
border: 1px solid var(--form-control-border-color);
|
|
235
|
-
border-radius: .25rem;
|
|
236
|
-
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
|
237
|
-
-webkit-appearance: none;
|
|
238
|
-
-moz-appearance: none;
|
|
239
|
-
appearance: none;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
221
|
.ultra-settings-dialog {
|
|
243
222
|
min-width: 20rem;
|
|
244
223
|
padding: 0;
|
|
@@ -273,4 +252,163 @@ code.ultra-settings-field-data-value {
|
|
|
273
252
|
padding: 1rem;
|
|
274
253
|
background-color: var(--background-color);
|
|
275
254
|
color: var(--text-color);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Dropdown Styles */
|
|
258
|
+
.ultra-settings-dropdown {
|
|
259
|
+
position: relative;
|
|
260
|
+
display: inline-block;
|
|
261
|
+
width: 100%;
|
|
262
|
+
max-width: 400px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.ultra-settings-dropdown-button {
|
|
266
|
+
width: 100%;
|
|
267
|
+
text-align: left;
|
|
268
|
+
padding: 0.5rem 1rem;
|
|
269
|
+
font-size: 1.25rem;
|
|
270
|
+
font-weight: 500;
|
|
271
|
+
background-color: var(--form-control-bg-color);
|
|
272
|
+
color: var(--form-control-color);
|
|
273
|
+
border: 1px solid var(--form-control-border-color);
|
|
274
|
+
border-radius: 0.375rem;
|
|
275
|
+
cursor: pointer;
|
|
276
|
+
display: flex;
|
|
277
|
+
justify-content: space-between;
|
|
278
|
+
align-items: center;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.ultra-settings-dropdown-button::after {
|
|
282
|
+
content: "";
|
|
283
|
+
display: inline-block;
|
|
284
|
+
width: 0;
|
|
285
|
+
height: 0;
|
|
286
|
+
margin-left: 0.5rem;
|
|
287
|
+
vertical-align: middle;
|
|
288
|
+
border-top: 4px solid;
|
|
289
|
+
border-right: 4px solid transparent;
|
|
290
|
+
border-left: 4px solid transparent;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.ultra-settings-dropdown-menu {
|
|
294
|
+
position: absolute;
|
|
295
|
+
top: 100%;
|
|
296
|
+
left: 0;
|
|
297
|
+
z-index: 1000;
|
|
298
|
+
display: none;
|
|
299
|
+
min-width: 100%;
|
|
300
|
+
padding: 0.5rem 0;
|
|
301
|
+
margin: 0.125rem 0 0;
|
|
302
|
+
font-size: 1rem;
|
|
303
|
+
color: var(--text-color);
|
|
304
|
+
text-align: left;
|
|
305
|
+
list-style: none;
|
|
306
|
+
background-color: var(--form-control-bg-color);
|
|
307
|
+
background-clip: padding-box;
|
|
308
|
+
border: 1px solid var(--form-control-border-color);
|
|
309
|
+
border-radius: 0.375rem;
|
|
310
|
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.ultra-settings-search-container {
|
|
314
|
+
padding: 0.5rem 1rem;
|
|
315
|
+
border-bottom: 1px solid var(--form-control-border-color);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#config-search {
|
|
319
|
+
width: 100%;
|
|
320
|
+
padding: 0.375rem 0.75rem;
|
|
321
|
+
font-size: 0.875rem;
|
|
322
|
+
line-height: 1.5;
|
|
323
|
+
color: var(--form-control-color);
|
|
324
|
+
background-color: var(--value-bg-color);
|
|
325
|
+
background-clip: padding-box;
|
|
326
|
+
border: 1px solid var(--form-control-border-color);
|
|
327
|
+
border-radius: 0.25rem;
|
|
328
|
+
box-sizing: border-box;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.ultra-settings-dropdown-list {
|
|
332
|
+
list-style: none;
|
|
333
|
+
padding: 0;
|
|
334
|
+
margin: 0;
|
|
335
|
+
max-height: 300px;
|
|
336
|
+
overflow-y: auto;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.ultra-settings-dropdown-item {
|
|
340
|
+
display: flex;
|
|
341
|
+
align-items: center;
|
|
342
|
+
padding: 0.5rem 1rem;
|
|
343
|
+
clear: both;
|
|
344
|
+
font-weight: 400;
|
|
345
|
+
color: var(--form-control-color);
|
|
346
|
+
text-align: inherit;
|
|
347
|
+
white-space: nowrap;
|
|
348
|
+
background-color: transparent;
|
|
349
|
+
border: 0;
|
|
350
|
+
cursor: pointer;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.ultra-settings-dropdown-item:hover {
|
|
354
|
+
background-color: var(--field-header-bg-color);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.ultra-settings-check-icon {
|
|
358
|
+
width: 1.5rem;
|
|
359
|
+
display: inline-block;
|
|
360
|
+
text-align: center;
|
|
361
|
+
font-weight: bold;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.ultra-settings-dropdown-item.selected .ultra-settings-check-icon::before {
|
|
365
|
+
content: "✓";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.ultra-settings-title {
|
|
369
|
+
margin: 0;
|
|
370
|
+
font-size: 1.25rem;
|
|
371
|
+
font-weight: 500;
|
|
372
|
+
color: var(--text-color);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* Configuration List Styles */
|
|
376
|
+
.ultra-settings-configuration-list {
|
|
377
|
+
display: grid;
|
|
378
|
+
grid-template-columns: repeat(auto-fill, minmax(max(300px, 30%), 1fr));
|
|
379
|
+
gap: 1.5rem;
|
|
380
|
+
margin-top: 1.5rem;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.ultra-settings-configuration-summary {
|
|
384
|
+
background-color: var(--field-bg-color);
|
|
385
|
+
border: 1px solid var(--field-border-color);
|
|
386
|
+
border-radius: 0.5rem;
|
|
387
|
+
padding: 1.5rem;
|
|
388
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
389
|
+
overflow: hidden;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.ultra-settings-configuration-summary:hover {
|
|
393
|
+
transform: translateY(-2px);
|
|
394
|
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.ultra-settings-configuration-title {
|
|
398
|
+
display: block;
|
|
399
|
+
font-size: 1.25rem;
|
|
400
|
+
font-weight: 600;
|
|
401
|
+
color: var(--description-color);
|
|
402
|
+
text-decoration: none;
|
|
403
|
+
margin-bottom: 0.75rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.ultra-settings-configuration-title:hover {
|
|
407
|
+
text-decoration: underline;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.ultra-settings-configuration-summary p {
|
|
411
|
+
margin: 0;
|
|
412
|
+
color: var(--description-color);
|
|
413
|
+
line-height: 1.5;
|
|
276
414
|
}
|
data/app/application.js
CHANGED
|
@@ -1,41 +1,124 @@
|
|
|
1
1
|
document.addEventListener("DOMContentLoaded", () => {
|
|
2
|
-
const
|
|
2
|
+
const dropdown = document.getElementById("config-dropdown");
|
|
3
|
+
const button = document.getElementById("config-dropdown-button");
|
|
4
|
+
const menu = document.getElementById("config-dropdown-menu");
|
|
5
|
+
const searchInput = document.getElementById("config-search");
|
|
6
|
+
const items = document.querySelectorAll(".ultra-settings-dropdown-item");
|
|
7
|
+
const configurations = document.querySelectorAll(".ultra-settings-configuration");
|
|
8
|
+
const configList = document.querySelector(".ultra-settings-configuration-list");
|
|
3
9
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
// If no dropdown, we might be in single config mode or no configs.
|
|
11
|
+
if (!dropdown) {
|
|
12
|
+
// If there is exactly one configuration, show it.
|
|
13
|
+
if (configurations.length === 1) {
|
|
14
|
+
configurations[0].style.display = "block";
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
7
18
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
const toggleMenu = () => {
|
|
20
|
+
const isVisible = menu.style.display === "block";
|
|
21
|
+
menu.style.display = isVisible ? "none" : "block";
|
|
22
|
+
if (!isVisible) {
|
|
23
|
+
searchInput.value = "";
|
|
24
|
+
filterItems("");
|
|
25
|
+
searchInput.focus();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const closeMenu = () => {
|
|
30
|
+
menu.style.display = "none";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const filterItems = (query) => {
|
|
34
|
+
const lowerQuery = query.toLowerCase();
|
|
35
|
+
items.forEach(item => {
|
|
36
|
+
const label = item.getAttribute("data-search").toLowerCase();
|
|
37
|
+
if (label.includes(lowerQuery)) {
|
|
38
|
+
item.style.display = "flex";
|
|
12
39
|
} else {
|
|
13
|
-
|
|
40
|
+
item.style.display = "none";
|
|
14
41
|
}
|
|
15
42
|
});
|
|
16
|
-
}
|
|
43
|
+
};
|
|
17
44
|
|
|
18
|
-
|
|
45
|
+
const showConfigList = () => {
|
|
46
|
+
if (configList) configList.style.display = "grid";
|
|
47
|
+
configurations.forEach(config => config.style.display = "none");
|
|
48
|
+
items.forEach(item => item.classList.remove("selected"));
|
|
49
|
+
button.textContent = "Select Configuration";
|
|
50
|
+
closeMenu();
|
|
51
|
+
};
|
|
19
52
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
53
|
+
const showConfig = (configId) => {
|
|
54
|
+
if (configList) configList.style.display = "none";
|
|
55
|
+
|
|
56
|
+
configurations.forEach(config => {
|
|
57
|
+
config.style.display = config.id === configId ? "block" : "none";
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
items.forEach(item => {
|
|
61
|
+
if (item.getAttribute("data-value") === configId) {
|
|
62
|
+
item.classList.add("selected");
|
|
63
|
+
button.textContent = item.getAttribute("data-label");
|
|
64
|
+
} else {
|
|
65
|
+
item.classList.remove("selected");
|
|
27
66
|
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
closeMenu();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Event Listeners
|
|
73
|
+
button.addEventListener("click", (e) => {
|
|
74
|
+
e.stopPropagation();
|
|
75
|
+
toggleMenu();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
document.addEventListener("click", (e) => {
|
|
79
|
+
if (!dropdown.contains(e.target)) {
|
|
80
|
+
closeMenu();
|
|
28
81
|
}
|
|
82
|
+
});
|
|
29
83
|
|
|
30
|
-
|
|
31
|
-
|
|
84
|
+
searchInput.addEventListener("input", (e) => {
|
|
85
|
+
filterItems(e.target.value);
|
|
86
|
+
});
|
|
32
87
|
|
|
33
|
-
|
|
88
|
+
items.forEach(item => {
|
|
89
|
+
item.addEventListener("click", () => {
|
|
90
|
+
const configId = item.getAttribute("data-value");
|
|
91
|
+
const hash = configId.replace(/^config-/, "");
|
|
34
92
|
|
|
93
|
+
if (item.classList.contains("selected")) {
|
|
94
|
+
// Toggle off: clear hash
|
|
95
|
+
history.pushState("", document.title, window.location.pathname + window.location.search);
|
|
96
|
+
handleHashChange();
|
|
97
|
+
} else {
|
|
98
|
+
window.location.hash = hash;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
35
102
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
103
|
+
// Initial Load & Hash Change
|
|
104
|
+
const handleHashChange = () => {
|
|
105
|
+
const hash = window.location.hash.replace(/^#/, "");
|
|
106
|
+
if (hash) {
|
|
107
|
+
const configId = `config-${hash}`;
|
|
108
|
+
// Check if config exists
|
|
109
|
+
const exists = Array.from(items).some(item => item.getAttribute("data-value") === configId);
|
|
110
|
+
if (exists) {
|
|
111
|
+
showConfig(configId);
|
|
112
|
+
} else {
|
|
113
|
+
showConfigList();
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
showConfigList();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
39
121
|
|
|
40
|
-
|
|
122
|
+
// Run once on load
|
|
123
|
+
handleHashChange();
|
|
41
124
|
});
|