Files
timeline-svg/index.html
tegwick 5bec61740b feat: add DOM-based prototype template system for full Inkscape editability
Implements a new templating approach that allows complete visual control
in Inkscape while maintaining 100% valid SVG.

New Features:
- DOM-based generator using DOMParser and cloneNode()
- Prototype elements (month-proto, lane-proto, item-proto) instead of string templates
- Full WYSIWYG editing in Inkscape - see exactly how timeline will look
- Auto-detection of template type (prototype vs template-v2)
- Text element mapping via IDs (e.g., id="item-title")
- SVG transforms for positioning instead of placeholder replacement

Implementation:
- generator-dom.js: New DOM-based generator with cloning logic
- engine.js: Auto-detect template type and use appropriate generator
- example-proto/: Complete working example with prototype template
- PROTOTYPE_TEMPLATES.md: Comprehensive guide for creating prototype templates

Benefits:
- No string placeholders ({{PLACEHOLDER}}) needed
- Native SVG editing workflow
- Better performance (DOM manipulation vs regex)
- Easier maintenance and styling
- Backward compatible (old template-v2 still works)

Template Structure:
- Prototypes with specific IDs visible in SVG (hidden after cloning)
- Container groups for generated content
- CSS classes for styling
- Text elements with IDs matching field names

All 56 tests still passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 23:50:37 +01:00

526 lines
16 KiB
HTML
Raw Permalink 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="generator-dom.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>
<!-- 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>
</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 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>
<button class="edit-btn" id="editProjectBtn" disabled>✏️ Edit</button>
</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>
<button class="edit-btn" id="editSvgBtn" disabled>✏️ Edit</button>
</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>
<button class="edit-btn" id="editCssBtn" disabled>✏️ Edit</button>
</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>
<button class="edit-btn" id="editCsvBtn" disabled>✏️ Edit</button>
</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 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();
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>