Fix template rendering issue and improve export functionality

- Fix template rendering by storing rendered HTML in variable before writing
  This resolves subdirectories not appearing in navigation despite being
  correctly detected and included in template variables
- Add missing template variables (paths.work_dir) to gallery config
- Add CGI refresh handler for web-based gallery regeneration
- Improve PDF export functionality with better error handling
- Add ESC key shortcut documentation for exiting selection mode
- Enhanced export manager with proper dependency checking

Fixes issue where new subdirectories were detected but not displayed
in browser navigation due to incomplete template rendering.
This commit is contained in:
Kylian Schmidt
2025-07-20 08:59:10 +02:00
parent 2d98ed0e7a
commit dbd67f6039
5 changed files with 347 additions and 29 deletions
+143 -15
View File
@@ -52,7 +52,7 @@ export class ExportManager {
this.toggleSelectionMode();
}
if (e.key === 'Escape') {
this.clearSelection();
this.exitSelectionMode();
}
});
@@ -118,10 +118,17 @@ export class ExportManager {
* Exit selection mode
*/
exitSelectionMode() {
const wasInSelectionMode = document.body.classList.contains('selection-mode');
document.body.classList.remove('selection-mode');
document.getElementById('exportBtn').style.display = 'none';
document.getElementById('selectionCounter').style.display = 'none';
this.clearSelection();
// Show message if user was actually in selection mode
if (wasInSelectionMode) {
this.showMessage('Exited selection mode', 'info');
}
}
/**
@@ -229,17 +236,26 @@ export class ExportManager {
return url;
});
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
const outputName = `merged_plots_${timestamp}.pdf`;
const payload = {
plots: actualPaths,
layout: this.calculateLayout(actualPaths.length),
output_name: `merged_plots_${new Date().toISOString().split('T')[0]}.pdf`
output_name: outputName
};
// Generate a unique temporary filename
const tempFileName = `export_request_${Date.now()}.json`;
// Get work directory from config or fallback
const workDir = window.galleryConfig?.workDir || '/work/kschmidt/web';
// Save the request to a JSON file that can be picked up by a Python script
const requestData = JSON.stringify(payload, null, 2);
// Since we can't directly call Python from the browser, we'll show instructions
this.showExportInstructions(requestData);
// Show improved export instructions with full command
this.showExportInstructions(requestData, tempFileName, workDir);
}
/**
@@ -258,21 +274,49 @@ export class ExportManager {
/**
* Show export instructions to user
*/
showExportInstructions(requestData) {
showExportInstructions(requestData, tempFileName, workDir) {
const tempFilePath = `/tmp/${tempFileName}`;
const fullCommand = `echo '${requestData.replace(/'/g, "'\\''")}' > ${tempFilePath} && cd ${workDir} && python export_plots.py ${tempFilePath}`;
const instructions = `
<div class="export-instructions">
<h3>Export Instructions</h3>
<p>To export your selected plots, save the following data to a file called <code>export_request.json</code> and run the export script:</p>
<div class="export-data">
<textarea readonly>${requestData}</textarea>
<h3>🚀 Export Selected Plots</h3>
<p>Run the following command in your terminal to export the selected plots:</p>
<div class="export-command-container">
<div class="export-command">
<code id="exportCommand">${fullCommand}</code>
</div>
<div class="export-actions">
<button onclick="this.copyCommand()" class="copy-btn" title="Copy command to clipboard">
📋 Copy Command
</button>
<button onclick="this.copyJSON()" class="copy-btn" title="Copy JSON only">
📄 Copy JSON
</button>
<button onclick="this.close()" class="close-btn">
✕ Close
</button>
</div>
</div>
<div class="export-commands">
<p><strong>Command to run:</strong></p>
<code>cd /work/kschmidt/web && python export_plots.py export_request.json</code>
<div class="export-details">
<h4>📋 Command Breakdown:</h4>
<ul>
<li><strong>Creates temporary file:</strong> <code>${tempFilePath}</code></li>
<li><strong>Changes to work directory:</strong> <code>${workDir}</code></li>
<li><strong>Runs export script:</strong> <code>python export_plots.py</code></li>
<li><strong>Output file:</strong> Will be saved in the work directory</li>
</ul>
</div>
<div class="export-actions">
<button onclick="this.parentElement.parentElement.parentElement.remove()">Close</button>
<button onclick="navigator.clipboard.writeText('${requestData.replace(/'/g, "\\'")}')">Copy JSON</button>
<div class="export-tips">
<h4>💡 Tips:</h4>
<ul>
<li>The temporary JSON file will be automatically cleaned up after successful export</li>
<li>Use <kbd>Esc</kbd> to exit selection mode</li>
<li>Press <kbd>Ctrl+E</kbd> to toggle selection mode</li>
</ul>
</div>
</div>
`;
@@ -280,7 +324,59 @@ export class ExportManager {
const overlay = document.createElement('div');
overlay.className = 'export-overlay';
overlay.innerHTML = instructions;
// Add methods to the overlay for button handlers
overlay.copyCommand = function() {
navigator.clipboard.writeText(fullCommand).then(() => {
this.showCopyFeedback('Command copied to clipboard!');
}).catch(() => {
this.showCopyFeedback('Failed to copy. Please select and copy manually.', 'error');
});
};
overlay.copyJSON = function() {
navigator.clipboard.writeText(requestData).then(() => {
this.showCopyFeedback('JSON copied to clipboard!');
}).catch(() => {
this.showCopyFeedback('Failed to copy. Please select and copy manually.', 'error');
});
};
overlay.close = function() {
this.remove();
};
overlay.showCopyFeedback = function(message, type = 'success') {
const feedback = document.createElement('div');
feedback.className = `copy-feedback copy-feedback-${type}`;
feedback.textContent = message;
this.appendChild(feedback);
setTimeout(() => {
if (feedback.parentNode) {
feedback.parentNode.removeChild(feedback);
}
}, 2000);
};
document.body.appendChild(overlay);
// Close on ESC key
const handleEscape = (e) => {
if (e.key === 'Escape') {
overlay.remove();
document.removeEventListener('keydown', handleEscape);
}
};
document.addEventListener('keydown', handleEscape);
// Close on clicking outside
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.remove();
document.removeEventListener('keydown', handleEscape);
}
});
}
/**
@@ -318,3 +414,35 @@ export class ExportManager {
}, 3000);
}
}
// Add these methods to ExportManager if not present
ExportManager.prototype.isSelectionModeActive = function() {
return document.body.classList.contains('selection-mode');
};
ExportManager.prototype.exitSelectionMode = function() {
document.body.classList.remove('selection-mode');
if (typeof this.clearSelection === 'function') {
this.clearSelection();
}
};
// Ensure a single global instance
window.exportManager = window.exportManager || new ExportManager();
// Listen for ESC key globally to exit selection mode
// (This will work even if focus is not on a plot)
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && window.exportManager && window.exportManager.isSelectionModeActive()) {
window.exportManager.exitSelectionMode();
}
});
// Attach improved export logic to export button
document.addEventListener('DOMContentLoaded', function() {
const exportBtn = document.getElementById('exportBtn');
if (exportBtn) {
exportBtn.addEventListener('click', function() {
window.exportManager.exportSelectedPlots();
});
}
});
+9 -1
View File
@@ -105,8 +105,16 @@ export class Utils {
refreshBtn.disabled = true;
refreshBtn.textContent = '⏳';
// Use the CGI script path from config if available
let cgiPath = '/cgi-bin/refresh_gallery.py';
if (window.galleryConfig && window.galleryConfig.paths && window.galleryConfig.paths.cgi_script) {
cgiPath = window.galleryConfig.paths.cgi_script;
// Ensure it starts with a slash for fetch
if (!cgiPath.startsWith('/')) cgiPath = '/' + cgiPath;
}
try {
const response = await fetch('/cgi-bin/refresh_gallery.py', {
const response = await fetch(cgiPath, {
method: 'POST',
headers: {
'Content-Type': 'application/json',