Add stop-all buttons to target sections, perf chart color reset, and TODO

- Add stop-all buttons to LED targets and KC targets section headers
  (visible only when targets are running, uses headerExtra on CardSection)
- Add reset ability to performance chart color pickers (removes custom
  color from localStorage and reverts to default)
- Remove CODEBASE_REVIEW.md
- Add prioritized TODO.md with P1/P2/P3 feature roadmap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 01:46:58 +03:00
parent 90acae5207
commit 62b3d44e63
10 changed files with 125 additions and 63 deletions

View File

@@ -33,8 +33,8 @@ import { updateSubTabHash, updateTabBadge } from './tabs.js';
// ── Card section instances ──
const csDevices = new CardSection('led-devices', { titleKey: 'targets.section.devices', gridClass: 'devices-grid', addCardOnclick: "showAddDevice()", keyAttr: 'data-device-id' });
const csColorStrips = new CardSection('led-css', { titleKey: 'targets.section.color_strips', gridClass: 'devices-grid', addCardOnclick: "showCSSEditor()", keyAttr: 'data-css-id' });
const csLedTargets = new CardSection('led-targets', { titleKey: 'targets.section.targets', gridClass: 'devices-grid', addCardOnclick: "showTargetEditor()", keyAttr: 'data-target-id' });
const csKCTargets = new CardSection('kc-targets', { titleKey: 'targets.section.key_colors', gridClass: 'devices-grid', addCardOnclick: "showKCEditor()", keyAttr: 'data-kc-target-id' });
const csLedTargets = new CardSection('led-targets', { titleKey: 'targets.section.targets', gridClass: 'devices-grid', addCardOnclick: "showTargetEditor()", keyAttr: 'data-target-id', headerExtra: `<button class="btn btn-sm btn-danger" onclick="event.stopPropagation(); stopAllLedTargets()" data-stop-all="led">${ICON_STOP}</button>` });
const csKCTargets = new CardSection('kc-targets', { titleKey: 'targets.section.key_colors', gridClass: 'devices-grid', addCardOnclick: "showKCEditor()", keyAttr: 'data-kc-target-id', headerExtra: `<button class="btn btn-sm btn-danger" onclick="event.stopPropagation(); stopAllKCTargets()" data-stop-all="kc">${ICON_STOP}</button>` });
const csPatternTemplates = new CardSection('kc-patterns', { titleKey: 'targets.section.pattern_templates', gridClass: 'templates-grid', addCardOnclick: "showPatternTemplateEditor()", keyAttr: 'data-pattern-template-id' });
// Re-render targets tab when language changes (only if tab is active)
@@ -621,6 +621,14 @@ export async function loadTargetsTab() {
CardSection.bindAll([csDevices, csColorStrips, csLedTargets, csKCTargets, csPatternTemplates]);
}
// Show/hide stop-all buttons based on running state
const ledRunning = ledTargets.some(t => t.state && t.state.processing);
const kcRunning = kcTargets.some(t => t.state && t.state.processing);
const ledStopBtn = container.querySelector('[data-stop-all="led"]');
const kcStopBtn = container.querySelector('[data-stop-all="kc"]');
if (ledStopBtn) ledStopBtn.style.display = ledRunning ? '' : 'none';
if (kcStopBtn) kcStopBtn.style.display = kcRunning ? '' : 'none';
// Patch volatile metrics in-place (avoids full card replacement on polls)
for (const tgt of ledTargets) {
if (tgt.state && tgt.state.processing) _patchTargetMetrics(tgt);
@@ -983,6 +991,40 @@ export async function stopTargetProcessing(targetId) {
});
}
export async function stopAllLedTargets() {
await _stopAllByType('led');
}
export async function stopAllKCTargets() {
await _stopAllByType('key_colors');
}
async function _stopAllByType(targetType) {
try {
const [targetsResp, statesResp] = await Promise.all([
fetchWithAuth('/picture-targets'),
fetchWithAuth('/picture-targets/batch/states'),
]);
const data = await targetsResp.json();
const statesData = statesResp.ok ? await statesResp.json() : { states: {} };
const states = statesData.states || {};
const typeMatch = targetType === 'led' ? t => t.target_type === 'led' || t.target_type === 'wled' : t => t.target_type === targetType;
const running = (data.targets || []).filter(t => typeMatch(t) && states[t.id]?.processing);
if (!running.length) {
showToast(t('targets.stop_all.none_running'), 'info');
return;
}
await Promise.all(running.map(t =>
fetchWithAuth(`/picture-targets/${t.id}/stop`, { method: 'POST' }).catch(() => {})
));
showToast(t('targets.stop_all.stopped', { count: running.length }), 'success');
loadTargetsTab();
} catch (error) {
if (error.isAuth) return;
showToast(t('targets.stop_all.error'), 'error');
}
}
export async function startTargetOverlay(targetId) {
await _targetAction(async () => {
const response = await fetchWithAuth(`/picture-targets/${targetId}/overlay/start`, {