generated from coulomb/repo-seed
feat: add inline file editing with modification tracking and save functionality
Add comprehensive inline editing capabilities for all project files with visual modification tracking and batch save functionality. Features: - Edit buttons next to each file load button (Project, SVG, CSS, CSV) - Modal editor with syntax validation for JSON files - Real-time preview updates after applying changes - Visual "MODIFIED" badges on edited files - "Save Changes" button to download all modified files - Keyboard shortcuts (Escape to close editor) Implementation: - file-editor.js: New module handling all editor functionality - Editor modal with textarea for content editing - Modification tracking using Set data structure - File-specific validation (JSON syntax checking) - Batch download of all modified files - Visual badge updates for modified state - index.html: UI updates - Edit buttons added to all file items - Modal HTML structure for editor - Save Changes button in controls area - CSS styling for editor modal and modified badges - engine.js: Integration and data storage - Store CSV and CSS content when loaded (for editing) - Enable edit buttons when files are successfully loaded - Works with all loading methods (folder picker, individual files, auto-load) - CSS now loaded via fetch to store content (changed from link href) - Makefile: Include file-editor.js in distribution build User workflow: 1. Load project files (folder picker or individual files) 2. Click "✏️ Edit" button next to any file 3. Make changes in modal editor 4. Click "Apply Changes" to update and see immediate preview 5. Modified files show orange "MODIFIED" badge 6. Click "💾 Save Changes" to download all modified files at once Technical details: - Edit buttons start disabled, enabled when file loads - JSON validation prevents saving invalid configurations - Changes apply immediately to in-memory data - Timeline regenerates automatically after edits - Modified state persists until files are saved - Optional clearing of modified state after download Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
110
engine.js
110
engine.js
@@ -3,6 +3,8 @@ window.timelineEngine = {
|
||||
template: null,
|
||||
csvOverride: false,
|
||||
cssOverride: false,
|
||||
csvData: null, // Store current CSV text
|
||||
cssData: null, // Store current CSS text
|
||||
|
||||
projectBasePath: '',
|
||||
|
||||
@@ -190,6 +192,11 @@ window.timelineEngine = {
|
||||
// Update project status
|
||||
this.updateFileStatus('project', name, 'loaded');
|
||||
|
||||
// Enable project edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('project');
|
||||
}
|
||||
|
||||
// Show field mappings in debug panel
|
||||
this.showFieldMappings();
|
||||
|
||||
@@ -199,27 +206,35 @@ window.timelineEngine = {
|
||||
// Stylesheet
|
||||
if (cfg.stylesheet && !this.cssOverride) {
|
||||
const stylesheetPath = this.resolveProjectPath(cfg.stylesheet);
|
||||
const linkElement = document.getElementById("dynamicCss");
|
||||
|
||||
// Set up load/error event handlers before setting href
|
||||
const handleLoad = () => {
|
||||
console.log("Stylesheet loaded successfully:", stylesheetPath);
|
||||
this.updateFileStatus('css', cfg.stylesheet + ' ✨', 'loaded');
|
||||
linkElement.removeEventListener('load', handleLoad);
|
||||
linkElement.removeEventListener('error', handleError);
|
||||
};
|
||||
// Load CSS via fetch to store content for editing
|
||||
try {
|
||||
const response = await fetch(stylesheetPath);
|
||||
if (response.ok) {
|
||||
const cssText = await response.text();
|
||||
this.cssData = cssText; // Store for editing
|
||||
|
||||
const handleError = () => {
|
||||
console.error("Stylesheet could not be loaded:", stylesheetPath);
|
||||
// Apply CSS
|
||||
const blob = new Blob([cssText], { type: "text/css" });
|
||||
const linkElement = document.getElementById("dynamicCss");
|
||||
linkElement.href = URL.createObjectURL(blob);
|
||||
|
||||
this.updateFileStatus('css', cfg.stylesheet + ' ✨', 'loaded');
|
||||
|
||||
// Enable CSS edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('css');
|
||||
}
|
||||
|
||||
console.log("Stylesheet loaded successfully:", stylesheetPath);
|
||||
} else {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Stylesheet could not be loaded:", e);
|
||||
this.updateFileStatus('css', cfg.stylesheet, 'error');
|
||||
loadingErrors.push(`Stylesheet: ${cfg.stylesheet}`);
|
||||
linkElement.removeEventListener('load', handleLoad);
|
||||
linkElement.removeEventListener('error', handleError);
|
||||
};
|
||||
|
||||
linkElement.addEventListener('load', handleLoad);
|
||||
linkElement.addEventListener('error', handleError);
|
||||
linkElement.href = stylesheetPath;
|
||||
}
|
||||
}
|
||||
|
||||
// SVG template
|
||||
@@ -238,6 +253,11 @@ window.timelineEngine = {
|
||||
console.log("SVG template loaded, length:", this.template.length);
|
||||
this.updateFileStatus('svg', cfg.svgTemplate, 'loaded');
|
||||
|
||||
// Enable SVG edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('svg');
|
||||
}
|
||||
|
||||
// Show template fields in debug panel
|
||||
this.showTemplateFields();
|
||||
|
||||
@@ -270,6 +290,12 @@ window.timelineEngine = {
|
||||
console.log("CSV preview:", csvText.substring(0, 200));
|
||||
|
||||
this.updateFileStatus('csv', cfg.dataSource, 'loaded');
|
||||
|
||||
// Enable CSV edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('csv');
|
||||
}
|
||||
|
||||
this.processCsv(csvText);
|
||||
} catch (e) {
|
||||
console.error("CSV could not be loaded:", e);
|
||||
@@ -306,6 +332,9 @@ window.timelineEngine = {
|
||||
processCsv(text) {
|
||||
console.log("processCsv called with text length:", text?.length);
|
||||
|
||||
// Store CSV data for editing
|
||||
this.csvData = text;
|
||||
|
||||
if (!this.config || !this.config.fieldMapping) {
|
||||
console.error("No config or fieldMapping found.");
|
||||
return;
|
||||
@@ -696,6 +725,11 @@ window.setupEventHandlers = function() {
|
||||
// Update project status
|
||||
window.timelineEngine.updateFileStatus('project', projectFile.name, 'loaded');
|
||||
|
||||
// Enable project edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('project');
|
||||
}
|
||||
|
||||
// Load referenced files from the folder
|
||||
const errors = [];
|
||||
|
||||
@@ -704,10 +738,17 @@ window.setupEventHandlers = function() {
|
||||
const cssFile = files.find(f => f.name === cfg.stylesheet || f.webkitRelativePath.endsWith(cfg.stylesheet));
|
||||
if (cssFile) {
|
||||
const cssText = await cssFile.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', cssFile.name, 'loaded');
|
||||
|
||||
// Enable CSS edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('css');
|
||||
}
|
||||
|
||||
console.log("Loaded stylesheet:", cssFile.name);
|
||||
} else {
|
||||
errors.push(`Stylesheet: ${cfg.stylesheet}`);
|
||||
@@ -722,6 +763,12 @@ window.setupEventHandlers = function() {
|
||||
window.timelineEngine.template = await svgFile.text();
|
||||
window.timelineEngine.updateFileStatus('svg', svgFile.name, 'loaded');
|
||||
window.timelineEngine.showTemplateFields();
|
||||
|
||||
// Enable SVG edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('svg');
|
||||
}
|
||||
|
||||
console.log("Loaded SVG template:", svgFile.name);
|
||||
} else {
|
||||
errors.push(`SVG template: ${cfg.svgTemplate}`);
|
||||
@@ -736,6 +783,12 @@ window.setupEventHandlers = function() {
|
||||
const csvText = await csvFile.text();
|
||||
window.timelineEngine.csvOverride = true;
|
||||
window.timelineEngine.updateFileStatus('csv', csvFile.name, 'loaded');
|
||||
|
||||
// Enable CSV edit button
|
||||
if (window.fileEditor) {
|
||||
window.fileEditor.enableEditButton('csv');
|
||||
}
|
||||
|
||||
console.log("Loaded CSV data:", csvFile.name);
|
||||
|
||||
// Set config and process CSV
|
||||
@@ -800,6 +853,12 @@ window.setupEventHandlers = function() {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -824,6 +883,12 @@ window.setupEventHandlers = function() {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -834,10 +899,16 @@ window.setupEventHandlers = function() {
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -849,6 +920,11 @@ window.setupEventHandlers = function() {
|
||||
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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user