Add clone support for scene and automation cards, update sync clock descriptions

- Scene clone: opens capture modal with prefilled name/description/targets
  instead of server-side duplication; removed backend clone endpoint
- Automation clone: opens editor with prefilled conditions, scene, logic,
  deactivation mode (webhook tokens stripped for uniqueness)
- Updated sync clock i18n descriptions to reflect speed-only-on-clock model
- Added entity card clone pattern documentation to server/CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 22:47:11 +03:00
parent bc5d8fdc9b
commit a330a8c0f0
9 changed files with 115 additions and 65 deletions
@@ -308,21 +308,49 @@ export async function recaptureScenePreset(presetId) {
// ===== Clone =====
export async function cloneScenePreset(presetId) {
try {
const resp = await fetchWithAuth(`/scene-presets/${presetId}/clone`, {
method: 'POST',
});
if (resp.ok) {
showToast(t('scenes.cloned'), 'success');
_reloadScenesTab();
} else {
const err = await resp.json().catch(() => ({}));
showToast(err.detail || t('scenes.error.clone_failed'), 'error');
}
} catch (error) {
if (error.isAuth) return;
showToast(t('scenes.error.clone_failed'), 'error');
const preset = scenePresetsCache.data.find(p => p.id === presetId);
if (!preset) return;
// Open the capture modal in create mode, prefilled from the cloned preset
_editingId = null;
document.getElementById('scene-preset-editor-id').value = '';
document.getElementById('scene-preset-editor-name').value = (preset.name || '') + ' (Copy)';
document.getElementById('scene-preset-editor-description').value = preset.description || '';
document.getElementById('scene-preset-editor-error').style.display = 'none';
const titleEl = document.querySelector('#scene-preset-editor-title span[data-i18n]');
if (titleEl) { titleEl.setAttribute('data-i18n', 'scenes.add'); titleEl.textContent = t('scenes.add'); }
// Fetch targets and populate selector, then pre-add the cloned preset's targets
const selectorGroup = document.getElementById('scene-target-selector-group');
const targetList = document.getElementById('scene-target-list');
if (selectorGroup && targetList) {
selectorGroup.style.display = '';
targetList.innerHTML = '';
try {
const resp = await fetchWithAuth('/picture-targets');
if (resp.ok) {
const data = await resp.json();
_allTargets = data.targets || [];
// Pre-add targets from the cloned preset
const clonedTargetIds = (preset.targets || []).map(pt => pt.target_id || pt.id);
for (const tid of clonedTargetIds) {
const tgt = _allTargets.find(t => t.id === tid);
if (!tgt) continue;
const item = document.createElement('div');
item.className = 'scene-target-item';
item.dataset.targetId = tid;
item.innerHTML = `<span>${escapeHtml(tgt.name)}</span><button type="button" class="btn-remove-condition" onclick="removeSceneTarget(this)" title="Remove">&#x2715;</button>`;
targetList.appendChild(item);
}
_refreshTargetSelect();
}
} catch { /* ignore */ }
}
scenePresetModal.open();
scenePresetModal.snapshot();
}
// ===== Delete =====