Files
timeline-svg/index.html
tegwick 4576d066b3 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>
2026-01-23 17:15:36 +01:00

542 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Timeline Generator</title>
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
<link id="dynamicCss" rel="stylesheet" href="">
<style>
/* File Manager Styling */
.file-item {
background: #fff;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 12px;
transition: all 0.2s ease;
}
.file-item:hover {
border-color: #495057;
box-shadow: 0 2px 8px rgba(73, 80, 87, 0.1);
}
.file-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.file-label {
font-weight: 600;
color: #495057;
font-size: 13px;
}
.upload-btn {
background: #495057;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
transition: background-color 0.2s;
border: none;
user-select: none;
}
.upload-btn:hover {
background: #343a40;
}
.edit-btn {
background: #6c757d;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
transition: background-color 0.2s;
border: none;
user-select: none;
}
.edit-btn:hover:not(:disabled) {
background: #545b62;
}
.edit-btn:disabled {
background: #e9ecef;
color: #adb5bd;
cursor: not-allowed;
}
.modified-badge {
display: inline-block;
background: #fd7e14;
color: white;
font-size: 9px;
padding: 2px 6px;
border-radius: 3px;
margin-left: 6px;
font-weight: 600;
}
.editor-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 10000;
padding: 20px;
overflow: auto;
}
.editor-content {
background: white;
max-width: 1200px;
margin: 0 auto;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
display: flex;
flex-direction: column;
max-height: 90vh;
}
.editor-header {
padding: 16px 20px;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
}
.editor-body {
padding: 20px;
flex: 1;
overflow: auto;
}
.editor-textarea {
width: 100%;
min-height: 400px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
font-size: 13px;
padding: 12px;
border: 1px solid #ced4da;
border-radius: 4px;
resize: vertical;
}
.editor-footer {
padding: 16px 20px;
border-top: 1px solid #dee2e6;
display: flex;
gap: 12px;
justify-content: flex-end;
}
.file-status {
border-top: 1px solid #f1f3f4;
padding-top: 8px;
}
.file-name {
font-family: 'Courier New', monospace;
font-size: 11px;
font-weight: 500;
display: block;
padding: 4px 8px;
border-radius: 4px;
background: #f8f9fa;
color: #6c757d;
font-style: italic;
transition: all 0.2s ease;
}
.file-name[style*="color: #28a745"] {
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724 !important;
font-style: normal;
font-weight: 600;
}
.file-name[style*="color: #dc3545"] {
background: #f8d7da;
border: 1px solid #f1b6bb;
color: #721c24 !important;
font-style: normal;
font-weight: 600;
}
.file-name[style*="color: #6c757d"] {
background: #f8f9fa;
border: 1px solid #e9ecef;
}
.controls {
animation: fadeInUp 0.3s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#fileManager {
transition: all 0.3s ease-in-out;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
@media (max-width: 768px) {
.file-grid {
grid-template-columns: 1fr !important;
}
.file-header {
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.upload-btn {
align-self: flex-end;
}
.controls {
flex-direction: column;
}
.controls button {
width: 100%;
}
}
/* Button improvements */
button {
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
button:disabled {
cursor: not-allowed;
opacity: 0.6;
}
/* SVG Viewer enhancements */
#viewerContainer {
position: relative;
border: 1px solid #ccd3db;
background: white;
border-radius: 8px;
min-height: 400px;
width: 100%;
overflow: hidden;
}
#viewer {
overflow: auto;
padding: 12px;
height: 100%;
max-height: 80vh;
transform-origin: top left;
transition: transform 0.2s ease;
width: 100%;
box-sizing: border-box;
min-width: 100%;
}
#viewer svg {
max-width: none !important;
height: auto !important;
width: auto !important;
display: block;
margin: 0;
min-width: 100%;
box-sizing: content-box;
}
/* Ensure zoom doesn't get clipped */
#viewer.zoomed {
overflow: visible;
width: fit-content;
height: fit-content;
max-height: none;
}
#zoomControls {
position: absolute;
top: 8px;
right: 8px;
z-index: 10;
background: rgba(255,255,255,0.95);
border-radius: 4px;
padding: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: none;
}
#zoomControls button {
padding: 4px 8px;
margin: 2px;
border: 1px solid #ccc;
border-radius: 3px;
background: white;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
}
#zoomControls button:hover:not(:disabled) {
background: #f0f0f0;
border-color: #999;
transform: none; /* Override global button transform */
}
#zoomControls button:disabled {
background: #f8f8f8;
color: #ccc;
cursor: not-allowed;
}
</style>
<script src="generator.js"></script>
<script src="engine.js"></script>
<script src="file-editor.js"></script>
</head>
<body class="internal-mode" style="font-family: Inter, Arial, sans-serif; background:#f5f7fa; margin:20px;">
<h1 id="projectName" style="color:#495057; margin-bottom:8px;">Timeline Generator</h1>
<p id="projectSubtitle" style="color:#5C6B7A; margin-top:0; margin-bottom:16px;">
Lade Projektdateien um eine Timeline zu erstellen oder verwende den lokalen Server für automatisches Laden.<br>
<small style="color:#6c757d;">💡 Zoom: Verwende die Zoom-Buttons oder Strg+Scrollrad für große Timelines.</small>
</p>
<!-- Integrated File Management -->
<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;">
<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
</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>
</div>
<div class="file-status">
<span id="projectFile" class="file-name">Not loaded</span>
</div>
</div>
<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>
</div>
<div class="file-status">
<span id="svgFile" class="file-name">Not loaded</span>
</div>
</div>
<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>
</div>
<div class="file-status">
<span id="cssFile" class="file-name">Not loaded</span>
</div>
</div>
<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>
</div>
<div class="file-status">
<span id="csvFile" class="file-name">Not loaded</span>
</div>
</div>
</div>
<div class="controls" style="display:flex; flex-wrap:wrap; gap:12px; justify-content:center; padding-top:12px; border-top:1px solid #dee2e6;">
<button id="toggleView" style="padding:8px 16px; background:#495057; color:white; border:none; border-radius:6px; cursor:pointer; font-size:12px;">
🔄 Switch View (Internal / External)
</button>
<button id="saveChanges" disabled
style="padding:8px 16px; background:#28a745; color:white; border:none; border-radius:6px; cursor:pointer; opacity:0.6; font-size:12px;">
💾 Save Changes
</button>
<button id="downloadSvg" disabled
style="padding:8px 16px; background:#495057; color:white; border:none; border-radius:6px; cursor:pointer; opacity:0.6; font-size:12px;">
📥 Download SVG
</button>
</div>
</div>
<!-- SVG Viewer with zoom and scroll capabilities -->
<div id="viewerContainer">
<!-- Zoom controls -->
<div id="zoomControls">
<button id="zoomIn">🔍+</button>
<button id="zoomOut">🔍-</button>
<button id="zoomReset">100%</button>
</div>
<!-- Scrollable viewer -->
<div id="viewer">
<div style="text-align:center; padding:40px 20px; color:#6c757d;">
<div style="font-size:48px; margin-bottom:16px;">📊</div>
<h4 style="margin:0 0 8px 0; color:#495057;">Keine Timeline verfügbar</h4>
<p style="margin:0; font-size:14px;">
Lade eine <strong>Projektkonfiguration</strong> oder <strong>CSV-Datei</strong> um zu beginnen.
</p>
</div>
</div>
</div>
<!-- Debug Information Panel -->
<div id="debugInfo" style="margin-top:16px; padding:16px; background:#f8f9fa; border:1px solid #e9ecef; border-radius:8px; display:none;">
<h3 style="margin:0 0 12px 0; font-size:14px; font-weight:600; color:#495057;">🔍 Debug Information</h3>
<!-- Field Mappings -->
<div id="fieldMappingInfo" style="display:none; margin-bottom:12px;">
<h4 style="margin:0 0 8px 0; font-size:13px; color:#6c757d; font-weight:600;">📋 Configured Field Mappings</h4>
<pre id="fieldMappingDisplay" style="background:#fff; padding:12px; border-radius:4px; border:1px solid #dee2e6; font-size:12px; margin:0; overflow-x:auto; font-family:'Courier New', monospace;"></pre>
</div>
<!-- Template Fields -->
<div id="templateFieldsInfo" style="display:none; margin-bottom:12px;">
<h4 style="margin:0 0 8px 0; font-size:13px; color:#6c757d; font-weight:600;">🖼️ Template Placeholders</h4>
<pre id="templateFieldsDisplay" style="background:#fff; padding:12px; border-radius:4px; border:1px solid #dee2e6; font-size:12px; margin:0; overflow-x:auto; font-family:'Courier New', monospace;"></pre>
</div>
<!-- CSV Data Preview -->
<div id="csvDataInfo" style="display:none;">
<h4 style="margin:0 0 8px 0; font-size:13px; color:#6c757d; font-weight:600;">📊 CSV Data Preview</h4>
<pre id="csvDataDisplay" style="background:#fff; padding:12px; border-radius:4px; border:1px solid #dee2e6; font-size:12px; margin:0; overflow-x:auto; font-family:'Courier New', monospace;"></pre>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Setup event handlers first
if (typeof setupEventHandlers === 'function') {
setupEventHandlers();
console.log("Event handlers set up");
}
// Initialize zoom functionality
if (window.svgViewer && typeof window.svgViewer.initializeZoom === 'function') {
window.svgViewer.initializeZoom();
console.log("SVG zoom initialized");
}
// Ensure engines are loaded before auto-loading
function tryAutoLoad() {
if (window.timelineEngine && window.timelineGenerator) {
console.log("Both engines loaded, starting auto-load");
// Only try auto-load if not running from file:// protocol
if (location.protocol !== 'file:') {
window.timelineEngine.autoLoadDefaultProject();
} else {
console.log("Running from file:// protocol - auto-load disabled due to CORS");
document.getElementById("viewer").innerHTML =
"<div style='text-align:center; padding:40px 20px; color:#6c757d;'>" +
"<div style='font-size:48px; margin-bottom:16px;'>📁</div>" +
"<h4 style='margin:0 0 8px 0; color:#495057;'>Manuelle Dateien laden</h4>" +
"<p style='margin:0; font-size:14px;'>" +
"Verwende die <strong>Load</strong>-Buttons oben um Projektdateien zu laden.<br>" +
"<small>💡 Tipp: Für automatisches Laden verwende einen lokalen Server (z.B. <code>make serve</code>)</small>" +
"</p></div>";
}
} else {
console.log("Engines not ready, retrying...", {
engine: !!window.timelineEngine,
generator: !!window.timelineGenerator
});
setTimeout(tryAutoLoad, 50);
}
}
tryAutoLoad();
});
</script>
<!-- Editor Modal -->
<div id="editorModal" class="editor-modal">
<div class="editor-content">
<div class="editor-header">
<h3 id="editorTitle" style="margin:0; font-size:16px; font-weight:600;">Edit File</h3>
<button onclick="window.fileEditor.closeEditor()" style="background:none; border:none; font-size:24px; cursor:pointer; color:#6c757d;">×</button>
</div>
<div class="editor-body">
<textarea id="editorTextarea" class="editor-textarea" spellcheck="false"></textarea>
</div>
<div class="editor-footer">
<button onclick="window.fileEditor.closeEditor()" style="padding:8px 16px; background:#6c757d; color:white; border:none; border-radius:4px; cursor:pointer;">
Cancel
</button>
<button onclick="window.fileEditor.applyChanges()" style="padding:8px 16px; background:#007bff; color:white; border:none; border-radius:4px; cursor:pointer;">
Apply Changes
</button>
</div>
</div>
</div>
</body>
</html>