Remove pre-package-restructure cruft and dead features
Delete the top-level implementation superseded by the gallery/ package conversion (generate_gallery.py, orchestration/, root templates/ and assets/, python/ scripts), stray scratch files, and docs describing a container/GitLab-CI coverage workflow that no longer exists. Also drop two half-wired, never-invoked features: the backup_folder config/TUI option (create_backup() was never called from the pipeline) and the unfinished export-to-LaTeX JS/CSS. Update README's install instructions to the current Gitea remote.
This commit is contained in:
@@ -1,440 +0,0 @@
|
||||
/* ========================================
|
||||
EXPORT FUNCTIONALITY STYLES
|
||||
======================================== */
|
||||
|
||||
/* Selection mode styles */
|
||||
.selection-mode .grid-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.selection-mode .grid-item:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Selection overlay */
|
||||
.selection-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.selection-mode .selection-overlay {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.selection-checkbox {
|
||||
background: var(--card-background);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.grid-item.selected .selection-checkbox {
|
||||
background: var(--primary-color, #007bff);
|
||||
border-color: var(--primary-color, #007bff);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Selection counter */
|
||||
.selection-counter {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
right: 20px;
|
||||
background: var(--card-background);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* Export button styles */
|
||||
.export-btn {
|
||||
background: #28a745 !important;
|
||||
}
|
||||
|
||||
.export-btn:hover {
|
||||
background: #218838 !important;
|
||||
}
|
||||
|
||||
.export-btn:disabled {
|
||||
background: #6c757d !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Export messages */
|
||||
.export-message {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 24px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
z-index: 1001;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
.export-message-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.export-message-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.export-message-warning {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.export-message-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Selection mode indicator */
|
||||
.selection-mode::before {
|
||||
content: "Selection Mode - Click plots to select them";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--primary-color, #007bff);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Adjust main content when in selection mode */
|
||||
.selection-mode {
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
/* Export instructions overlay */
|
||||
.export-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1002;
|
||||
}
|
||||
|
||||
.export-instructions {
|
||||
background: var(--card-background);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.export-instructions h3 {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.export-instructions p {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.export-data {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.export-data textarea {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
background: var(--background-color);
|
||||
color: var(--text-color);
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.export-commands {
|
||||
margin: 16px 0;
|
||||
padding: 12px;
|
||||
background: var(--header-background);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.export-command-container {
|
||||
margin: 20px 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 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 {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: var(--card-background);
|
||||
}
|
||||
|
||||
.export-actions button {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
background: var(--card-background);
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.export-actions button:hover {
|
||||
background: var(--header-background);
|
||||
}
|
||||
|
||||
.export-actions button:last-child {
|
||||
background: var(--primary-color, #007bff);
|
||||
color: white;
|
||||
border-color: var(--primary-color, #007bff);
|
||||
}
|
||||
|
||||
.export-actions button:last-child:hover {
|
||||
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 {
|
||||
background: var(--card-background);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .grid-item.selected .selection-checkbox {
|
||||
background: var(--primary-color, #0d6efd);
|
||||
border-color: var(--primary-color, #0d6efd);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .export-message-info {
|
||||
background: #0c5460;
|
||||
color: #d1ecf1;
|
||||
border-color: #086972;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .export-message-success {
|
||||
background: #155724;
|
||||
color: #d4edda;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .export-message-warning {
|
||||
background: #856404;
|
||||
color: #fff3cd;
|
||||
border-color: #b58b14;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .export-message-error {
|
||||
background: #721c24;
|
||||
color: #f8d7da;
|
||||
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);
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
@import url('./metadata.css');
|
||||
@import url('./metadata-section.css');
|
||||
@import url('./folder-metadata.css');
|
||||
@import url('./export.css');
|
||||
|
||||
/* View controls - must come after grid.css to override */
|
||||
@import url('./view-controls.css');
|
||||
|
||||
@@ -1,444 +0,0 @@
|
||||
/**
|
||||
* Export Manager for Gallery
|
||||
* Handles exporting selected plots to merged PDF
|
||||
*/
|
||||
|
||||
export class ExportManager {
|
||||
constructor() {
|
||||
this.selectedPlots = new Set();
|
||||
this.maxPlots = 4;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createExportButton();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the export button in the floating buttons section
|
||||
*/
|
||||
createExportButton() {
|
||||
const floatingButtons = document.querySelector('.floating-buttons');
|
||||
if (!floatingButtons) return;
|
||||
|
||||
const exportBtn = document.createElement('button');
|
||||
exportBtn.className = 'floating-btn export-btn';
|
||||
exportBtn.id = 'exportBtn';
|
||||
exportBtn.title = 'Export Selected Plots (Ctrl+E)';
|
||||
exportBtn.innerHTML = '📄';
|
||||
exportBtn.style.display = 'none'; // Hidden by default
|
||||
exportBtn.onclick = () => this.exportSelectedPlots();
|
||||
|
||||
floatingButtons.appendChild(exportBtn);
|
||||
|
||||
// Add selection counter
|
||||
const selectionCounter = document.createElement('div');
|
||||
selectionCounter.className = 'selection-counter';
|
||||
selectionCounter.id = 'selectionCounter';
|
||||
selectionCounter.style.display = 'none';
|
||||
selectionCounter.innerHTML = '0/4 selected';
|
||||
floatingButtons.appendChild(selectionCounter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind events for plot selection
|
||||
*/
|
||||
bindEvents() {
|
||||
// Add selection mode toggle
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.key === 'e') {
|
||||
e.preventDefault();
|
||||
this.toggleSelectionMode();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
this.exitSelectionMode();
|
||||
}
|
||||
});
|
||||
|
||||
// Add selection handlers to existing plots
|
||||
this.addSelectionHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add selection handlers to all plot items
|
||||
*/
|
||||
addSelectionHandlers() {
|
||||
const plotItems = document.querySelectorAll('.grid-item');
|
||||
plotItems.forEach(item => this.addSelectionHandler(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add selection handler to a single plot item
|
||||
*/
|
||||
addSelectionHandler(item) {
|
||||
// Create selection overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'selection-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="selection-checkbox">
|
||||
<span class="checkbox-icon">☐</span>
|
||||
</div>
|
||||
`;
|
||||
item.appendChild(overlay);
|
||||
|
||||
// Add click handler for selection
|
||||
overlay.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.togglePlotSelection(item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle selection mode
|
||||
*/
|
||||
toggleSelectionMode() {
|
||||
const body = document.body;
|
||||
const isSelectionMode = body.classList.contains('selection-mode');
|
||||
|
||||
if (isSelectionMode) {
|
||||
this.exitSelectionMode();
|
||||
} else {
|
||||
this.enterSelectionMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter selection mode
|
||||
*/
|
||||
enterSelectionMode() {
|
||||
document.body.classList.add('selection-mode');
|
||||
document.getElementById('exportBtn').style.display = 'block';
|
||||
document.getElementById('selectionCounter').style.display = 'block';
|
||||
this.updateSelectionCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle plot selection
|
||||
*/
|
||||
togglePlotSelection(item) {
|
||||
const plotName = this.getPlotName(item);
|
||||
const plotPath = this.getPlotPath(item);
|
||||
|
||||
if (this.selectedPlots.has(plotName)) {
|
||||
this.selectedPlots.delete(plotName);
|
||||
item.classList.remove('selected');
|
||||
item.querySelector('.checkbox-icon').textContent = '☐';
|
||||
} else {
|
||||
if (this.selectedPlots.size >= this.maxPlots) {
|
||||
this.showMessage(`Maximum ${this.maxPlots} plots can be selected`, 'warning');
|
||||
return;
|
||||
}
|
||||
this.selectedPlots.add(plotName);
|
||||
item.classList.add('selected');
|
||||
item.querySelector('.checkbox-icon').textContent = '☑';
|
||||
}
|
||||
|
||||
this.updateSelectionCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plot name from grid item
|
||||
*/
|
||||
getPlotName(item) {
|
||||
const plotName = item.querySelector('.plot-name');
|
||||
return plotName ? plotName.textContent.trim() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plot PDF path from grid item
|
||||
*/
|
||||
getPlotPath(item) {
|
||||
const link = item.querySelector('a[href$=".pdf"]');
|
||||
return link ? link.href : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update selection counter
|
||||
*/
|
||||
updateSelectionCounter() {
|
||||
const counter = document.getElementById('selectionCounter');
|
||||
if (counter) {
|
||||
counter.textContent = `${this.selectedPlots.size}/${this.maxPlots} selected`;
|
||||
}
|
||||
|
||||
const exportBtn = document.getElementById('exportBtn');
|
||||
if (exportBtn) {
|
||||
exportBtn.disabled = this.selectedPlots.size === 0;
|
||||
exportBtn.style.opacity = this.selectedPlots.size === 0 ? '0.5' : '1';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all selections
|
||||
*/
|
||||
clearSelection() {
|
||||
this.selectedPlots.clear();
|
||||
document.querySelectorAll('.grid-item.selected').forEach(item => {
|
||||
item.classList.remove('selected');
|
||||
const checkbox = item.querySelector('.checkbox-icon');
|
||||
if (checkbox) checkbox.textContent = '☐';
|
||||
});
|
||||
this.updateSelectionCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export selected plots to merged PDF
|
||||
*/
|
||||
async exportSelectedPlots() {
|
||||
if (this.selectedPlots.size === 0) {
|
||||
this.showMessage('No plots selected', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const plotPaths = Array.from(this.selectedPlots).map(plotName => {
|
||||
const item = Array.from(document.querySelectorAll('.grid-item'))
|
||||
.find(item => this.getPlotName(item) === plotName);
|
||||
return this.getPlotPath(item);
|
||||
});
|
||||
|
||||
this.showMessage('Preparing export...', 'info');
|
||||
|
||||
try {
|
||||
await this.createMergedPDF(plotPaths);
|
||||
} catch (error) {
|
||||
this.showMessage('Export failed: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create merged PDF using Python script
|
||||
*/
|
||||
async createMergedPDF(plotPaths) {
|
||||
// Convert file:// URLs to actual paths
|
||||
const actualPaths = plotPaths.map(url => {
|
||||
if (url.startsWith('file://')) {
|
||||
return url.substring(7); // Remove 'file://' prefix
|
||||
}
|
||||
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: outputName
|
||||
};
|
||||
|
||||
// Generate a unique temporary filename
|
||||
const tempFileName = `export_request_${Date.now()}.json`;
|
||||
|
||||
// Save the request to a JSON file that can be picked up by a Python script
|
||||
const requestData = JSON.stringify(payload, null, 2);
|
||||
|
||||
// Show improved export instructions with full command
|
||||
this.showExportInstructions(requestData, tempFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate optimal layout for given number of plots
|
||||
*/
|
||||
calculateLayout(numPlots) {
|
||||
switch (numPlots) {
|
||||
case 1: return { rows: 1, cols: 1 };
|
||||
case 2: return { rows: 1, cols: 2 };
|
||||
case 3: return { rows: 2, cols: 2 }; // 3 plots in 2x2 grid with one empty
|
||||
case 4: return { rows: 2, cols: 2 };
|
||||
default: return { rows: 2, cols: 2 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show export instructions to user
|
||||
*/
|
||||
showExportInstructions(requestData, tempFileName) {
|
||||
const tempFilePath = `/tmp/${tempFileName}`;
|
||||
const fullCommand = `echo '${requestData.replace(/'/g, "'\\''")}' > ${tempFilePath} && python export_plots.py ${tempFilePath}`;
|
||||
|
||||
const instructions = `
|
||||
<div class="export-instructions">
|
||||
<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-details">
|
||||
<h4>📋 Command Breakdown:</h4>
|
||||
<ul>
|
||||
<li><strong>Creates temporary file:</strong> <code>${tempFilePath}</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-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>
|
||||
`;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the PDF blob
|
||||
*/
|
||||
downloadPDF(blob) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `merged_plots_${new Date().toISOString().split('T')[0]}.pdf`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show temporary message to user
|
||||
*/
|
||||
showMessage(text, type = 'info') {
|
||||
// Remove existing message
|
||||
const existing = document.querySelector('.export-message');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const message = document.createElement('div');
|
||||
message.className = `export-message export-message-${type}`;
|
||||
message.textContent = text;
|
||||
|
||||
document.body.appendChild(message);
|
||||
|
||||
setTimeout(() => {
|
||||
if (message.parentNode) {
|
||||
message.parentNode.removeChild(message);
|
||||
}
|
||||
}, 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();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -167,7 +167,6 @@ class GalleryConfig:
|
||||
plot_root: str = GalleryDefaults.plot_root
|
||||
cache_enabled: bool = GalleryDefaults.cache_enabled
|
||||
inherit_from_parent: bool = GalleryDefaults.inherit_from_parent
|
||||
backup_folder: str = ""
|
||||
|
||||
def __post_init__(self):
|
||||
if isinstance(self.web_folder, str):
|
||||
@@ -215,7 +214,6 @@ class GalleryConfig:
|
||||
plot_root=gallery_cfg.get("plot_root", GalleryDefaults.plot_root),
|
||||
cache_enabled=metadata_cfg.get("cache_enabled", GalleryDefaults.cache_enabled),
|
||||
inherit_from_parent=metadata_cfg.get("inherit_from_parent", GalleryDefaults.inherit_from_parent),
|
||||
backup_folder=gallery_cfg.get("backup_folder", ""),
|
||||
)
|
||||
|
||||
def to_yaml(self, yaml_file: Union[str, Path]) -> None:
|
||||
@@ -229,7 +227,6 @@ class GalleryConfig:
|
||||
"gallery": {
|
||||
"plot_root": self.plot_root,
|
||||
"png_dpi": self.png_dpi,
|
||||
"backup_folder": self.backup_folder,
|
||||
},
|
||||
"ui": {
|
||||
"max_recent_plots": 20,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
gallery:
|
||||
backup_folder: ''
|
||||
plot_root: gallery
|
||||
png_dpi: 400
|
||||
metadata:
|
||||
|
||||
@@ -41,7 +41,6 @@ CONFIG_FIELDS = [
|
||||
("web-folder", "paths.web_folder", True),
|
||||
("plot-root", "gallery.plot_root", False),
|
||||
("png-dpi", "gallery.png_dpi", False),
|
||||
("backup-folder", "gallery.backup_folder", False),
|
||||
("cache-enabled", "metadata.cache_enabled", False),
|
||||
("inherit-meta", "metadata.inherit_from_parent", False),
|
||||
]
|
||||
@@ -250,9 +249,6 @@ class GalleryTUI(App):
|
||||
with Horizontal(classes="field-row"):
|
||||
yield Label("PNG DPI")
|
||||
yield Input(id="png-dpi", placeholder="400")
|
||||
with Horizontal(classes="field-row"):
|
||||
yield Label("Backup folder")
|
||||
yield Input(id="backup-folder", placeholder="leave empty to disable")
|
||||
with Horizontal(classes="field-row"):
|
||||
yield Label("Cache metadata")
|
||||
yield Input(id="cache-enabled", placeholder="true")
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
"""Backup utilities for gallery."""
|
||||
|
||||
import datetime
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def create_backup(web_folder: Path, backup_folder: Path) -> bool:
|
||||
"""
|
||||
Create a backup of the web folder.
|
||||
|
||||
Args:
|
||||
web_folder: Path to the web folder to backup
|
||||
backup_folder: Path to the backup directory
|
||||
|
||||
Returns:
|
||||
True if backup was created successfully, False otherwise
|
||||
"""
|
||||
try:
|
||||
today = datetime.date.today().strftime("%Y%m%d")
|
||||
backup_name = f"backup-{today}.zip"
|
||||
backup_path = backup_folder / backup_name
|
||||
|
||||
backup_folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if backup_path.exists():
|
||||
return True
|
||||
|
||||
with zipfile.ZipFile(backup_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
for path in web_folder.rglob("*"):
|
||||
if path.is_file():
|
||||
arcname = path.relative_to(web_folder.parent)
|
||||
zipf.write(path, arcname)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not create backup: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user