added asset manegement

This commit is contained in:
2026-03-27 14:27:01 +01:00
parent 8e10cb9d17
commit d7e44a84d5
6 changed files with 279 additions and 151 deletions
+178
View File
@@ -0,0 +1,178 @@
---
interface Props {
mode?: 'select' | 'manage';
}
const { mode = 'manage' } = Astro.props;
---
<div class="space-y-8">
<div id="asset-alert" class="hidden p-4 rounded-lg mb-6 text-sm"></div>
<!-- Upload Zone -->
<div class="glass p-6 border-dashed border-2 border-surface1 hover:border-mauve transition-colors group relative">
<input type="file" id="zone-file-upload" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" multiple />
<div class="text-center py-4">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-4 text-subtext0 group-hover:text-mauve transition-colors"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>
<p class="text-lg font-bold text-lavender">Click or drag to upload assets</p>
<p class="text-xs text-subtext0 mt-1">Any file type up to 50MB</p>
</div>
</div>
<!-- Assets Grid -->
<div id="manager-assets-grid" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-4">
<!-- Assets injected here -->
</div>
<div id="manager-assets-empty" class="hidden text-center py-20 text-subtext0">
No assets uploaded yet.
</div>
</div>
<script is:inline define:vars={{ mode }}>
const token = localStorage.getItem('admin_token');
const grid = document.getElementById('manager-assets-grid');
const empty = document.getElementById('manager-assets-empty');
const fileInput = document.getElementById('zone-file-upload');
const alertEl = document.getElementById('asset-alert');
let allAssets = [];
async function fetchAssets() {
try {
const res = await fetch('/api/uploads', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
allAssets = await res.json();
renderAssets();
}
} catch (e) {
console.error("Failed to fetch assets", e);
}
}
function showLocalAlert(msg, type) {
if (!alertEl) return;
alertEl.textContent = msg;
alertEl.className = `p-4 rounded-lg mb-6 text-sm ${type === 'success' ? 'bg-green/20 text-green border border-green/30' : 'bg-red/20 text-red border border-red/30'}`;
alertEl.classList.remove('hidden');
setTimeout(() => alertEl.classList.add('hidden'), 4000);
}
async function uploadFiles(files) {
let successCount = 0;
for (const file of files) {
const formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/upload', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: formData
});
if (res.ok) successCount++;
} catch (e) {}
}
if (successCount > 0) {
showLocalAlert(`Successfully uploaded ${successCount} file(s).`, 'success');
fetchAssets();
}
}
async function deleteAsset(filename) {
if (!confirm(`Delete "${filename}" permanently?`)) return;
try {
const res = await fetch(`/api/uploads/${encodeURIComponent(filename)}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) {
showLocalAlert('File deleted.', 'success');
fetchAssets();
} else {
showLocalAlert('Failed to delete file.', 'error');
}
} catch (e) {
showLocalAlert('Connection error.', 'error');
}
}
function renderAssets() {
if (!grid || !empty) return;
grid.innerHTML = '';
if (allAssets.length === 0) {
empty.classList.remove('hidden');
return;
}
empty.classList.add('hidden');
allAssets.forEach(asset => {
const div = document.createElement('div');
div.className = "group relative aspect-square bg-crust rounded-xl overflow-hidden border border-white/5 transition-all hover:scale-105 shadow-lg flex flex-col";
const isImage = /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(asset.name);
// Preview Container
const preview = document.createElement('div');
preview.className = "flex-1 overflow-hidden bg-surface0/20 relative cursor-pointer";
if (isImage) {
const img = document.createElement('img');
img.src = asset.url;
img.className = "w-full h-full object-cover opacity-80 group-hover:opacity-100 transition-opacity";
preview.appendChild(img);
} else {
preview.className += " flex flex-col items-center justify-center text-subtext0";
preview.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mb-2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
<span class="text-[8px] font-mono px-2 truncate w-full text-center">${asset.name.split('.').pop().toUpperCase()}</span>
`;
}
// Action Overlays
const actions = document.createElement('div');
actions.className = "absolute inset-0 bg-crust/60 backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2";
if (mode === 'select') {
const selectBtn = document.createElement('button');
selectBtn.className = "bg-mauve text-crust px-3 py-1 rounded-md text-xs font-bold";
selectBtn.textContent = "Insert";
selectBtn.onclick = () => {
document.dispatchEvent(new CustomEvent('asset-selected', { detail: asset }));
};
actions.appendChild(selectBtn);
}
const deleteBtn = document.createElement('button');
deleteBtn.className = "bg-red/80 hover:bg-red text-white p-1.5 rounded-md transition-colors";
deleteBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>';
deleteBtn.onclick = (e) => {
e.stopPropagation();
deleteAsset(asset.name);
};
actions.appendChild(deleteBtn);
preview.appendChild(actions);
// Label
const label = document.createElement('div');
label.className = "p-2 bg-crust text-[10px] truncate border-t border-white/5 text-subtext1";
label.textContent = asset.name;
div.appendChild(preview);
div.appendChild(label);
grid.appendChild(div);
});
}
fileInput?.addEventListener('change', (e) => {
if (e.target.files.length > 0) uploadFiles(e.target.files);
});
// Initialize
fetchAssets();
// Re-fetch on global upload events if needed
document.addEventListener('assets-updated', fetchAssets);
</script>