makerstash/client/index.html

749 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MakerStash - 3D Model Manager</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="nav-container">
<div class="nav-brand">
<i class="fas fa-box-open"></i>
<h1>MakerStash</h1>
</div>
<div class="nav-actions" id="navActions">
<button class="btn btn-secondary" id="themeToggle" onclick="toggleTheme()" title="Toggle dark/light mode">
<i class="fas fa-moon"></i>
</button>
<button class="btn btn-secondary" id="loginBtn" onclick="showLoginModal()">
<i class="fas fa-sign-in-alt"></i> Login
</button>
<button class="btn btn-primary" id="registerBtn" onclick="showRegisterModal()">
<i class="fas fa-user-plus"></i> Register
</button>
<div class="user-menu" id="userMenu" style="display: none;">
<span id="username"></span>
<button class="btn btn-secondary" onclick="showSettingsModal()">
<i class="fas fa-cog"></i> Settings
</button>
<button class="btn btn-secondary" onclick="logout()">
<i class="fas fa-sign-out-alt"></i> Logout
</button>
</div>
</div>
</div>
</nav>
<!-- Main Container -->
<div class="container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-section">
<h3>Browse</h3>
<ul class="sidebar-menu">
<li><a href="#" onclick="loadAllModels()"><i class="fas fa-th"></i> All Models</a></li>
<li><a href="#" onclick="showCollections()"><i class="fas fa-folder"></i> Collections</a></li>
<li><a href="#" onclick="showTags()"><i class="fas fa-tags"></i> Tags</a></li>
</ul>
</div>
<div class="sidebar-section" id="collectionsSection">
<h3>Collections</h3>
<ul class="sidebar-menu" id="collectionsList">
<li class="loading">Loading...</li>
</ul>
<button class="btn btn-sm btn-primary" onclick="showCreateCollectionModal()" id="createCollectionBtn" style="display: none;">
<i class="fas fa-plus"></i> New Collection
</button>
</div>
<div class="sidebar-section" id="tagsSection">
<h3>Popular Tags</h3>
<div class="tags-container" id="tagsList">
<span class="loading">Loading...</span>
</div>
</div>
<div class="sidebar-section">
<h3>Filters</h3>
<div class="filter-group">
<label>License</label>
<select id="filterLicense" onchange="applyFilters()">
<option value="">All Licenses</option>
<option value="Unknown">Unknown</option>
<option value="MIT">MIT</option>
<option value="Creative Commons">Creative Commons</option>
<option value="CC0">CC0 (Public Domain)</option>
<option value="GPL">GPL</option>
<option value="Apache">Apache</option>
<option value="Custom">Custom</option>
</select>
</div>
<div class="filter-group">
<label>File Type</label>
<select id="filterFileType" onchange="applyFilters()">
<option value="">All Types</option>
<option value=".stl">STL</option>
<option value=".3mf">3MF</option>
<option value=".obj">OBJ</option>
<option value=".gcode">GCODE</option>
<option value=".zip">ZIP</option>
</select>
</div>
<div class="filter-group">
<label>
<input type="checkbox" id="filterSupports" onchange="applyFilters()">
Has Supports
</label>
</div>
<div class="filter-group">
<label>Sort By</label>
<select id="sortBy" onchange="applyFilters()">
<option value="created_at">Date Added</option>
<option value="name">Name</option>
<option value="file_size">File Size</option>
<option value="updated_at">Last Updated</option>
</select>
</div>
<div class="filter-group">
<label>Order</label>
<select id="sortOrder" onchange="applyFilters()">
<option value="DESC">Newest First / Z-A / Largest</option>
<option value="ASC">Oldest First / A-Z / Smallest</option>
</select>
</div>
<button class="btn btn-sm btn-secondary btn-block" onclick="clearFilters()">
<i class="fas fa-times"></i> Clear Filters
</button>
</div>
<div class="sidebar-section">
<button class="btn btn-primary btn-block" onclick="showUploadModal()" id="uploadBtn" style="display: none;">
<i class="fas fa-upload"></i> Upload Model
</button>
</div>
<div class="sidebar-section" style="display: none;" id="printQueueSection">
<button class="btn btn-success btn-block" onclick="showPrintQueueModal()">
<i class="fas fa-print"></i> Print Queue <span id="queueCount" class="badge">0</span>
</button>
</div>
<div class="sidebar-section" style="display: none;" id="exportSection">
<button class="btn btn-secondary btn-block" onclick="exportAllModels()">
<i class="fas fa-download"></i> Export All
</button>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Search Bar -->
<div class="search-bar">
<input type="text" id="searchInput" placeholder="Search models..." onkeyup="handleSearch()">
<button class="btn btn-primary" onclick="handleSearch()">
<i class="fas fa-search"></i>
</button>
</div>
<!-- Bulk Selection Toolbar -->
<div class="bulk-toolbar" id="bulkToolbar" style="display: none;">
<div class="bulk-toolbar-content">
<span id="bulkSelectionCount">0 selected</span>
<button class="btn btn-sm btn-primary" onclick="bulkAddTags()">
<i class="fas fa-tags"></i> Add Tags
</button>
<button class="btn btn-sm btn-primary" onclick="bulkMoveToCollection()">
<i class="fas fa-folder"></i> Move to Collection
</button>
<button class="btn btn-sm btn-success" onclick="bulkAddToQueue()">
<i class="fas fa-print"></i> Add to Queue
</button>
<button class="btn btn-sm btn-secondary" onclick="bulkExport()">
<i class="fas fa-download"></i> Export
</button>
<button class="btn btn-sm btn-danger" onclick="bulkDelete()">
<i class="fas fa-trash"></i> Delete
</button>
<button class="btn btn-sm btn-secondary" onclick="clearSelection()">
<i class="fas fa-times"></i> Clear
</button>
</div>
</div>
<!-- Models Grid -->
<div class="models-grid" id="modelsGrid">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin"></i>
<p>Loading models...</p>
</div>
</div>
</main>
</div>
<!-- Login Modal -->
<div id="loginModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('loginModal')">&times;</span>
<h2>Login</h2>
<form onsubmit="handleLogin(event)">
<div class="form-group">
<label>Username</label>
<input type="text" id="loginUsername" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="loginPassword" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
<p class="form-footer">Don't have an account? <a href="#" onclick="closeModal('loginModal'); showRegisterModal()">Register</a></p>
</form>
</div>
</div>
<!-- Register Modal -->
<div id="registerModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('registerModal')">&times;</span>
<h2>Register</h2>
<form onsubmit="handleRegister(event)">
<div class="form-group">
<label>Username</label>
<input type="text" id="registerUsername" required>
</div>
<div class="form-group">
<label>Email</label>
<input type="email" id="registerEmail" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="registerPassword" required minlength="6">
</div>
<button type="submit" class="btn btn-primary btn-block">Register</button>
<p class="form-footer">Already have an account? <a href="#" onclick="closeModal('registerModal'); showLoginModal()">Login</a></p>
</form>
</div>
</div>
<!-- Upload Modal -->
<div id="uploadModal" class="modal">
<div class="modal-content modal-large">
<span class="close" onclick="closeModal('uploadModal')">&times;</span>
<h2>Upload 3D Model</h2>
<form onsubmit="handleUpload(event)">
<div class="form-group">
<label>File(s) (STL, OBJ, 3MF, GCODE, ZIP)</label>
<input type="file" id="uploadFile" accept=".stl,.obj,.3mf,.gcode,.zip,.txt" multiple required>
<small>Select multiple files to upload them together (first file will be primary)</small>
</div>
<div class="form-group">
<label>Model Name</label>
<input type="text" id="uploadName" required>
</div>
<div class="form-group">
<label>Description</label>
<textarea id="uploadDescription" rows="3"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label>Creator</label>
<input type="text" id="uploadCreator">
</div>
<div class="form-group">
<label>Source URL</label>
<input type="url" id="uploadSourceUrl">
</div>
</div>
<div class="form-group">
<label>Collection</label>
<select id="uploadCollection">
<option value="">None</option>
</select>
</div>
<div class="form-group">
<label>Tags (comma separated)</label>
<input type="text" id="uploadTags" placeholder="e.g. miniature, terrain, functional">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="uploadSupported">
This model includes supports
</label>
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="uploadNotes" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Upload Model</button>
</form>
</div>
</div>
<!-- Model Details Modal -->
<div id="modelModal" class="modal">
<div class="modal-content modal-large">
<span class="close" onclick="closeModal('modelModal')">&times;</span>
<div id="modelDetails">
<!-- Model details will be loaded here -->
</div>
</div>
</div>
<!-- Create Collection Modal -->
<div id="createCollectionModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('createCollectionModal')">&times;</span>
<h2>Create Collection</h2>
<form onsubmit="handleCreateCollection(event)">
<div class="form-group">
<label>Name</label>
<input type="text" id="collectionName" required>
</div>
<div class="form-group">
<label>Description</label>
<textarea id="collectionDescription" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Create Collection</button>
</form>
</div>
</div>
<!-- Edit Model Modal -->
<div id="editModelModal" class="modal">
<div class="modal-content modal-large">
<span class="close" onclick="closeModal('editModelModal')">&times;</span>
<h2>Edit Model</h2>
<form onsubmit="handleEditModel(event)">
<input type="hidden" id="editModelId">
<div class="form-group">
<label>Model Name</label>
<input type="text" id="editModelName" required>
</div>
<div class="form-group">
<label>Description</label>
<textarea id="editModelDescription" rows="3"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label>Creator</label>
<input type="text" id="editModelCreator">
</div>
<div class="form-group">
<label>Source URL</label>
<input type="url" id="editModelSourceUrl">
</div>
</div>
<div class="form-group">
<label>Collection</label>
<select id="editModelCollection">
<option value="">None</option>
</select>
</div>
<div class="form-group">
<label>Tags (comma separated)</label>
<input type="text" id="editModelTags" placeholder="e.g. miniature, terrain, functional">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="editModelSupported">
This model includes supports
</label>
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="editModelNotes" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Save Changes</button>
</form>
</div>
</div>
<!-- Bulk Add Tags Modal -->
<div id="bulkTagsModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('bulkTagsModal')">&times;</span>
<h2>Add Tags to Selected Models</h2>
<form onsubmit="handleBulkAddTags(event)">
<div class="form-group">
<label>Tags (comma separated)</label>
<input type="text" id="bulkTagsInput" placeholder="tag1, tag2, tag3" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Add Tags</button>
</form>
</div>
</div>
<!-- Bulk Move Modal -->
<div id="bulkMoveModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('bulkMoveModal')">&times;</span>
<h2>Move Selected Models to Collection</h2>
<form onsubmit="handleBulkMove(event)">
<div class="form-group">
<label>Collection</label>
<select id="bulkMoveCollection" required>
<option value="">Select Collection</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-block">Move Models</button>
</form>
</div>
</div>
<!-- Print Queue Modal -->
<div id="printQueueModal" class="modal">
<div class="modal-content modal-large">
<span class="close" onclick="closeModal('printQueueModal')">&times;</span>
<h2>Print Queue</h2>
<div id="printQueueList" class="queue-list">
<div class="loading-spinner">Loading queue...</div>
</div>
</div>
</div>
<!-- Print Details Modal -->
<div id="printDetailsModal" class="modal">
<div class="modal-content modal-large">
<span class="close" onclick="closeModal('printDetailsModal')">&times;</span>
<h2>Print Job Details</h2>
<form onsubmit="savePrintDetails(event)" id="printDetailsForm">
<div class="form-row">
<div class="form-group">
<label>Model Name</label>
<input type="text" id="printModelName" readonly>
</div>
<div class="form-group">
<label>Quantity</label>
<input type="number" id="printQuantity" min="1" value="1" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Filament Type</label>
<select id="printFilamentType" required>
<option value="">Select filament type...</option>
<option value="pla">PLA</option>
<option value="petg">PETG</option>
<option value="abs">ABS</option>
<option value="tpu">TPU (Flexible)</option>
<option value="nylon">Nylon</option>
<option value="carbon">Carbon Fiber</option>
<option value="resin">Resin (SLA/DLP)</option>
<option value="other">Other</option>
</select>
</div>
<div class="form-group">
<label>Color/Material</label>
<input type="text" id="printColor" placeholder="e.g., Blue, Red, Natural" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Print Temperature (°C)</label>
<input type="number" id="printTemp" min="0" max="300" placeholder="e.g., 200">
</div>
<div class="form-group">
<label>Bed Temperature (°C)</label>
<input type="number" id="printBedTemp" min="0" max="150" placeholder="e.g., 60">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Print Speed</label>
<select id="printSpeed" required>
<option value="">Select speed...</option>
<option value="slow">Slow (High Quality)</option>
<option value="normal">Normal (Balanced)</option>
<option value="fast">Fast (Quick)</option>
</select>
</div>
<div class="form-group">
<label>Support Structure</label>
<select id="printSupport" required>
<option value="none">None</option>
<option value="tree">Tree Support</option>
<option value="linear">Linear Support</option>
<option value="grid">Grid Support</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Infill Density (%)</label>
<input type="number" id="printInfill" min="0" max="100" value="20" required>
</div>
<div class="form-group">
<label>Layer Height (mm)</label>
<input type="number" id="printLayerHeight" min="0.1" max="0.5" step="0.1" value="0.2" required>
</div>
</div>
<div class="form-group full-width">
<label>Special Instructions/Notes</label>
<textarea id="printNotes" placeholder="Any special requirements, post-processing notes, etc." rows="4"></textarea>
</div>
<div class="form-group full-width">
<label>Priority Level</label>
<select id="printPriority" required>
<option value="0">Low</option>
<option value="5">Medium</option>
<option value="10">High</option>
<option value="20">Urgent</option>
</select>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="closeModal('printDetailsModal')">Cancel</button>
<button type="submit" class="btn btn-primary">Add to Queue</button>
</div>
</form>
</div>
</div>
<!-- Edit Print Job Modal -->
<div id="editPrintJobModal" class="modal">
<div class="modal-content modal-large">
<span class="close" onclick="closeModal('editPrintJobModal')">&times;</span>
<h2>Edit Print Job</h2>
<form onsubmit="saveEditPrintJob(event)" id="editPrintJobForm">
<div class="form-row">
<div class="form-group">
<label>Model Name</label>
<input type="text" id="editPrintModelName" readonly>
</div>
<div class="form-group">
<label>Quantity</label>
<input type="number" id="editPrintQuantity" min="1" value="1" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Filament Type</label>
<select id="editPrintFilamentType" required>
<option value="">Select filament type...</option>
<option value="pla">PLA</option>
<option value="petg">PETG</option>
<option value="abs">ABS</option>
<option value="tpu">TPU (Flexible)</option>
<option value="nylon">Nylon</option>
<option value="carbon">Carbon Fiber</option>
<option value="resin">Resin (SLA/DLP)</option>
<option value="other">Other</option>
</select>
</div>
<div class="form-group">
<label>Color/Material</label>
<input type="text" id="editPrintColor" placeholder="e.g., Blue, Red, Natural" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Print Temperature (°C)</label>
<input type="number" id="editPrintTemp" min="0" max="300" placeholder="e.g., 200">
</div>
<div class="form-group">
<label>Bed Temperature (°C)</label>
<input type="number" id="editPrintBedTemp" min="0" max="150" placeholder="e.g., 60">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Print Speed</label>
<select id="editPrintSpeed" required>
<option value="">Select speed...</option>
<option value="slow">Slow (High Quality)</option>
<option value="normal">Normal (Balanced)</option>
<option value="fast">Fast (Quick)</option>
</select>
</div>
<div class="form-group">
<label>Support Structure</label>
<select id="editPrintSupport" required>
<option value="none">None</option>
<option value="tree">Tree Support</option>
<option value="linear">Linear Support</option>
<option value="grid">Grid Support</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Infill Density (%)</label>
<input type="number" id="editPrintInfill" min="0" max="100" value="20" required>
</div>
<div class="form-group">
<label>Layer Height (mm)</label>
<input type="number" id="editPrintLayerHeight" min="0.1" max="0.5" step="0.1" value="0.2" required>
</div>
</div>
<div class="form-group full-width">
<label>Special Instructions/Notes</label>
<textarea id="editPrintNotes" placeholder="Any special requirements, post-processing notes, etc." rows="4"></textarea>
</div>
<div class="form-group full-width">
<label>Priority Level</label>
<select id="editPrintPriority" required>
<option value="0">Low</option>
<option value="5">Medium</option>
<option value="10">High</option>
<option value="20">Urgent</option>
</select>
</div>
<div class="form-actions">
<button type="button" class="btn btn-danger" onclick="removePrintJob()">Delete Job</button>
<button type="button" class="btn btn-secondary" onclick="closeModal('editPrintJobModal')">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
<!-- Printer Settings Modal -->
<!-- Filament Cost Calculator Modal -->
<div id="costCalculatorModal" class="modal">
<div class="modal-content modal-large">
<span class="close" onclick="closeModal('costCalculatorModal')">&times;</span>
<h2>Filament/Resin Cost Calculator</h2>
<div class="cost-calculator">
<div class="cost-controls">
<div class="form-group">
<label>Material Type</label>
<select id="materialType" onchange="updateCostEstimate()">
<option value="pla">PLA (~$15/kg)</option>
<option value="abs">ABS (~$18/kg)</option>
<option value="petg">PETG (~$20/kg)</option>
<option value="nylon">Nylon (~$35/kg)</option>
<option value="tpu">TPU (~$40/kg)</option>
<option value="carbon">Carbon Fiber (~$50/kg)</option>
<option value="bamboo">Bamboo (~$25/kg)</option>
<option value="standard">Standard Resin (~$12/ml)</option>
<option value="tough">Tough Resin (~$18/ml)</option>
<option value="flexible">Flexible Resin (~$20/ml)</option>
<option value="castable">Castable Resin (~$25/ml)</option>
</select>
</div>
</div>
<div id="costResults" class="cost-results">
<p class="text-muted">Select models from the grid to see cost estimates</p>
</div>
</div>
</div>
</div>
<!-- Account Settings Modal -->
<div id="settingsModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal('settingsModal')">&times;</span>
<h2>Account Settings</h2>
<div class="settings-tabs">
<button class="tab-button active" onclick="switchSettingsTab('email')">Change Email</button>
<button class="tab-button" onclick="switchSettingsTab('password')">Change Password</button>
<button class="tab-button" onclick="switchSettingsTab('accounts')">Connected Accounts</button>
</div>
<!-- Change Email Tab -->
<div id="emailTab" class="tab-content active">
<form onsubmit="updateEmail(event)">
<div class="form-group">
<label>Current Email</label>
<input type="email" id="currentEmailDisplay" disabled>
</div>
<div class="form-group">
<label>New Email</label>
<input type="email" id="newEmail" required placeholder="Enter your new email">
</div>
<div class="form-group">
<label>Password (to confirm)</label>
<input type="password" id="emailConfirmPassword" required placeholder="Enter your password to confirm">
</div>
<button type="submit" class="btn btn-primary">Update Email</button>
</form>
</div>
<!-- Change Password Tab -->
<div id="passwordTab" class="tab-content">
<form onsubmit="updatePassword(event)">
<div class="form-group">
<label>Current Password</label>
<input type="password" id="currentPassword" required placeholder="Enter your current password">
</div>
<div class="form-group">
<label>New Password</label>
<input type="password" id="newPassword" required placeholder="Enter your new password (min 6 characters)">
</div>
<div class="form-group">
<label>Confirm New Password</label>
<input type="password" id="confirmNewPassword" required placeholder="Confirm your new password">
</div>
<button type="submit" class="btn btn-primary">Update Password</button>
</form>
</div>
<!-- Connected Accounts Tab -->
<div id="accountsTab" class="tab-content">
<p style="color: var(--text-secondary); margin-bottom: 1rem;">Connect your accounts from 3D model websites to automatically download models from URLs.</p>
<div id="connectedAccountsList" style="display: grid; gap: 1rem; margin-bottom: 2rem;">
<!-- Connected accounts will be loaded here -->
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: 1rem;">
<h3 style="margin-bottom: 1rem;">Add New Account</h3>
<div class="form-group">
<label>Website</label>
<select id="accountSiteSelect" onchange="updateAccountForm()">
<option value="">Select a website...</option>
</select>
</div>
<div id="accountFormFields" style="display: none;">
<div class="form-group">
<label>Username</label>
<input type="text" id="accountUsername" placeholder="Your username">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="accountPassword" placeholder="Your password (encrypted)">
</div>
<div class="form-group">
<label>API Key <span style="font-size: 0.8rem; color: var(--text-secondary);">(optional)</span></label>
<input type="text" id="accountApiKey" placeholder="API key if available">
</div>
<div class="form-group">
<label>Access Token <span style="font-size: 0.8rem; color: var(--text-secondary);">(optional)</span></label>
<input type="text" id="accountAccessToken" placeholder="Access token if available">
</div>
<button type="button" class="btn btn-primary" onclick="addConnectedAccount()">Connect Account</button>
<button type="button" class="btn btn-secondary" onclick="testAccountConnection()" style="margin-left: 0.5rem;">Test Connection</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/STLLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script>
<script src="app.js"></script>
<script src="app-features.js"></script>
<script src="viewer3d.js"></script>
<script src="theme.js"></script>
<script src="features.js"></script>
</body>
</html>