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:
+161
-10
@@ -214,21 +214,37 @@
|
|||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-commands code {
|
.export-command-container {
|
||||||
background: var(--background-color);
|
margin: 20px 0;
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
display: inline-block;
|
border-radius: 8px;
|
||||||
margin-top: 8px;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-command {
|
||||||
|
background: var(--header-background);
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-command code {
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--text-color);
|
||||||
|
word-break: break-all;
|
||||||
|
display: block;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-actions {
|
.export-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
justify-content: flex-end;
|
padding: 12px 16px;
|
||||||
margin-top: 20px;
|
background: var(--card-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-actions button {
|
.export-actions button {
|
||||||
@@ -254,6 +270,124 @@
|
|||||||
.export-actions button:last-child:hover {
|
.export-actions button:last-child:hover {
|
||||||
background: var(--primary-color-dark, #0056b3);
|
background: var(--primary-color-dark, #0056b3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-btn, .close-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--card-background);
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: var(--primary-color, #007bff);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--primary-color, #007bff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border-color: #dc3545;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-details, .export-tips {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-details {
|
||||||
|
background: var(--header-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-tips {
|
||||||
|
background: var(--card-background);
|
||||||
|
border-color: var(--primary-color, #007bff);
|
||||||
|
border-left: 4px solid var(--primary-color, #007bff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-details h4, .export-tips h4 {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-details ul, .export-tips ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-details li, .export-tips li {
|
||||||
|
margin: 8px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-details code, .export-tips code {
|
||||||
|
background: var(--background-color);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-tips kbd {
|
||||||
|
background: var(--header-background);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-feedback {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 1003;
|
||||||
|
animation: fadeInOut 2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-feedback-success {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-feedback-error {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInOut {
|
||||||
|
0% { opacity: 0; transform: translateY(-10px); }
|
||||||
|
20% { opacity: 1; transform: translateY(0); }
|
||||||
|
80% { opacity: 1; transform: translateY(0); }
|
||||||
|
100% { opacity: 0; transform: translateY(-10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme adjustments */
|
||||||
[data-theme="dark"] .selection-checkbox {
|
[data-theme="dark"] .selection-checkbox {
|
||||||
background: var(--card-background);
|
background: var(--card-background);
|
||||||
border-color: var(--border-color);
|
border-color: var(--border-color);
|
||||||
@@ -287,3 +421,20 @@
|
|||||||
color: #f8d7da;
|
color: #f8d7da;
|
||||||
border-color: #a94442;
|
border-color: #a94442;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .copy-feedback-success {
|
||||||
|
background: #155724;
|
||||||
|
color: #d4edda;
|
||||||
|
border-color: #1e7e34;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .copy-feedback-error {
|
||||||
|
background: #721c24;
|
||||||
|
color: #f8d7da;
|
||||||
|
border-color: #a94442;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .export-tips kbd {
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|||||||
+143
-15
@@ -52,7 +52,7 @@ export class ExportManager {
|
|||||||
this.toggleSelectionMode();
|
this.toggleSelectionMode();
|
||||||
}
|
}
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
this.clearSelection();
|
this.exitSelectionMode();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,10 +118,17 @@ export class ExportManager {
|
|||||||
* Exit selection mode
|
* Exit selection mode
|
||||||
*/
|
*/
|
||||||
exitSelectionMode() {
|
exitSelectionMode() {
|
||||||
|
const wasInSelectionMode = document.body.classList.contains('selection-mode');
|
||||||
|
|
||||||
document.body.classList.remove('selection-mode');
|
document.body.classList.remove('selection-mode');
|
||||||
document.getElementById('exportBtn').style.display = 'none';
|
document.getElementById('exportBtn').style.display = 'none';
|
||||||
document.getElementById('selectionCounter').style.display = 'none';
|
document.getElementById('selectionCounter').style.display = 'none';
|
||||||
this.clearSelection();
|
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;
|
return url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
|
||||||
|
const outputName = `merged_plots_${timestamp}.pdf`;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
plots: actualPaths,
|
plots: actualPaths,
|
||||||
layout: this.calculateLayout(actualPaths.length),
|
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
|
// Save the request to a JSON file that can be picked up by a Python script
|
||||||
const requestData = JSON.stringify(payload, null, 2);
|
const requestData = JSON.stringify(payload, null, 2);
|
||||||
|
|
||||||
// Since we can't directly call Python from the browser, we'll show instructions
|
// Show improved export instructions with full command
|
||||||
this.showExportInstructions(requestData);
|
this.showExportInstructions(requestData, tempFileName, workDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,21 +274,49 @@ export class ExportManager {
|
|||||||
/**
|
/**
|
||||||
* Show export instructions to user
|
* 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 = `
|
const instructions = `
|
||||||
<div class="export-instructions">
|
<div class="export-instructions">
|
||||||
<h3>Export Instructions</h3>
|
<h3>🚀 Export Selected Plots</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>
|
<p>Run the following command in your terminal to export the selected plots:</p>
|
||||||
<div class="export-data">
|
|
||||||
<textarea readonly>${requestData}</textarea>
|
<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>
|
||||||
<div class="export-commands">
|
|
||||||
<p><strong>Command to run:</strong></p>
|
<div class="export-details">
|
||||||
<code>cd /work/kschmidt/web && python export_plots.py export_request.json</code>
|
<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>
|
||||||
<div class="export-actions">
|
|
||||||
<button onclick="this.parentElement.parentElement.parentElement.remove()">Close</button>
|
<div class="export-tips">
|
||||||
<button onclick="navigator.clipboard.writeText('${requestData.replace(/'/g, "\\'")}')">Copy JSON</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -280,7 +324,59 @@ export class ExportManager {
|
|||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.className = 'export-overlay';
|
overlay.className = 'export-overlay';
|
||||||
overlay.innerHTML = instructions;
|
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);
|
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);
|
}, 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
@@ -105,8 +105,16 @@ export class Utils {
|
|||||||
refreshBtn.disabled = true;
|
refreshBtn.disabled = true;
|
||||||
refreshBtn.textContent = '⏳';
|
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 {
|
try {
|
||||||
const response = await fetch('/cgi-bin/refresh_gallery.py', {
|
const response = await fetch(cgiPath, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
+29
-3
@@ -15,6 +15,8 @@ Features:
|
|||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
@@ -114,6 +116,7 @@ def build_gallery(source_dir: Path, web_dir: Path,
|
|||||||
current_metadata = merge_metadata(inherited_metadata, folder_metadata)
|
current_metadata = merge_metadata(inherited_metadata, folder_metadata)
|
||||||
|
|
||||||
pdf_files = list(source_dir.glob("*.pdf"))
|
pdf_files = list(source_dir.glob("*.pdf"))
|
||||||
|
|
||||||
subdirs = [d for d in source_dir.iterdir() if d.is_dir()]
|
subdirs = [d for d in source_dir.iterdir() if d.is_dir()]
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
@@ -188,16 +191,18 @@ def build_gallery(source_dir: Path, web_dir: Path,
|
|||||||
assets_path = "../" * (depth + 1) + "assets"
|
assets_path = "../" * (depth + 1) + "assets"
|
||||||
|
|
||||||
with output_html.open("w") as f:
|
with output_html.open("w") as f:
|
||||||
f.write(template.render(
|
rendered_html = template.render(
|
||||||
title=title,
|
title=title,
|
||||||
items=items,
|
items=items,
|
||||||
subdirs=subdir_names,
|
subdirs=subdir_names,
|
||||||
relpath=str(relative_path),
|
relpath=str(relative_path),
|
||||||
|
paths=CONFIG.paths,
|
||||||
ui=CONFIG.ui,
|
ui=CONFIG.ui,
|
||||||
stats=stats,
|
stats=stats,
|
||||||
folder_metadata=current_metadata,
|
folder_metadata=current_metadata,
|
||||||
assets_path=assets_path
|
assets_path=assets_path
|
||||||
))
|
)
|
||||||
|
f.write(rendered_html)
|
||||||
|
|
||||||
print(f"Generated {output_html}")
|
print(f"Generated {output_html}")
|
||||||
|
|
||||||
@@ -262,6 +267,21 @@ def format_file_size(size_bytes: int) -> str:
|
|||||||
return f"{size:.1f} {size_names[i]}"
|
return f"{size:.1f} {size_names[i]}"
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_gallery_cgi():
|
||||||
|
"""
|
||||||
|
CGI handler to refresh the gallery from a web request.
|
||||||
|
Outputs a minimal HTTP response and triggers gallery regeneration.
|
||||||
|
"""
|
||||||
|
import traceback
|
||||||
|
print("Content-Type: text/plain\n")
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
print("Gallery refreshed successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error refreshing gallery: {e}")
|
||||||
|
traceback.print_exc(file=sys.stdout)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""
|
"""
|
||||||
Main entry point for gallery generation.
|
Main entry point for gallery generation.
|
||||||
@@ -356,6 +376,7 @@ def main() -> None:
|
|||||||
items=items,
|
items=items,
|
||||||
subdirs=[],
|
subdirs=[],
|
||||||
relpath=source.name,
|
relpath=source.name,
|
||||||
|
paths=CONFIG.paths,
|
||||||
ui=CONFIG.ui,
|
ui=CONFIG.ui,
|
||||||
stats=stats,
|
stats=stats,
|
||||||
folder_metadata={},
|
folder_metadata={},
|
||||||
@@ -375,7 +396,12 @@ def main() -> None:
|
|||||||
f"directory nor a PDF file")
|
f"directory nor a PDF file")
|
||||||
|
|
||||||
print("Done")
|
print("Done")
|
||||||
|
# No copying of this script to CGI location
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
# If run as CGI, call the CGI handler
|
||||||
|
if 'GATEWAY_INTERFACE' in os.environ:
|
||||||
|
refresh_gallery_cgi()
|
||||||
|
else:
|
||||||
|
main()
|
||||||
|
|||||||
@@ -108,6 +108,10 @@
|
|||||||
<span>Export plots</span>
|
<span>Export plots</span>
|
||||||
<span class="shortcut-key">Ctrl+E</span>
|
<span class="shortcut-key">Ctrl+E</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="shortcut-item">
|
||||||
|
<span>Exit selection mode</span>
|
||||||
|
<span class="shortcut-key">Esc</span>
|
||||||
|
</div>
|
||||||
<div class="shortcut-item">
|
<div class="shortcut-item">
|
||||||
<span>Refresh</span>
|
<span>Refresh</span>
|
||||||
<span class="shortcut-key">F5</span>
|
<span class="shortcut-key">F5</span>
|
||||||
@@ -186,6 +190,7 @@
|
|||||||
window.galleryConfig = {
|
window.galleryConfig = {
|
||||||
searchDebounceMs: {{ ui.search_debounce_ms|default(300) }},
|
searchDebounceMs: {{ ui.search_debounce_ms|default(300) }},
|
||||||
maxRecentPlots: {{ ui.max_recent_plots|default(20) }},
|
maxRecentPlots: {{ ui.max_recent_plots|default(20) }},
|
||||||
|
workDir: "{{ paths.work_dir }}",
|
||||||
stats: {% if stats %}{{ stats|tojson }}{% else %}null{% endif %}
|
stats: {% if stats %}{{ stats|tojson }}{% else %}null{% endif %}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user