feat: add folder picker for automatic project file loading

- Add "Load Project Folder" button with folder selection (webkitdirectory)
- Automatically load all project files (JSON, CSV, SVG, CSS) from selected folder
- Eliminate need to manually upload each file individually
- Show clear errors if referenced files are missing from folder
- Update all documentation to explain folder picker usage

This solves the browser security limitation where uploading a single
project.json doesn't allow automatic access to other files in the same
directory. Users can now select an entire project folder and all files
load automatically in one click.

Changes:
- index.html: Add folder input with webkitdirectory attribute and UI
- engine.js: Add folderInput event handler to process all files from folder
- README.md: Document folder picker as primary loading method
- WINDOWS_USAGE.md: Add folder picker as recommended Option 1
- TROUBLESHOOTING.md: Add section explaining project files not auto-loading
- CLAUDE.md: Document folder picker architecture for future instances
- Makefile: Update DIST_README.md template to mention folder picker

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-23 16:34:35 +01:00
parent 39037587ba
commit cefbf96a82
7 changed files with 180 additions and 6 deletions

103
engine.js
View File

@@ -668,6 +668,109 @@ window.svgViewer = {
// --------- UI event handlers ---------
window.setupEventHandlers = function() {
// Handler for loading entire project folder
const folderInput = document.getElementById("folderInput");
if (folderInput) {
folderInput.addEventListener("change", async (ev) => {
const files = Array.from(ev.target.files);
if (!files.length) return;
console.log("Folder selected with", files.length, "files");
try {
// Find project.json in the uploaded files
const projectFile = files.find(f => f.name === 'project.json');
if (!projectFile) {
alert('No project.json found in selected folder. Please select a folder containing a project.json file.');
return;
}
// Parse project.json
const projectText = await projectFile.text();
const cfg = JSON.parse(projectText);
console.log("Loaded project configuration:", cfg);
// Clear projectBasePath since we're loading from uploaded files
window.timelineEngine.projectBasePath = '';
// Update project status
window.timelineEngine.updateFileStatus('project', projectFile.name, 'loaded');
// Load referenced files from the folder
const errors = [];
// Load stylesheet
if (cfg.stylesheet) {
const cssFile = files.find(f => f.name === cfg.stylesheet || f.webkitRelativePath.endsWith(cfg.stylesheet));
if (cssFile) {
const cssText = await cssFile.text();
window.timelineEngine.cssOverride = true;
const blob = new Blob([cssText], { type: "text/css" });
document.getElementById("dynamicCss").href = URL.createObjectURL(blob);
window.timelineEngine.updateFileStatus('css', cssFile.name, 'loaded');
console.log("Loaded stylesheet:", cssFile.name);
} else {
errors.push(`Stylesheet: ${cfg.stylesheet}`);
window.timelineEngine.updateFileStatus('css', cfg.stylesheet, 'error');
}
}
// Load SVG template
if (cfg.svgTemplate) {
const svgFile = files.find(f => f.name === cfg.svgTemplate || f.webkitRelativePath.endsWith(cfg.svgTemplate));
if (svgFile) {
window.timelineEngine.template = await svgFile.text();
window.timelineEngine.updateFileStatus('svg', svgFile.name, 'loaded');
window.timelineEngine.showTemplateFields();
console.log("Loaded SVG template:", svgFile.name);
} else {
errors.push(`SVG template: ${cfg.svgTemplate}`);
window.timelineEngine.updateFileStatus('svg', cfg.svgTemplate, 'error');
}
}
// Load CSV data
if (cfg.dataSource) {
const csvFile = files.find(f => f.name === cfg.dataSource || f.webkitRelativePath.endsWith(cfg.dataSource));
if (csvFile) {
const csvText = await csvFile.text();
window.timelineEngine.csvOverride = true;
window.timelineEngine.updateFileStatus('csv', csvFile.name, 'loaded');
console.log("Loaded CSV data:", csvFile.name);
// Set config and process CSV
window.timelineEngine.config = cfg;
window.timelineEngine.processCsv(csvText);
} else {
errors.push(`CSV data: ${cfg.dataSource}`);
window.timelineEngine.updateFileStatus('csv', cfg.dataSource, 'error');
}
}
// Update project name and description
const name = cfg.name || "Timeline";
document.getElementById("projectName").textContent = name;
document.getElementById("projectSubtitle").textContent = cfg.description || "Project loaded from folder.";
// Show field mappings
window.timelineEngine.showFieldMappings();
// Show any errors
if (errors.length > 0) {
setTimeout(() => {
alert(`⚠️ Some files could not be loaded:\n\n${errors.join('\n')}\n\nMake sure all referenced files are in the selected folder.`);
}, 500);
} else {
console.log("✅ All project files loaded successfully from folder");
}
} catch (error) {
console.error("Error loading project folder:", error);
alert(`Error loading project: ${error.message}`);
}
});
}
const projectInput = document.getElementById("projectInput");
if (projectInput) {
projectInput.addEventListener("change", async (ev) => {