generated from coulomb/repo-seed
fix: initialize file editor and remove broken individual file uploads
Changes: - Add fileEditor.init() call in DOMContentLoaded to activate edit buttons - Remove individual file upload inputs (projectInput, svgInput, cssInput, csvInput) that had CORS issues when loading project configurations - Keep only the folder picker which works reliably - Update UI to emphasize folder picker as the primary loading method - Remove corresponding event handlers from engine.js - Remove tests for individual file upload functionality The folder picker loads all project files in one operation without CORS issues, while individual file uploads failed when trying to load referenced files (CSV, SVG, CSS) from the project.json. All 56 tests passing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
109
engine.js
109
engine.js
@@ -824,114 +824,7 @@ window.setupEventHandlers = function() {
|
||||
});
|
||||
}
|
||||
|
||||
const projectInput = document.getElementById("projectInput");
|
||||
if (projectInput) {
|
||||
projectInput.addEventListener("change", async (ev) => {
|
||||
const file = ev.target.files[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
const text = await file.text();
|
||||
const cfg = JSON.parse(text);
|
||||
|
||||
// For manually loaded projects, try to infer base path from filename
|
||||
// If it's example/project.json or binect/project.json, set appropriate base path
|
||||
const filename = file.name;
|
||||
if (filename === 'project.json') {
|
||||
// Try to detect if this is a known project by checking the config
|
||||
if (cfg.name && cfg.name.includes('Example')) {
|
||||
window.timelineEngine.projectBasePath = 'example/';
|
||||
console.log("Detected example project, setting base path to example/");
|
||||
} else if (cfg.name && (cfg.name.includes('My Project') || cfg.name.includes('Roadmap'))) {
|
||||
window.timelineEngine.projectBasePath = 'my-project/';
|
||||
console.log("Detected my-project, setting base path to my-project/");
|
||||
} else {
|
||||
window.timelineEngine.projectBasePath = '';
|
||||
console.log("Unknown project, clearing base path");
|
||||
}
|
||||
} else {
|
||||
window.timelineEngine.projectBasePath = '';
|
||||
}
|
||||
|
||||
window.timelineEngine.updateFileStatus('project', file.name, 'loaded');
|
||||
|
||||
// Enable project edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('project');
|
||||
}
|
||||
|
||||
await window.timelineEngine.loadProjectConfigObject(cfg);
|
||||
|
||||
// Show message about relative paths if project has data sources
|
||||
if (cfg.dataSource || cfg.stylesheet || cfg.svgTemplate) {
|
||||
const viewer = document.getElementById("viewer");
|
||||
if (viewer && (viewer.innerHTML.includes("could not be loaded") || viewer.innerHTML.includes("Keine gültigen Items"))) {
|
||||
viewer.innerHTML += "<br><br><em style='color:#6c757d; font-size:12px;'>💡 Hinweis: Stelle sicher, dass sich die referenzierten Dateien (CSV, CSS, SVG) im gleichen Verzeichnis wie die HTML-Datei befinden.</em>";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading project:", error);
|
||||
window.timelineEngine.updateFileStatus('project', file.name, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const csvInput = document.getElementById("csvInput");
|
||||
if (csvInput) {
|
||||
csvInput.addEventListener("change", async (ev) => {
|
||||
const file = ev.target.files[0];
|
||||
if (!file) return;
|
||||
const text = await file.text();
|
||||
window.timelineEngine.csvOverride = true;
|
||||
window.timelineEngine.updateFileStatus('csv', file.name, 'loaded');
|
||||
|
||||
// Enable CSV edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('csv');
|
||||
}
|
||||
|
||||
window.timelineEngine.processCsv(text);
|
||||
});
|
||||
}
|
||||
|
||||
const cssInput = document.getElementById("cssInput");
|
||||
if (cssInput) {
|
||||
cssInput.addEventListener("change", async (ev) => {
|
||||
const file = ev.target.files[0];
|
||||
if (!file) return;
|
||||
const cssText = await file.text();
|
||||
window.timelineEngine.cssData = cssText; // Store for editing
|
||||
window.timelineEngine.cssOverride = true;
|
||||
const blob = new Blob([cssText], { type: "text/css" });
|
||||
document.getElementById("dynamicCss").href = URL.createObjectURL(blob);
|
||||
window.timelineEngine.updateFileStatus('css', file.name, 'loaded');
|
||||
|
||||
// Enable CSS edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('css');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const svgInput = document.getElementById("svgInput");
|
||||
if (svgInput) {
|
||||
svgInput.addEventListener("change", async (ev) => {
|
||||
const file = ev.target.files[0];
|
||||
if (!file) return;
|
||||
window.timelineEngine.template = await file.text();
|
||||
window.timelineEngine.updateFileStatus('svg', file.name, 'loaded');
|
||||
|
||||
// Enable SVG edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('svg');
|
||||
}
|
||||
|
||||
// Show template fields in debug panel
|
||||
window.timelineEngine.showTemplateFields();
|
||||
|
||||
// Show template preview immediately when manually loaded
|
||||
window.timelineEngine.showTemplatePreview();
|
||||
});
|
||||
}
|
||||
// Individual file inputs removed - use folder picker instead
|
||||
|
||||
const downloadSvg = document.getElementById("downloadSvg");
|
||||
if (downloadSvg) {
|
||||
|
||||
59
index.html
59
index.html
@@ -330,34 +330,29 @@
|
||||
<div id="fileManager" style="margin-bottom:16px; padding:16px; background:#f8f9fa; border:1px solid #e9ecef; border-radius:8px;">
|
||||
<h3 style="margin:0 0 12px 0; font-size:14px; font-weight:600; color:#495057;">Project Files</h3>
|
||||
|
||||
<!-- Quick Load: Project Folder -->
|
||||
<div style="margin-bottom:16px; padding:12px; background:#e7f3ff; border:1px solid #b3d9ff; border-radius:6px;">
|
||||
<div style="display:flex; align-items:center; gap:12px;">
|
||||
<label class="upload-btn" style="background:#0066cc; flex-shrink:0;">
|
||||
<!-- Project Folder Picker -->
|
||||
<div style="margin-bottom:16px; padding:16px; background:#e7f3ff; border:2px solid #0066cc; border-radius:6px;">
|
||||
<div style="text-align:center; margin-bottom:8px;">
|
||||
<label class="upload-btn" style="background:#0066cc; font-size:14px; padding:10px 20px;">
|
||||
<input type="file" id="folderInput" webkitdirectory directory multiple style="display:none;" />
|
||||
📂 Load Project Folder
|
||||
</label>
|
||||
<span style="font-size:12px; color:#004080;">
|
||||
Select an entire project folder to load all files automatically
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<span style="font-size:13px; color:#004080; font-weight:500;">
|
||||
Select your project folder to load all files automatically
|
||||
</span><br>
|
||||
<span style="font-size:11px; color:#0066cc;">
|
||||
(project.json, template-v2.svg, style.css, sample.csv)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin:0 0 12px 0; text-align:center; color:#6c757d; font-size:12px;">
|
||||
— or load files individually —
|
||||
</div>
|
||||
|
||||
<div class="file-grid" style="display:grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap:12px; margin-bottom:16px;">
|
||||
<div class="file-item">
|
||||
<div class="file-header">
|
||||
<span class="file-label">Project Configuration</span>
|
||||
<div style="display:flex; gap:4px;">
|
||||
<label class="upload-btn">
|
||||
<input type="file" id="projectInput" accept=".json" style="display:none;" />
|
||||
📁 Load
|
||||
</label>
|
||||
<button class="edit-btn" id="editProjectBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<button class="edit-btn" id="editProjectBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<div class="file-status">
|
||||
<span id="projectFile" class="file-name">Not loaded</span>
|
||||
@@ -367,13 +362,7 @@
|
||||
<div class="file-item">
|
||||
<div class="file-header">
|
||||
<span class="file-label">SVG Template</span>
|
||||
<div style="display:flex; gap:4px;">
|
||||
<label class="upload-btn">
|
||||
<input type="file" id="svgInput" accept=".svg" style="display:none;" />
|
||||
🖼️ Load
|
||||
</label>
|
||||
<button class="edit-btn" id="editSvgBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<button class="edit-btn" id="editSvgBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<div class="file-status">
|
||||
<span id="svgFile" class="file-name">Not loaded</span>
|
||||
@@ -383,13 +372,7 @@
|
||||
<div class="file-item">
|
||||
<div class="file-header">
|
||||
<span class="file-label">Stylesheet</span>
|
||||
<div style="display:flex; gap:4px;">
|
||||
<label class="upload-btn">
|
||||
<input type="file" id="cssInput" accept=".css" style="display:none;" />
|
||||
🎨 Load
|
||||
</label>
|
||||
<button class="edit-btn" id="editCssBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<button class="edit-btn" id="editCssBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<div class="file-status">
|
||||
<span id="cssFile" class="file-name">Not loaded</span>
|
||||
@@ -399,13 +382,7 @@
|
||||
<div class="file-item">
|
||||
<div class="file-header">
|
||||
<span class="file-label">CSV Data</span>
|
||||
<div style="display:flex; gap:4px;">
|
||||
<label class="upload-btn">
|
||||
<input type="file" id="csvInput" accept=".csv" style="display:none;" />
|
||||
📊 Load
|
||||
</label>
|
||||
<button class="edit-btn" id="editCsvBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<button class="edit-btn" id="editCsvBtn" disabled>✏️ Edit</button>
|
||||
</div>
|
||||
<div class="file-status">
|
||||
<span id="csvFile" class="file-name">Not loaded</span>
|
||||
@@ -480,6 +457,12 @@
|
||||
console.log("Event handlers set up");
|
||||
}
|
||||
|
||||
// Initialize file editor
|
||||
if (window.fileEditor && typeof window.fileEditor.init === 'function') {
|
||||
window.fileEditor.init();
|
||||
console.log("File editor initialized");
|
||||
}
|
||||
|
||||
// Initialize zoom functionality
|
||||
if (window.svgViewer && typeof window.svgViewer.initializeZoom === 'function') {
|
||||
window.svgViewer.initializeZoom();
|
||||
|
||||
@@ -310,78 +310,7 @@ describe('Timeline Integration', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('DOM Event Handling', () => {
|
||||
it('should handle project file upload', async () => {
|
||||
const config = createSampleProject()
|
||||
const projectInput = document.createElement('input')
|
||||
projectInput.id = 'projectInput'
|
||||
document.body.appendChild(projectInput)
|
||||
|
||||
// Setup event handlers
|
||||
window.setupEventHandlers()
|
||||
|
||||
// Mock file reading
|
||||
const mockFile = new File([JSON.stringify(config)], 'project.json', { type: 'application/json' })
|
||||
mockFile.text = vi.fn().mockResolvedValue(JSON.stringify(config))
|
||||
|
||||
// Mock the fetch calls that loadProjectConfigObject will make
|
||||
mockFetch('/* test css */') // CSS
|
||||
mockFetch(createSampleTemplate())
|
||||
mockFetch(createSampleCSV())
|
||||
mockPapaParse()
|
||||
|
||||
// Simulate file selection
|
||||
Object.defineProperty(projectInput, 'files', {
|
||||
value: [mockFile],
|
||||
writable: false
|
||||
})
|
||||
|
||||
// Trigger the event
|
||||
const event = new Event('change')
|
||||
projectInput.dispatchEvent(event)
|
||||
|
||||
// Wait for async operations
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
expect(document.getElementById('projectName').textContent).toBe('Test Project')
|
||||
})
|
||||
|
||||
it('should handle CSV file upload', async () => {
|
||||
const config = createSampleProject()
|
||||
timelineEngine.config = config
|
||||
timelineEngine.template = createSampleTemplate() // Need template for generation
|
||||
|
||||
const csvInput = document.createElement('input')
|
||||
csvInput.id = 'csvInput'
|
||||
document.body.appendChild(csvInput)
|
||||
|
||||
// Setup event handlers
|
||||
window.setupEventHandlers()
|
||||
|
||||
const csvContent = createSampleCSV()
|
||||
const mockFile = new File([csvContent], 'data.csv', { type: 'text/csv' })
|
||||
mockFile.text = vi.fn().mockResolvedValue(csvContent)
|
||||
|
||||
global.Papa.parse.mockImplementation((text, options) => {
|
||||
options.complete({
|
||||
data: [{ ID: 'T-1', Title: 'Uploaded Task', Lane: 'Upload Lane', Due: '2025-01-15' }]
|
||||
})
|
||||
})
|
||||
|
||||
Object.defineProperty(csvInput, 'files', {
|
||||
value: [mockFile],
|
||||
writable: false
|
||||
})
|
||||
|
||||
const event = new Event('change')
|
||||
csvInput.dispatchEvent(event)
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
expect(timelineEngine.csvOverride).toBe(true)
|
||||
expect(document.getElementById('viewer').innerHTML).toContain('Uploaded Task')
|
||||
})
|
||||
})
|
||||
// DOM Event Handling tests removed - individual file uploads replaced with folder picker
|
||||
|
||||
describe('SVG Export', () => {
|
||||
it('should hide IDs in external view for export', async () => {
|
||||
|
||||
Reference in New Issue
Block a user