/** * Duplicate Management Module * * Handles duplicate asset management (merge, delete) */ export class DuplicateManagement { constructor() { this.init(); } init() { this.setupDeleteDuplicate(); this.setupDeleteGroup(); this.setupMergeGroup(); this.setupRefresh(); } setupDeleteDuplicate() { document.addEventListener('click', async (e) => { const button = e.target.closest('[data-delete-duplicate]'); if (!button) return; e.preventDefault(); const assetId = button.dataset.assetId; if (!confirm(`Are you sure you want to delete this duplicate asset?`)) { return; } await this.deleteAsset(assetId, button); }); } setupDeleteGroup() { document.addEventListener('click', async (e) => { const button = e.target.closest('[data-delete-group]'); if (!button) return; e.preventDefault(); const sha256 = button.dataset.sha256; const groupCard = button.closest('.duplicate-group'); const assetCount = groupCard?.querySelectorAll('.duplicate-asset-card').length || 0; if (!confirm(`Are you sure you want to delete all ${assetCount} duplicate assets in this group?`)) { return; } await this.deleteGroup(sha256, button); }); } setupMergeGroup() { document.addEventListener('click', async (e) => { const button = e.target.closest('[data-merge-group]'); if (!button) return; e.preventDefault(); const sha256 = button.dataset.sha256; const groupCard = button.closest('.duplicate-group'); const assets = Array.from(groupCard?.querySelectorAll('[data-asset-id]') || []) .map(el => el.dataset.assetId); if (assets.length < 2) { if (window.Toast) { window.Toast.warning('Need at least 2 assets to merge'); } else { alert('Need at least 2 assets to merge'); } return; } // Show merge dialog const keepAssetId = await this.showMergeDialog(assets); if (!keepAssetId) { return; } await this.mergeGroup(sha256, assets, keepAssetId, button); }); } setupRefresh() { document.addEventListener('click', (e) => { const button = e.target.closest('[data-refresh-duplicates]'); if (!button) return; e.preventDefault(); window.location.reload(); }); } async deleteAsset(assetId, button) { const originalText = button.textContent; button.disabled = true; button.textContent = 'Deleting...'; try { const response = await fetch(`/admin/api/assets/${assetId}`, { method: 'DELETE', headers: { 'X-Requested-With': 'XMLHttpRequest', }, }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } if (window.Toast) { window.Toast.success('Duplicate asset deleted'); } // Remove asset card button.closest('.duplicate-asset-card')?.remove(); // Check if group is now empty or has only one asset const groupCard = button.closest('.duplicate-group'); const remainingAssets = groupCard?.querySelectorAll('.duplicate-asset-card').length || 0; if (remainingAssets <= 1) { groupCard?.remove(); } } catch (error) { console.error('Failed to delete asset:', error); if (window.Toast) { window.Toast.error(`Failed to delete asset: ${error.message}`); } else { alert(`Failed to delete asset: ${error.message}`); } } finally { button.disabled = false; button.textContent = originalText; } } async deleteGroup(sha256, button) { const groupCard = button.closest('.duplicate-group'); const assetCards = groupCard?.querySelectorAll('.duplicate-asset-card') || []; const assetIds = Array.from(assetCards).map(card => card.querySelector('[data-asset-id]')?.dataset.assetId ).filter(Boolean); button.disabled = true; button.textContent = 'Deleting...'; try { // Delete all assets in parallel const deletePromises = assetIds.map(id => fetch(`/admin/api/assets/${id}`, { method: 'DELETE', headers: { 'X-Requested-With': 'XMLHttpRequest', }, }) ); await Promise.all(deletePromises); if (window.Toast) { window.Toast.success(`Deleted ${assetIds.length} duplicate assets`); } // Remove group card groupCard?.remove(); } catch (error) { console.error('Failed to delete group:', error); if (window.Toast) { window.Toast.error(`Failed to delete group: ${error.message}`); } else { alert(`Failed to delete group: ${error.message}`); } } finally { button.disabled = false; button.textContent = 'Delete All'; } } async showMergeDialog(assetIds) { return new Promise((resolve) => { const dialog = document.createElement('div'); dialog.className = 'merge-dialog'; dialog.innerHTML = `
Select which asset to keep. All other duplicates will be deleted.