command_deck 0.3.1 → 0.3.2
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 +19 -0
- data/README.md +21 -0
- data/app/controllers/command_deck/panels_controller.rb +2 -1
- data/lib/command_deck/assets/css/main.css +31 -2
- data/lib/command_deck/assets/js/core/dom.js +5 -0
- data/lib/command_deck/assets/js/ui/overlay/action_form.js +41 -9
- data/lib/command_deck/assets/js/ui/panel_selector.js +11 -4
- data/lib/command_deck/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebceefafcd11660d8efdd358ad30b1d779b236d1cd135286b9fdb79871dd11bf
|
4
|
+
data.tar.gz: 9d4bffc3981b9959dfd491d4d17a5f1e8e89465f54c461d20ffa63eb9b62328a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ea6c387e844f38d2cbdf0772b53979df7c93be42b8626f7be1120e61e9a052ae42fa0bb53bab411af1572723ce6824743137f16e1bf20ce040951c06ec7eda2
|
7
|
+
data.tar.gz: 11969e5bff8030f91e81cd4a6ddf466fea747d35843da09661377e17f835d847b3dc9c185be941da7ca63597f88877965e6748c4b38214ec71dadfa8588dccf8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## [0.3.2] - 2025-10-15
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- **Auto-populate feature**: New `auto_populate: true` option for params that automatically fills text/number inputs with values from selector meta data
|
8
|
+
- **Dark mode support**: Panel selector now properly respects dark theme settings
|
9
|
+
- **Form accessibility improvements**: All form inputs now have proper `id`, `name`, `for`, and `autocomplete` attributes for better accessibility and browser compatibility
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
- **Dropdown text overflow**: Long labels in selectors are now properly truncated (50 chars for action params, 35 chars for panel selector) with full text shown on hover
|
14
|
+
- **Select2 conflict prevention**: Added `data-select-type="default"` to prevent third-party select enhancement libraries from interfering with Command Deck selectors
|
15
|
+
- **CSS cascade issues**: Fixed dark mode styles not applying due to CSS specificity problems
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
- **DRY code improvements**: Extracted label truncation logic into shared `truncateLabel()` utility function in `core/dom.js`
|
20
|
+
- Better hint display: "Current:" hints now only show for boolean values (ON/OFF badges), while string values are populated directly in inputs
|
21
|
+
|
3
22
|
## [0.3.1] - 2025-10-10
|
4
23
|
|
5
24
|
### Fixed
|
data/README.md
CHANGED
@@ -70,6 +70,26 @@ module CommandDeck::Panels
|
|
70
70
|
{ chosen: p[:color] }
|
71
71
|
end
|
72
72
|
end
|
73
|
+
|
74
|
+
action 'Update Setting', key: 'utils.update_setting' do
|
75
|
+
# Selector with current values in meta
|
76
|
+
param :setting_name, :selector,
|
77
|
+
collection: -> {
|
78
|
+
# Example: fetch settings with current values
|
79
|
+
[
|
80
|
+
{ label: 'Max Users (100)', value: 'max_users', meta: { value: 100 } },
|
81
|
+
{ label: 'Timeout (30)', value: 'timeout', meta: { value: 30 } }
|
82
|
+
]
|
83
|
+
}
|
84
|
+
|
85
|
+
# Auto-populate this input with the value from meta
|
86
|
+
param :new_value, :string, required: true, auto_populate: true
|
87
|
+
|
88
|
+
perform do |p, _ctx|
|
89
|
+
# Update the setting with new value
|
90
|
+
{ ok: true, setting: p[:setting_name], new_value: p[:new_value] }
|
91
|
+
end
|
92
|
+
end
|
73
93
|
end
|
74
94
|
end
|
75
95
|
end
|
@@ -134,6 +154,7 @@ Common param options:
|
|
134
154
|
|
135
155
|
- `label:` String – UI label. Defaults to a humanized `name`.
|
136
156
|
- `required:` true/false – disables Run button until filled. Default: false.
|
157
|
+
- `auto_populate:` true/false – for text/number inputs, automatically populate with value from previous selector's `meta: { value: ... }`. Default: false.
|
137
158
|
|
138
159
|
Selector-specific options:
|
139
160
|
|
@@ -65,7 +65,8 @@ module CommandDeck
|
|
65
65
|
name: param[:name],
|
66
66
|
type: param[:type].to_s,
|
67
67
|
label: param[:label] || param[:name].to_s.tr("_", " ").capitalize,
|
68
|
-
required: param.key?(:required) ? param[:required] : false
|
68
|
+
required: param.key?(:required) ? param[:required] : false,
|
69
|
+
auto_populate: param.key?(:auto_populate) ? param[:auto_populate] : false
|
69
70
|
}
|
70
71
|
end
|
71
72
|
|
@@ -56,8 +56,7 @@
|
|
56
56
|
#command-deck-panel.cd-theme-dark .cd-menu-item,
|
57
57
|
#command-deck-panel.cd-theme-dark .cd-form input[type="text"],
|
58
58
|
#command-deck-panel.cd-theme-dark .cd-form input[type="number"],
|
59
|
-
#command-deck-panel.cd-theme-dark .cd-form select
|
60
|
-
#command-deck-panel.cd-theme-dark #cd-panel-selector select {
|
59
|
+
#command-deck-panel.cd-theme-dark .cd-form select {
|
61
60
|
background: #111; /* near-black */
|
62
61
|
color: #eee;
|
63
62
|
border-color: #333;
|
@@ -144,6 +143,22 @@
|
|
144
143
|
border: 1px solid #ddd;
|
145
144
|
border-radius: 6px;
|
146
145
|
background: #fff;
|
146
|
+
max-width: 200px;
|
147
|
+
overflow: hidden;
|
148
|
+
text-overflow: ellipsis;
|
149
|
+
white-space: nowrap;
|
150
|
+
}
|
151
|
+
|
152
|
+
#cd-panel-selector select option {
|
153
|
+
overflow: hidden;
|
154
|
+
text-overflow: ellipsis;
|
155
|
+
white-space: nowrap;
|
156
|
+
}
|
157
|
+
|
158
|
+
#command-deck-panel.cd-theme-dark #cd-panel-selector select {
|
159
|
+
background: #111;
|
160
|
+
color: #eee;
|
161
|
+
border-color: #333;
|
147
162
|
}
|
148
163
|
|
149
164
|
#cd-settings-panel {
|
@@ -247,6 +262,20 @@
|
|
247
262
|
border-radius: 6px;
|
248
263
|
}
|
249
264
|
|
265
|
+
.cd-form select {
|
266
|
+
max-width: 100%;
|
267
|
+
overflow: hidden;
|
268
|
+
text-overflow: ellipsis;
|
269
|
+
white-space: nowrap;
|
270
|
+
}
|
271
|
+
|
272
|
+
.cd-form select option {
|
273
|
+
overflow: hidden;
|
274
|
+
text-overflow: ellipsis;
|
275
|
+
white-space: nowrap;
|
276
|
+
max-width: 100%;
|
277
|
+
}
|
278
|
+
|
250
279
|
.cd-actions-row {
|
251
280
|
display: flex;
|
252
281
|
justify-content: flex-end;
|
@@ -33,3 +33,8 @@ export function el(tag, attrs, children) {
|
|
33
33
|
export function jsonPretty(obj) {
|
34
34
|
try { return JSON.stringify(obj, null, 2); } catch(_) { return String(obj); }
|
35
35
|
}
|
36
|
+
|
37
|
+
export function truncateLabel(text, maxLength = 50) {
|
38
|
+
const str = String(text);
|
39
|
+
return str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
|
40
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { el } from '../../core/dom.js';
|
1
|
+
import { el, truncateLabel } from '../../core/dom.js';
|
2
2
|
import { store } from '../../core/store.js';
|
3
3
|
|
4
4
|
export class ActionForm {
|
@@ -22,26 +22,30 @@ export class ActionForm {
|
|
22
22
|
const inputs = {};
|
23
23
|
const paramsMeta = {};
|
24
24
|
|
25
|
-
(this.action.params || []).forEach(p => {
|
25
|
+
(this.action.params || []).forEach((p, paramIndex) => {
|
26
|
+
const inputId = `cd-input-${this.action.key}-${p.name}-${paramIndex}`;
|
26
27
|
const labelChildren = [p.label || p.name];
|
27
28
|
if (p.required === true) {
|
28
29
|
labelChildren.push(el('span', { class: 'cd-required', title: 'Required' }, ['*']));
|
29
30
|
}
|
30
|
-
const label = el('label',
|
31
|
+
const label = el('label', { for: inputId }, labelChildren);
|
31
32
|
let input;
|
32
33
|
if (p.type === 'boolean') {
|
33
|
-
input = el('input', { type: 'checkbox' });
|
34
|
+
input = el('input', { type: 'checkbox', id: inputId, name: p.name, autocomplete: 'off' });
|
34
35
|
} else if (p.type === 'integer') {
|
35
|
-
input = el('input', { type: 'number', step: '1' });
|
36
|
+
input = el('input', { type: 'number', step: '1', id: inputId, name: p.name, autocomplete: 'off' });
|
36
37
|
} else if (p.type === 'selector' || (p.choices && p.choices.length)) {
|
37
|
-
input = el('select');
|
38
|
+
input = el('select', { 'data-select-type': 'default', id: inputId, name: p.name, autocomplete: 'off' });
|
38
39
|
if (p.include_blank) {
|
39
40
|
input.appendChild(el('option', { value: '' }, ['']));
|
40
41
|
}
|
41
42
|
(p.choices || []).forEach(ch => {
|
42
43
|
const attrs = { 'data-val': JSON.stringify(ch.value) };
|
43
44
|
if (ch.meta != null) attrs['data-meta'] = JSON.stringify(ch.meta);
|
44
|
-
const
|
45
|
+
const fullLabel = ch.label != null ? String(ch.label) : String(ch.value);
|
46
|
+
const displayLabel = truncateLabel(fullLabel, 50);
|
47
|
+
attrs['title'] = fullLabel;
|
48
|
+
const opt = el('option', attrs, [displayLabel]);
|
45
49
|
input.appendChild(opt);
|
46
50
|
});
|
47
51
|
|
@@ -65,7 +69,7 @@ export class ActionForm {
|
|
65
69
|
}
|
66
70
|
}
|
67
71
|
} else {
|
68
|
-
input = el('input', { type: 'text' });
|
72
|
+
input = el('input', { type: 'text', id: inputId, name: p.name, autocomplete: 'off' });
|
69
73
|
}
|
70
74
|
if (p.required === true && input) {
|
71
75
|
input.setAttribute('aria-required', 'true');
|
@@ -94,15 +98,43 @@ export class ActionForm {
|
|
94
98
|
}
|
95
99
|
} catch(_) { hint.textContent = ''; }
|
96
100
|
};
|
101
|
+
|
102
|
+
const populateRelatedInput = () => {
|
103
|
+
const sel = input.options[input.selectedIndex];
|
104
|
+
if (!sel) return;
|
105
|
+
const metaRaw = sel.getAttribute('data-meta');
|
106
|
+
if (!metaRaw) return;
|
107
|
+
try {
|
108
|
+
const meta = JSON.parse(metaRaw);
|
109
|
+
if (meta.value != null && typeof meta.enabled !== 'boolean') {
|
110
|
+
// Find the next string/number input after this selector
|
111
|
+
const currentParamIndex = (this.action.params || []).findIndex(param => param.name === p.name);
|
112
|
+
if (currentParamIndex >= 0 && currentParamIndex < (this.action.params || []).length - 1) {
|
113
|
+
const nextParam = this.action.params[currentParamIndex + 1];
|
114
|
+
// Only auto-populate if the next param has auto_populate explicitly enabled (defaults to false)
|
115
|
+
const shouldAutoPopulate = nextParam.auto_populate === true;
|
116
|
+
const nextInput = inputs[nextParam.name];
|
117
|
+
if (shouldAutoPopulate && nextInput && (nextInput.type === 'text' || nextInput.type === 'number')) {
|
118
|
+
nextInput.value = String(meta.value);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
} catch(_) { }
|
123
|
+
};
|
124
|
+
|
97
125
|
input.addEventListener('change', () => {
|
98
126
|
const sel = input.options[input.selectedIndex];
|
99
127
|
const raw = sel ? sel.getAttribute('data-val') : '';
|
100
128
|
const selKey = `sel:${this.getCurrentPanelKey() || ''}:${this.action.key}:${p.name}`;
|
101
129
|
store.set(selKey, raw || '');
|
102
130
|
updateHint();
|
131
|
+
populateRelatedInput();
|
103
132
|
validate();
|
104
133
|
});
|
105
|
-
setTimeout(
|
134
|
+
setTimeout(() => {
|
135
|
+
updateHint();
|
136
|
+
populateRelatedInput();
|
137
|
+
}, 0);
|
106
138
|
form.appendChild(hint);
|
107
139
|
}
|
108
140
|
|
@@ -1,10 +1,15 @@
|
|
1
|
-
import { el } from '../core/dom.js';
|
1
|
+
import { el, truncateLabel } from '../core/dom.js';
|
2
2
|
|
3
3
|
export class PanelSelector {
|
4
4
|
constructor(onSelect) {
|
5
5
|
this.onSelect = onSelect;
|
6
6
|
this.root = el('div', { id: 'cd-panel-selector' });
|
7
|
-
this.select = el('select'
|
7
|
+
this.select = el('select', {
|
8
|
+
'data-select-type': 'default',
|
9
|
+
id: 'cd-panel-select',
|
10
|
+
name: 'panel',
|
11
|
+
autocomplete: 'off'
|
12
|
+
});
|
8
13
|
this.root.appendChild(this.select);
|
9
14
|
|
10
15
|
this.select.addEventListener('change', () => {
|
@@ -19,8 +24,10 @@ export class PanelSelector {
|
|
19
24
|
// panels: [{ key, title, owner, group }]
|
20
25
|
this.select.textContent = '';
|
21
26
|
(panels || []).forEach(p => {
|
22
|
-
const
|
23
|
-
const
|
27
|
+
const fullLabel = [p.group, p.title].filter(Boolean).join(' • ');
|
28
|
+
const label = fullLabel || p.title || p.key;
|
29
|
+
const displayLabel = truncateLabel(label, 35);
|
30
|
+
const opt = el('option', { value: p.key, title: label }, [displayLabel]);
|
24
31
|
this.select.appendChild(opt);
|
25
32
|
});
|
26
33
|
}
|
data/lib/command_deck/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: command_deck
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- crowrojas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-10-
|
11
|
+
date: 2025-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|