Compare commits
No commits in common. "5fb3939bcd05feef501f95802bda3d1a137453eb" and "3a6a2143ef57202036b98536c63e3f2febb0ca62" have entirely different histories.
5fb3939bcd
...
3a6a2143ef
4 changed files with 1 additions and 106 deletions
|
|
@ -15,10 +15,8 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
app:
|
app:
|
||||||
# Pulled from the Forgejo registry (built by CI). To build locally instead:
|
build: .
|
||||||
# docker build -t git.plexultra.com/dlawler489/printhive:latest .
|
|
||||||
image: git.plexultra.com/dlawler489/printhive:latest
|
image: git.plexultra.com/dlawler489/printhive:latest
|
||||||
pull_policy: always
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ const uploadModal = document.getElementById('uploadModal');
|
||||||
const projectModal = document.getElementById('projectModal');
|
const projectModal = document.getElementById('projectModal');
|
||||||
const importModal = document.getElementById('importModal');
|
const importModal = document.getElementById('importModal');
|
||||||
const settingsModal = document.getElementById('settingsModal');
|
const settingsModal = document.getElementById('settingsModal');
|
||||||
const setupModal = document.getElementById('setupModal');
|
|
||||||
const editModal = document.getElementById('editModal');
|
const editModal = document.getElementById('editModal');
|
||||||
const fileDetailModal = document.getElementById('fileDetailModal');
|
const fileDetailModal = document.getElementById('fileDetailModal');
|
||||||
|
|
||||||
|
|
@ -40,11 +39,6 @@ async function loadData() {
|
||||||
renderModels();
|
renderModels();
|
||||||
updateCounts();
|
updateCounts();
|
||||||
generateMissingThumbnails(); // fire-and-forget; updates cards as they finish
|
generateMissingThumbnails(); // fire-and-forget; updates cards as they finish
|
||||||
|
|
||||||
// First-run onboarding: prompt for import sources once.
|
|
||||||
if (config.setupComplete === false && !setupModal.classList.contains('active')) {
|
|
||||||
setupModal.classList.add('active');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Error loading data:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -176,13 +170,9 @@ function setupEventListeners() {
|
||||||
btn.addEventListener('click', (e) => closeModal(e.target.closest('.modal')));
|
btn.addEventListener('click', (e) => closeModal(e.target.closest('.modal')));
|
||||||
});
|
});
|
||||||
document.querySelectorAll('.modal').forEach((modal) => {
|
document.querySelectorAll('.modal').forEach((modal) => {
|
||||||
if (modal.id === 'setupModal') return; // must be finished or skipped, not backdrop-dismissed
|
|
||||||
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(modal); });
|
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(modal); });
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('setupForm').addEventListener('submit', handleFinishSetup);
|
|
||||||
document.getElementById('skipSetup').addEventListener('click', handleSkipSetup);
|
|
||||||
|
|
||||||
document.getElementById('uploadForm').addEventListener('submit', handleUpload);
|
document.getElementById('uploadForm').addEventListener('submit', handleUpload);
|
||||||
document.getElementById('cancelUpload').addEventListener('click', () => closeModal(uploadModal));
|
document.getElementById('cancelUpload').addEventListener('click', () => closeModal(uploadModal));
|
||||||
document.getElementById('projectForm').addEventListener('submit', handleCreateProject);
|
document.getElementById('projectForm').addEventListener('submit', handleCreateProject);
|
||||||
|
|
@ -269,42 +259,6 @@ function openProjectModal() {
|
||||||
projectModal.classList.add('active');
|
projectModal.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function markSetupComplete() {
|
|
||||||
try {
|
|
||||||
await fetch('/api/setup/complete', { method: 'POST' });
|
|
||||||
config.setupComplete = true;
|
|
||||||
} catch { /* non-fatal */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleFinishSetup(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const status = document.getElementById('setupStatus');
|
|
||||||
const payload = {};
|
|
||||||
document.querySelectorAll('#setupForm [data-setting]').forEach((input) => {
|
|
||||||
if (input.value.trim()) payload[input.dataset.setting] = input.value.trim();
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
if (Object.keys(payload).length) {
|
|
||||||
const res = await fetch('/api/settings', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error((await res.json()).error);
|
|
||||||
}
|
|
||||||
await markSetupComplete();
|
|
||||||
closeModal(setupModal);
|
|
||||||
await loadData();
|
|
||||||
} catch (err) {
|
|
||||||
status.textContent = 'Could not save: ' + err.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSkipSetup() {
|
|
||||||
await markSetupComplete();
|
|
||||||
closeModal(setupModal);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openSettingsModal() {
|
async function openSettingsModal() {
|
||||||
document.getElementById('settingsForm').reset();
|
document.getElementById('settingsForm').reset();
|
||||||
document.getElementById('settingsStatus').textContent = '';
|
document.getElementById('settingsStatus').textContent = '';
|
||||||
|
|
|
||||||
|
|
@ -69,50 +69,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- First-run Setup Modal -->
|
|
||||||
<div id="setupModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>Welcome to Printhive</h2>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p class="form-hint" style="margin-bottom:18px">
|
|
||||||
Optionally connect import sources now — you can change these any time in Settings.
|
|
||||||
All are optional; you can also just upload files directly.
|
|
||||||
</p>
|
|
||||||
<form id="setupForm">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="setupThingiverse">Thingiverse API token</label>
|
|
||||||
<input type="password" id="setupThingiverse" data-setting="thingiverse_token" autocomplete="off"
|
|
||||||
placeholder="App Token from thingiverse.com/developers">
|
|
||||||
<p class="form-hint">Enables automatic Thingiverse imports.</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="setupFlaresolverr">FlareSolverr URL</label>
|
|
||||||
<input type="text" id="setupFlaresolverr" data-setting="flaresolverr_url" autocomplete="off"
|
|
||||||
placeholder="http://192.168.1.124:8191">
|
|
||||||
<p class="form-hint">Lets imports read Cloudflare-protected sites like MakerWorld.</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="setupMakerworld">MakerWorld auth token</label>
|
|
||||||
<input type="password" id="setupMakerworld" data-setting="makerworld_token" autocomplete="off"
|
|
||||||
placeholder="Bearer token from your logged-in session">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="setupPrintables">Printables auth token</label>
|
|
||||||
<input type="password" id="setupPrintables" data-setting="printables_token" autocomplete="off"
|
|
||||||
placeholder="Optional — for account-gated files">
|
|
||||||
</div>
|
|
||||||
<div class="form-actions">
|
|
||||||
<button type="button" class="btn btn-secondary" id="skipSetup">Skip for now</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Save & finish</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div id="setupStatus" class="import-status"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Settings Modal -->
|
<!-- Settings Modal -->
|
||||||
<div id="settingsModal" class="modal">
|
<div id="settingsModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
|
||||||
13
server.js
13
server.js
|
|
@ -98,14 +98,9 @@ app.get('/api/config', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const thingiverse = await resolveSetting('thingiverse_token');
|
const thingiverse = await resolveSetting('thingiverse_token');
|
||||||
const flare = await resolveSetting('flaresolverr_url');
|
const flare = await resolveSetting('flaresolverr_url');
|
||||||
const makerworld = await resolveSetting('makerworld_token');
|
|
||||||
const printables = await resolveSetting('printables_token');
|
|
||||||
const alreadyConfigured = Boolean(thingiverse.value || flare.value || makerworld.value || printables.value);
|
|
||||||
const setupComplete = alreadyConfigured || Boolean(await store.getSetting('setup_complete'));
|
|
||||||
res.json({
|
res.json({
|
||||||
importers: { thingiverse: Boolean(thingiverse.value), flaresolverr: Boolean(flare.value) },
|
importers: { thingiverse: Boolean(thingiverse.value), flaresolverr: Boolean(flare.value) },
|
||||||
formats: fileProcessor.MODEL_FORMATS,
|
formats: fileProcessor.MODEL_FORMATS,
|
||||||
setupComplete,
|
|
||||||
});
|
});
|
||||||
} catch (e) { next(e); }
|
} catch (e) { next(e); }
|
||||||
});
|
});
|
||||||
|
|
@ -131,14 +126,6 @@ app.get('/api/settings', async (req, res, next) => {
|
||||||
} catch (e) { next(e); }
|
} catch (e) { next(e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark the first-run setup wizard as done (so it won't show again).
|
|
||||||
app.post('/api/setup/complete', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
await store.setSetting('setup_complete', 'true');
|
|
||||||
res.json({ ok: true });
|
|
||||||
} catch (e) { next(e); }
|
|
||||||
});
|
|
||||||
|
|
||||||
app.put('/api/settings', async (req, res, next) => {
|
app.put('/api/settings', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
for (const [key, value] of Object.entries(req.body || {})) {
|
for (const [key, value] of Object.entries(req.body || {})) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue