174 lines
8.8 KiB
Plaintext
174 lines
8.8 KiB
Plaintext
<%- include('../partials/head', { title: 'Edit Model' }) %>
|
|
<%- include('../partials/adminNav', { currentPath: '/admin' }) %>
|
|
<%- include('../partials/adminTopBar') %>
|
|
|
|
<div class="lg:pl-56 pt-14 lg:pt-0 min-h-screen">
|
|
<%- include('../partials/adminBanner') %>
|
|
<div class="max-w-2xl mx-auto px-4 sm:px-8 py-6 sm:py-8">
|
|
<div class="flex items-center gap-3 mb-8">
|
|
<a href="/admin" class="text-gray-500 hover:text-white transition-colors">
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.75">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
|
|
</svg>
|
|
</a>
|
|
<h1 class="text-xl font-semibold text-white">Edit Model</h1>
|
|
</div>
|
|
|
|
<% if (error) { %>
|
|
<div class="mb-6 flex items-start gap-3 bg-red-500/10 border border-red-500/30 rounded-lg px-4 py-3">
|
|
<p class="text-sm text-red-400"><%= error %></p>
|
|
</div>
|
|
<% } %>
|
|
|
|
<!-- Model details form -->
|
|
<form method="POST" action="/admin/models/<%= model.id %>/edit" class="bg-surface-900 border border-gray-800 rounded-2xl p-6 space-y-5 mb-6">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-400 mb-1.5">Display Name <span class="text-red-400">*</span></label>
|
|
<input name="name" type="text" required value="<%= model.name %>"
|
|
class="w-full bg-surface-800 border border-gray-700 rounded-lg px-3.5 py-2.5 text-sm text-white focus:outline-none focus:border-accent transition-colors"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-400 mb-1.5">Description</label>
|
|
<textarea name="description" rows="3"
|
|
class="w-full bg-surface-800 border border-gray-700 rounded-lg px-3.5 py-2.5 text-sm text-white placeholder-gray-600 focus:outline-none focus:border-accent transition-colors resize-none"
|
|
><%= model.description || '' %></textarea>
|
|
</div>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-400 mb-1.5">Category</label>
|
|
<select name="category_id"
|
|
class="w-full bg-surface-800 border border-gray-700 rounded-lg px-3 py-2.5 text-sm text-gray-300 focus:outline-none focus:border-accent transition-colors">
|
|
<option value="">Uncategorized</option>
|
|
<% categories.forEach(c => { %>
|
|
<option value="<%= c.id %>" <%= model.category_id == c.id ? 'selected' : '' %>><%= c.name %></option>
|
|
<% }) %>
|
|
</select>
|
|
</div>
|
|
<div class="flex items-end pb-0.5">
|
|
<label class="flex items-center gap-2.5 cursor-pointer">
|
|
<div class="relative">
|
|
<input name="is_public" type="checkbox" <%= model.is_public ? 'checked' : '' %> class="sr-only peer" />
|
|
<div class="w-9 h-5 bg-gray-700 rounded-full peer-checked:bg-accent transition-colors"></div>
|
|
<div class="absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-4"></div>
|
|
</div>
|
|
<span class="text-sm text-gray-400">Public</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="pt-2">
|
|
<button type="submit" class="btn-accent text-white rounded-lg px-5 py-2 text-sm font-medium transition-all">Save Changes</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Geometry status (STEP/STP only) -->
|
|
<% if (model.file_type === 'step' || model.file_type === 'stp') { %>
|
|
<div class="bg-surface-900 border border-gray-800 rounded-2xl p-6 mb-6">
|
|
<h2 class="text-sm font-semibold text-white mb-3">3D Geometry Processing</h2>
|
|
<% if (hasGeometry) { %>
|
|
<div class="flex items-center gap-2 text-sm text-green-400">
|
|
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
|
Geometry processed successfully
|
|
</div>
|
|
<% } else { %>
|
|
<div class="flex items-center justify-between gap-4">
|
|
<div class="flex items-center gap-2 text-sm text-amber-400">
|
|
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>
|
|
Geometry processing failed — model cannot be viewed
|
|
</div>
|
|
<button type="button" id="reconvert-btn"
|
|
class="shrink-0 bg-surface-700 hover:bg-surface-600 border border-gray-700 text-sm text-gray-300 rounded-lg px-4 py-2 transition-colors">
|
|
Retry Processing
|
|
</button>
|
|
</div>
|
|
<pre id="reconvert-error" class="mt-3 text-xs text-red-400 whitespace-pre-wrap break-all hidden"></pre>
|
|
<% } %>
|
|
</div>
|
|
<% } %>
|
|
|
|
<!-- Attached PDFs -->
|
|
<div class="bg-surface-900 border border-gray-800 rounded-2xl p-6 mb-6">
|
|
<h2 class="text-sm font-semibold text-white mb-4">Shop Diagrams / PDFs</h2>
|
|
<% if (pdfs.length === 0) { %>
|
|
<p class="text-sm text-gray-500 mb-4">No PDFs attached yet.</p>
|
|
<% } else { %>
|
|
<ul class="space-y-2 mb-4">
|
|
<% pdfs.forEach(pdf => { %>
|
|
<li class="flex items-center justify-between bg-surface-800 rounded-lg px-4 py-2.5">
|
|
<span class="text-sm text-gray-300"><%= pdf.display_name %></span>
|
|
<form method="POST" action="/admin/pdfs/<%= pdf.id %>/delete" onsubmit="return confirm('Remove this PDF?')">
|
|
<button type="submit" class="text-xs text-gray-500 hover:text-red-400 transition-colors">Remove</button>
|
|
</form>
|
|
</li>
|
|
<% }) %>
|
|
</ul>
|
|
<% } %>
|
|
<form method="POST" action="/admin/models/<%= model.id %>/pdf" enctype="multipart/form-data" class="flex gap-3 items-end">
|
|
<div class="flex-1">
|
|
<label class="block text-xs font-medium text-gray-400 mb-1.5">Attach PDF</label>
|
|
<input type="file" name="pdf_file" accept=".pdf" required
|
|
class="w-full text-sm text-gray-400 file:mr-3 file:py-1.5 file:px-3 file:rounded-lg file:border-0 file:text-xs file:font-medium file:bg-surface-700 file:text-gray-300 hover:file:bg-surface-600 transition-colors" />
|
|
</div>
|
|
<div class="flex-1">
|
|
<label class="block text-xs font-medium text-gray-400 mb-1.5">Display Name</label>
|
|
<input type="text" name="display_name" placeholder="e.g. Sheet 1 — Overview"
|
|
class="w-full bg-surface-800 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-600 focus:outline-none focus:border-accent transition-colors" />
|
|
</div>
|
|
<button type="submit" class="bg-surface-700 hover:bg-surface-600 border border-gray-700 text-sm text-gray-300 rounded-lg px-4 py-2 transition-colors shrink-0">
|
|
Attach
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Share link -->
|
|
<div class="bg-surface-900 border border-gray-800 rounded-2xl p-6">
|
|
<h2 class="text-sm font-semibold text-white mb-3">Share Link</h2>
|
|
<div class="flex items-center gap-3">
|
|
<input readonly value="<%= typeof baseUrl !== 'undefined' ? baseUrl : '' %>/view/<%= model.slug %>"
|
|
class="flex-1 bg-surface-800 border border-gray-700 rounded-lg px-3.5 py-2.5 text-sm text-gray-400 focus:outline-none" />
|
|
<button type="button" class="copy-link-btn btn-accent text-white rounded-lg px-4 py-2.5 text-sm font-medium transition-all shrink-0"
|
|
data-url="<%= typeof baseUrl !== 'undefined' ? baseUrl : '' %>/view/<%= model.slug %>">
|
|
Copy Link
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast" class="fixed bottom-6 right-6 bg-surface-800 border border-gray-700 text-white text-sm px-4 py-3 rounded-xl shadow-2xl opacity-0 translate-y-2 transition-all duration-300 pointer-events-none z-50">
|
|
<span id="toast-msg"></span>
|
|
</div>
|
|
|
|
<script src="/admin.js"></script>
|
|
<script>
|
|
(function () {
|
|
const btn = document.getElementById('reconvert-btn')
|
|
if (!btn) return
|
|
const errEl = document.getElementById('reconvert-error')
|
|
btn.addEventListener('click', async () => {
|
|
btn.disabled = true
|
|
btn.textContent = 'Processing…'
|
|
errEl.classList.add('hidden')
|
|
try {
|
|
const res = await fetch('/admin/models/<%= model.id %>/reconvert', { method: 'POST' })
|
|
const data = await res.json()
|
|
if (data.ok) {
|
|
window.location.reload()
|
|
} else {
|
|
errEl.textContent = data.error ?? 'Unknown error'
|
|
errEl.classList.remove('hidden')
|
|
btn.disabled = false
|
|
btn.textContent = 'Retry Processing'
|
|
}
|
|
} catch (err) {
|
|
errEl.textContent = String(err)
|
|
errEl.classList.remove('hidden')
|
|
btn.disabled = false
|
|
btn.textContent = 'Retry Processing'
|
|
}
|
|
})
|
|
})()
|
|
</script>
|
|
</body>
|
|
</html>
|