Files
stepview/views/viewer.ejs
T
2026-04-22 15:47:27 -05:00

153 lines
7.2 KiB
Plaintext

<%- include('./partials/head', { title: model.name }) %>
<style>
body { overflow: hidden; }
#viewer-canvas { display: block; width: 100vw; height: 100vh; }
#pdf-panel { transition: transform 0.3s ease; }
#pdf-panel.closed { transform: translateX(100%); }
</style>
<!-- 3D Canvas -->
<canvas id="viewer-canvas"></canvas>
<!-- Loading overlay -->
<div id="loading-overlay" class="fixed inset-0 bg-surface-950 flex items-center justify-center z-30">
<div class="text-center">
<div class="w-10 h-10 border-2 border-accent border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p class="text-sm text-gray-400" id="loading-msg">Loading model…</p>
</div>
</div>
<!-- Error overlay -->
<div id="error-overlay" class="fixed inset-0 bg-surface-950 items-center justify-center z-30 hidden">
<div class="text-center max-w-sm px-6">
<svg class="w-12 h-12 text-red-400 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
<p class="text-sm text-gray-300 font-medium mb-2">Failed to load model</p>
<p id="error-msg" class="text-xs text-gray-500"></p>
</div>
</div>
<!-- Top-right controls -->
<div class="fixed top-5 right-5 flex items-center gap-2 z-20">
<!-- Wireframe toggle -->
<button id="wireframe-btn" title="Toggle wireframe"
class="flex items-center justify-center w-8 h-8 bg-surface-900/90 backdrop-blur-sm border border-gray-700 hover:border-gray-600 text-gray-400 hover:text-white rounded-xl transition-all">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.75">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
</svg>
</button>
<!-- Reset camera -->
<button id="reset-camera-btn" title="Reset camera"
class="flex items-center justify-center w-8 h-8 bg-surface-900/90 backdrop-blur-sm border border-gray-700 hover:border-gray-600 text-gray-400 hover:text-white rounded-xl transition-all">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.75">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>
</button>
<!-- Copy link -->
<button id="copy-link-btn"
data-url="<%= shareUrl %>"
class="flex items-center gap-2 bg-surface-900/90 backdrop-blur-sm border border-gray-700 hover:border-gray-600 text-gray-300 hover:text-white rounded-xl px-3.5 py-2 text-xs font-medium transition-all">
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
</svg>
Copy Link
</button>
<!-- PDF toggle (only shown if PDFs exist) -->
<% if (pdfs.length > 0) { %>
<button id="pdf-toggle-btn"
class="flex items-center gap-2 bg-surface-900/90 backdrop-blur-sm border border-gray-700 hover:border-gray-600 text-gray-300 hover:text-white rounded-xl px-3.5 py-2 text-xs font-medium transition-all">
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
Diagrams <span class="opacity-60">(<%= pdfs.length %>)</span>
</button>
<% } %>
</div>
<!-- Bottom-left model info -->
<div class="fixed bottom-6 left-6 z-20 max-w-xs">
<% if (brand.brand_logo_path) { %>
<img src="/brand/logo" alt="<%= brand.brand_name %>" class="h-5 w-auto object-contain mb-3 opacity-70" />
<% } %>
<h1 class="text-base font-semibold text-white leading-tight"><%= model.name %></h1>
<% if (model.category_name) { %>
<p class="text-xs text-gray-500 mt-0.5"><%= model.category_name %></p>
<% } %>
<% if (model.description) { %>
<p class="text-xs text-gray-400 mt-1.5 leading-relaxed"><%= model.description %></p>
<% } %>
</div>
<!-- Bottom-right orbit hint -->
<div class="fixed bottom-6 right-6 z-20 text-right hidden md:block">
<p class="text-xs text-gray-700">Left drag — rotate &nbsp;·&nbsp; Right drag — pan &nbsp;·&nbsp; Scroll — zoom</p>
</div>
<!-- PDF slide-in panel -->
<% if (pdfs.length > 0) { %>
<div id="pdf-panel" class="fixed top-0 right-0 h-full w-full max-w-md bg-surface-900 border-l border-gray-800 z-20 closed flex flex-col">
<!-- Panel header -->
<div class="flex items-center justify-between px-5 py-4 border-b border-gray-800 shrink-0">
<div>
<h2 class="text-sm font-semibold text-white">Shop Diagrams</h2>
<p class="text-xs text-gray-500 mt-0.5"><%= model.name %></p>
</div>
<button id="pdf-close-btn" class="p-1.5 rounded-lg text-gray-500 hover:text-white hover:bg-surface-800 transition-colors">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- PDF tabs -->
<% if (pdfs.length > 1) { %>
<div class="flex border-b border-gray-800 overflow-x-auto shrink-0">
<% pdfs.forEach((pdf, i) => { %>
<button
class="pdf-tab px-4 py-2.5 text-xs whitespace-nowrap font-medium transition-colors border-b-2 <%= i === 0 ? 'border-accent text-white' : 'border-transparent text-gray-500 hover:text-gray-300' %>"
data-tab="<%= i %>">
<%= pdf.display_name %>
</button>
<% }) %>
</div>
<% } %>
<!-- PDF viewer -->
<div class="flex-1 overflow-hidden relative">
<% pdfs.forEach((pdf, i) => { %>
<iframe
src="/files/pdf/<%= model.id %>/<%= pdf.id %>#toolbar=0&navpanes=0&view=FitH"
class="pdf-frame absolute inset-0 w-full h-full border-0 <%= i > 0 ? 'hidden' : '' %>"
data-frame="<%= i %>"
></iframe>
<% }) %>
</div>
</div>
<% } %>
<!-- Toast -->
<div id="toast" class="fixed bottom-6 left-1/2 -translate-x-1/2 bg-surface-800 border border-gray-700 text-white text-sm px-4 py-3 rounded-xl shadow-2xl opacity-0 transition-all duration-300 pointer-events-none z-50">
<span id="toast-msg"></span>
</div>
<!-- Model data passed to viewer JS -->
<script>
window.__STEPVIEW__ = {
modelId: <%= model.id %>,
fileType: '<%= model.file_type %>',
shareUrl: '<%- shareUrl %>',
hasPdfs: <%= pdfs.length > 0 %>,
hasGeometry: <%= typeof hasGeometry !== 'undefined' ? hasGeometry : false %>,
}
</script>
<script src="/viewer.js"></script>
</body>
</html>