fix: improve command palette actions and automation condition button
All checks were successful
Lint & Test / test (push) Successful in 1m16s

- Action items (start/stop, enable/disable) no longer close the palette
- Action items toggle state after success (Start→Stop, Enable→Disable)
- Toast z-index raised above command palette backdrop (3000→3500)
- Automation condition remove button uses ICON_TRASH SVG instead of ✕
This commit is contained in:
2026-03-26 02:21:52 +03:00
parent 3e0bf8538c
commit c0853ce184
3 changed files with 50 additions and 42 deletions

View File

@@ -49,7 +49,7 @@
--z-log-overlay: 2100;
--z-confirm: 2500;
--z-command-palette: 3000;
--z-toast: 3000;
--z-toast: 3500;
--z-overlay-spinner: 9999;
--z-lightbox: 10000;
--z-connection: 10000;

View File

@@ -57,26 +57,31 @@ function _buildItems(results: any[], states: any = {}) {
nav: ['targets', 'led-targets', 'led-targets', 'data-target-id', tgt.id], running,
});
}
// Action items: start or stop
if (running) {
items.push({
name: tgt.name, detail: t('search.action.stop'), group: 'actions', icon: '■',
// Action item: toggle start/stop
const actionItem: any = {
name: tgt.name, group: 'actions',
detail: running ? t('search.action.stop') : t('search.action.start'),
icon: running ? '■' : '▶',
_running: running, _targetId: tgt.id,
action: async () => {
const resp = await fetchWithAuth(`/output-targets/${tgt.id}/stop`, { method: 'POST' });
if (resp.ok) { showToast(t('device.stopped'), 'success'); }
else { const err = await resp.json().catch(() => ({})); const d = err.detail || err.message || ''; const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d); showToast(ds || t('target.error.stop_failed'), 'error'); }
},
});
const isRunning = actionItem._running;
const endpoint = isRunning ? 'stop' : 'start';
const resp = await fetchWithAuth(`/output-targets/${tgt.id}/${endpoint}`, { method: 'POST' });
if (resp.ok) {
showToast(t(isRunning ? 'device.stopped' : 'device.started'), 'success');
actionItem._running = !isRunning;
actionItem.detail = !isRunning ? t('search.action.stop') : t('search.action.start');
actionItem.icon = !isRunning ? '■' : '▶';
_render();
} else {
items.push({
name: tgt.name, detail: t('search.action.start'), group: 'actions', icon: '',
action: async () => {
const resp = await fetchWithAuth(`/output-targets/${tgt.id}/start`, { method: 'POST' });
if (resp.ok) { showToast(t('device.started'), 'success'); }
else { const err = await resp.json().catch(() => ({})); const d = err.detail || err.message || ''; const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d); showToast(ds || t('target.error.start_failed'), 'error'); }
},
});
const err = await resp.json().catch(() => ({}));
const d = err.detail || err.message || '';
const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d);
showToast(ds || t(`target.error.${endpoint}_failed`), 'error');
}
},
};
items.push(actionItem);
});
_mapEntities(css, c => items.push({
@@ -89,25 +94,28 @@ function _buildItems(results: any[], states: any = {}) {
name: a.name, detail: a.enabled ? 'enabled' : '', group: 'automations', icon: ICON_AUTOMATION,
nav: ['automations', null, 'automations', 'data-automation-id', a.id],
});
if (a.enabled) {
items.push({
name: a.name, detail: t('search.action.disable'), group: 'actions', icon: ICON_AUTOMATION,
const autoItem: any = {
name: a.name, group: 'actions', icon: ICON_AUTOMATION,
detail: a.enabled ? t('search.action.disable') : t('search.action.enable'),
_enabled: a.enabled,
action: async () => {
const resp = await fetchWithAuth(`/automations/${a.id}/disable`, { method: 'POST' });
if (resp.ok) { showToast(t('search.action.disable') + ': ' + a.name, 'success'); }
else { const err = await resp.json().catch(() => ({})); const d = err.detail || err.message || ''; const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d); showToast(ds || (t('search.action.disable') + ' failed'), 'error'); }
},
});
const isEnabled = autoItem._enabled;
const endpoint = isEnabled ? 'disable' : 'enable';
const resp = await fetchWithAuth(`/automations/${a.id}/${endpoint}`, { method: 'POST' });
if (resp.ok) {
showToast(t('search.action.' + endpoint) + ': ' + a.name, 'success');
autoItem._enabled = !isEnabled;
autoItem.detail = !isEnabled ? t('search.action.disable') : t('search.action.enable');
_render();
} else {
items.push({
name: a.name, detail: t('search.action.enable'), group: 'actions', icon: ICON_AUTOMATION,
action: async () => {
const resp = await fetchWithAuth(`/automations/${a.id}/enable`, { method: 'POST' });
if (resp.ok) { showToast(t('search.action.enable') + ': ' + a.name, 'success'); }
else { const err = await resp.json().catch(() => ({})); const d = err.detail || err.message || ''; const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d); showToast(ds || (t('search.action.enable') + ' failed'), 'error'); }
},
});
const err = await resp.json().catch(() => ({}));
const d = err.detail || err.message || '';
const ds = Array.isArray(d) ? d.map(x => x.msg || x).join('; ') : String(d);
showToast(ds || (t('search.action.' + endpoint) + ' failed'), 'error');
}
},
};
items.push(autoItem);
});
_mapEntities(capTempl, ct => items.push({
@@ -378,13 +386,13 @@ function _onClick(e: Event) {
function _selectCurrent() {
if (_selectedIdx < 0 || _selectedIdx >= _filtered.length) return;
const item = _filtered[_selectedIdx];
closeCommandPalette();
if (item.action) {
item.action().catch(err => {
if (!err.isAuth) showToast(err.message || 'Action failed', 'error');
});
return;
}
closeCommandPalette();
// If graph tab is active, navigate to graph node instead of card
const graphTabActive = document.querySelector('.tab-btn[data-tab="graph"].active');
if (graphTabActive) {

View File

@@ -610,7 +610,7 @@ function addAutomationConditionRow(condition: any) {
<select class="condition-type-select">
${CONDITION_TYPE_KEYS.map(k => `<option value="${k}" ${condType === k ? 'selected' : ''}>${t('automations.condition.' + k)}</option>`).join('')}
</select>
<button type="button" class="btn-remove-condition" onclick="this.closest('.automation-condition-row').remove(); if(window._autoGenerateAutomationName) window._autoGenerateAutomationName();" title="Remove">&#x2715;</button>
<button type="button" class="btn-remove-condition" onclick="this.closest('.automation-condition-row').remove(); if(window._autoGenerateAutomationName) window._autoGenerateAutomationName();" title="Remove">${ICON_TRASH}</button>
</div>
<div class="condition-fields-container"></div>
`;