Some checks failed
Deploy Application / deploy (push) Has been cancelled
300 lines
10 KiB
JavaScript
300 lines
10 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<div class="merge-dialog__overlay"></div>
|
|
<div class="merge-dialog__content">
|
|
<h3>Merge Duplicate Assets</h3>
|
|
<p>Select which asset to keep. All other duplicates will be deleted.</p>
|
|
<div class="merge-dialog__assets" id="merge-assets-list"></div>
|
|
<div class="merge-dialog__actions">
|
|
<button class="btn btn--secondary" data-merge-cancel>Cancel</button>
|
|
<button class="btn btn--primary" data-merge-confirm disabled>Merge</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
const assetsList = dialog.querySelector('#merge-assets-list');
|
|
let selectedAssetId = null;
|
|
|
|
// Load asset details and create radio buttons
|
|
assetIds.forEach(assetId => {
|
|
const assetCard = document.querySelector(`[data-asset-id="${assetId}"]`)?.closest('.duplicate-asset-card');
|
|
if (!assetCard) return;
|
|
|
|
const assetIdEl = assetCard.querySelector('.asset-id code');
|
|
const assetBucket = assetCard.querySelector('.asset-bucket');
|
|
const assetPreview = assetCard.querySelector('.asset-preview-small')?.innerHTML;
|
|
|
|
const radio = document.createElement('div');
|
|
radio.className = 'merge-asset-option';
|
|
radio.innerHTML = `
|
|
<input type="radio" name="keep-asset" value="${assetId}" id="asset-${assetId}">
|
|
<label for="asset-${assetId}">
|
|
<div class="merge-asset-preview">${assetPreview || ''}</div>
|
|
<div class="merge-asset-info">
|
|
<strong>${assetIdEl?.textContent || assetId}</strong>
|
|
<small>${assetBucket?.textContent || ''}</small>
|
|
</div>
|
|
</label>
|
|
`;
|
|
|
|
radio.querySelector('input').addEventListener('change', (e) => {
|
|
if (e.target.checked) {
|
|
selectedAssetId = e.target.value;
|
|
dialog.querySelector('[data-merge-confirm]').disabled = false;
|
|
}
|
|
});
|
|
|
|
assetsList.appendChild(radio);
|
|
});
|
|
|
|
const cleanup = () => {
|
|
document.body.removeChild(dialog);
|
|
};
|
|
|
|
dialog.querySelector('[data-merge-confirm]').addEventListener('click', () => {
|
|
cleanup();
|
|
resolve(selectedAssetId);
|
|
});
|
|
|
|
dialog.querySelector('[data-merge-cancel]').addEventListener('click', () => {
|
|
cleanup();
|
|
resolve(null);
|
|
});
|
|
|
|
dialog.querySelector('.merge-dialog__overlay').addEventListener('click', () => {
|
|
cleanup();
|
|
resolve(null);
|
|
});
|
|
});
|
|
}
|
|
|
|
async mergeGroup(sha256, assetIds, keepAssetId, button) {
|
|
button.disabled = true;
|
|
button.textContent = 'Merging...';
|
|
|
|
try {
|
|
// Delete all assets except the one to keep
|
|
const deleteIds = assetIds.filter(id => id !== keepAssetId);
|
|
const deletePromises = deleteIds.map(id =>
|
|
fetch(`/admin/api/assets/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
})
|
|
);
|
|
|
|
await Promise.all(deletePromises);
|
|
|
|
if (window.Toast) {
|
|
window.Toast.success(`Merged ${deleteIds.length} duplicates into one asset`);
|
|
}
|
|
|
|
// Remove group card
|
|
button.closest('.duplicate-group')?.remove();
|
|
} catch (error) {
|
|
console.error('Failed to merge group:', error);
|
|
if (window.Toast) {
|
|
window.Toast.error(`Failed to merge group: ${error.message}`);
|
|
} else {
|
|
alert(`Failed to merge group: ${error.message}`);
|
|
}
|
|
} finally {
|
|
button.disabled = false;
|
|
button.textContent = 'Merge Group';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Auto-initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (document.querySelector('.duplicates-list')) {
|
|
new DuplicateManagement();
|
|
}
|
|
});
|
|
|