Refactor symbols app: cleanup and fix issues
- Removed duplicate files (index2/3/4.html, symbols.js duplicates) - Kept index4.html as the main index.html (modular version) - Removed old text-generator.js (replaced by modular version) - Fixed ID mismatch in ui-bindings.js to match HTML - Added square and circle shape support in svg-generator.js - Added legend preview with copy functionality - Removed 580 lines of obsolete text-generator v4 code from app.js - Added addTextToLegend and addStandaloneArrowToLegend to export.js Still TODO: Split large files to comply with 300 line limit - app.js: 1219 lines - styles.css: 1319 lines - symbols.js: 870 lines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -445,6 +445,7 @@ function openLegendModal() {
|
||||
const modal = document.getElementById('legendModal');
|
||||
modal.classList.add('active');
|
||||
renderLegendEditor();
|
||||
updateLegendPreview();
|
||||
}
|
||||
|
||||
function closeLegendModal() {
|
||||
@@ -454,34 +455,38 @@ function closeLegendModal() {
|
||||
|
||||
function renderLegendEditor() {
|
||||
const container = document.getElementById('legendItems');
|
||||
|
||||
|
||||
if (legendItems.length === 0) {
|
||||
container.innerHTML = '<div class="legend-empty">Keine Symbole in der Legende. Klicken Sie auf 📑 bei einem Symbol, um es hinzuzufügen.</div>';
|
||||
updateLegendPreview();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
container.innerHTML = legendItems.map((item, index) => `
|
||||
<div class="legend-item" data-index="${index}">
|
||||
<div class="legend-item-preview">${item.svg}</div>
|
||||
<div class="legend-item-content">
|
||||
<input type="text" class="legend-item-name" value="${item.name}"
|
||||
onchange="updateLegendItem(${index}, 'name', this.value)" placeholder="Name">
|
||||
<input type="text" class="legend-item-desc" value="${item.description || ''}"
|
||||
onchange="updateLegendItem(${index}, 'description', this.value)" placeholder="Beschreibung (optional)">
|
||||
<input type="text" class="legend-item-name" value="${item.name}"
|
||||
oninput="updateLegendItem(${index}, 'name', this.value)" placeholder="Name">
|
||||
<input type="text" class="legend-item-desc" value="${item.description || ''}"
|
||||
oninput="updateLegendItem(${index}, 'description', this.value)" placeholder="Beschreibung (optional)">
|
||||
</div>
|
||||
<div class="legend-item-actions">
|
||||
<button onclick="moveLegendItem(${index}, -1)" title="Nach oben" ${index === 0 ? 'disabled' : ''}>▲</button>
|
||||
<button onclick="moveLegendItem(${index}, 1)" title="Nach unten" ${index === legendItems.length - 1 ? 'disabled' : ''}>▼</button>
|
||||
<button onclick="removeLegendItem(${index})" title="Entfernen" class="btn-remove">✕</button>
|
||||
<button type="button" onclick="moveLegendItem(${index}, -1)" title="Nach oben" ${index === 0 ? 'disabled' : ''}>▲</button>
|
||||
<button type="button" onclick="moveLegendItem(${index}, 1)" title="Nach unten" ${index === legendItems.length - 1 ? 'disabled' : ''}>▼</button>
|
||||
<button type="button" onclick="removeLegendItem(${index})" title="Entfernen" class="btn-remove">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
updateLegendPreview();
|
||||
}
|
||||
|
||||
function updateLegendItem(index, field, value) {
|
||||
if (legendItems[index]) {
|
||||
legendItems[index][field] = value;
|
||||
saveLegendToStorage();
|
||||
updateLegendPreview();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,28 +552,130 @@ function loadLegendFromStorage() {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== LEGENDE VORSCHAU ==========
|
||||
function generateLegendSVG() {
|
||||
if (legendItems.length === 0) return null;
|
||||
|
||||
const itemHeight = 50;
|
||||
const width = 320;
|
||||
const height = legendItems.length * itemHeight + 60;
|
||||
|
||||
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
||||
<rect width="${width}" height="${height}" fill="white"/>
|
||||
<text x="20" y="30" font-family="Arial" font-size="18" font-weight="bold">Legende</text>
|
||||
<line x1="20" y1="40" x2="${width - 20}" y2="40" stroke="#ccc" stroke-width="1"/>`;
|
||||
|
||||
legendItems.forEach((item, index) => {
|
||||
const y = 60 + index * itemHeight;
|
||||
svg += `<g transform="translate(20, ${y})">
|
||||
<g transform="scale(0.5)">${item.svg.replace(/<svg[^>]*>/, '').replace('</svg>', '')}</g>
|
||||
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${escapeHtml(item.name)}</text>
|
||||
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${escapeHtml(item.description)}</text>` : ''}
|
||||
</g>`;
|
||||
});
|
||||
|
||||
svg += '</svg>';
|
||||
return svg;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function updateLegendPreview() {
|
||||
const previewBox = document.getElementById('legendPreviewBox');
|
||||
if (!previewBox) return;
|
||||
|
||||
if (legendItems.length === 0) {
|
||||
previewBox.innerHTML = '<div class="legend-preview-empty">Keine Eintraege vorhanden</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const svg = generateLegendSVG();
|
||||
if (svg) {
|
||||
previewBox.innerHTML = svg;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyLegendAsImage() {
|
||||
if (legendItems.length === 0) {
|
||||
showNotification('Legende ist leer', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const svg = generateLegendSVG();
|
||||
if (!svg) return;
|
||||
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const itemHeight = 50;
|
||||
const width = 320;
|
||||
const height = legendItems.length * itemHeight + 60;
|
||||
|
||||
canvas.width = width * 2;
|
||||
canvas.height = height * 2;
|
||||
ctx.scale(2, 2);
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
const img = new Image();
|
||||
const svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onload = resolve;
|
||||
img.onerror = reject;
|
||||
img.src = url;
|
||||
});
|
||||
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
canvas.toBlob(async (blob) => {
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({ 'image/png': blob })
|
||||
]);
|
||||
showNotification('Legende in Zwischenablage kopiert!');
|
||||
} catch (err) {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = 'legende.png';
|
||||
link.click();
|
||||
showNotification('Legende als PNG heruntergeladen');
|
||||
}
|
||||
}, 'image/png');
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Kopieren:', err);
|
||||
showNotification('Fehler beim Kopieren', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== LEGENDE EXPORTIEREN ==========
|
||||
function exportLegendSVG() {
|
||||
if (legendItems.length === 0) {
|
||||
showNotification('Legende ist leer', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const itemHeight = 50;
|
||||
const width = 400;
|
||||
const height = legendItems.length * itemHeight + 60;
|
||||
|
||||
|
||||
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
||||
<rect width="${width}" height="${height}" fill="white"/>
|
||||
<text x="20" y="30" font-family="Arial" font-size="18" font-weight="bold">Legende</text>
|
||||
<line x1="20" y1="40" x2="${width - 20}" y2="40" stroke="#ccc" stroke-width="1"/>`;
|
||||
|
||||
|
||||
legendItems.forEach((item, index) => {
|
||||
const y = 60 + index * itemHeight;
|
||||
svg += `<g transform="translate(20, ${y})">
|
||||
<g transform="scale(0.5)">${item.svg.replace(/<svg[^>]*>/, '').replace('</svg>', '')}</g>
|
||||
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${item.name}</text>
|
||||
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${item.description}</text>` : ''}
|
||||
<text x="50" y="20" font-family="Arial" font-size="14" font-weight="bold">${escapeHtml(item.name)}</text>
|
||||
${item.description ? `<text x="50" y="35" font-family="Arial" font-size="11" fill="#666">${escapeHtml(item.description)}</text>` : ''}
|
||||
</g>`;
|
||||
});
|
||||
|
||||
@@ -838,586 +945,14 @@ function parseSvgPath(d, scaleX, flipY) {
|
||||
}
|
||||
|
||||
|
||||
// ========== TEXT-GENERATOR v4 ==========
|
||||
// Features: Farben, Skalierung, abknickbare Pfeile (korrigiert), Pfeilspitzengroesse, PNG/JPG, Speichern
|
||||
|
||||
var textGenState = {
|
||||
text: '',
|
||||
fontSize: 16,
|
||||
textColor: '#000000',
|
||||
frameColor: '#000000',
|
||||
shape: 'none',
|
||||
frameScale: 100,
|
||||
framePadding: 10,
|
||||
lineStyle: 'solid',
|
||||
lineWeight: 2,
|
||||
arrow: 'none',
|
||||
arrowLength: 40,
|
||||
arrowAngle: 0,
|
||||
arrowBend: 50,
|
||||
arrowSize: 10
|
||||
};
|
||||
|
||||
// Custom Symbols aus localStorage
|
||||
var customSymbols = [];
|
||||
|
||||
function initTextGenerator() {
|
||||
// Load custom symbols
|
||||
loadCustomSymbols();
|
||||
|
||||
var textInput = document.getElementById('customText');
|
||||
var fontSizeInput = document.getElementById('fontSize');
|
||||
var fontSizeValue = document.getElementById('fontSizeValue');
|
||||
var textColorInput = document.getElementById('textColor');
|
||||
var textColorValue = document.getElementById('textColorValue');
|
||||
var frameColorInput = document.getElementById('frameColor');
|
||||
var frameColorValue = document.getElementById('frameColorValue');
|
||||
var frameScaleInput = document.getElementById('frameScale');
|
||||
var frameScaleValue = document.getElementById('frameScaleValue');
|
||||
var framePaddingInput = document.getElementById('framePadding');
|
||||
var framePaddingValue = document.getElementById('framePaddingValue');
|
||||
var arrowLengthInput = document.getElementById('arrowLength');
|
||||
var arrowLengthValue = document.getElementById('arrowLengthValue');
|
||||
var arrowAngleInput = document.getElementById('arrowAngle');
|
||||
var arrowAngleValue = document.getElementById('arrowAngleValue');
|
||||
var arrowBendInput = document.getElementById('arrowBend');
|
||||
var arrowBendValue = document.getElementById('arrowBendValue');
|
||||
var arrowSizeInput = document.getElementById('arrowSize');
|
||||
var arrowSizeValue = document.getElementById('arrowSizeValue');
|
||||
|
||||
// Text input
|
||||
if (textInput) {
|
||||
textInput.addEventListener('input', function(e) {
|
||||
textGenState.text = e.target.value;
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Font size
|
||||
if (fontSizeInput) {
|
||||
fontSizeInput.addEventListener('input', function(e) {
|
||||
textGenState.fontSize = parseInt(e.target.value);
|
||||
fontSizeValue.textContent = e.target.value + 'px';
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Text color
|
||||
if (textColorInput) {
|
||||
textColorInput.addEventListener('input', function(e) {
|
||||
textGenState.textColor = e.target.value;
|
||||
textColorValue.textContent = e.target.value;
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Frame color
|
||||
if (frameColorInput) {
|
||||
frameColorInput.addEventListener('input', function(e) {
|
||||
textGenState.frameColor = e.target.value;
|
||||
frameColorValue.textContent = e.target.value;
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Frame scale
|
||||
if (frameScaleInput) {
|
||||
frameScaleInput.addEventListener('input', function(e) {
|
||||
textGenState.frameScale = parseInt(e.target.value);
|
||||
frameScaleValue.textContent = e.target.value + '%';
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Frame padding
|
||||
if (framePaddingInput) {
|
||||
framePaddingInput.addEventListener('input', function(e) {
|
||||
textGenState.framePadding = parseInt(e.target.value);
|
||||
framePaddingValue.textContent = e.target.value + 'px';
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Arrow length
|
||||
if (arrowLengthInput) {
|
||||
arrowLengthInput.addEventListener('input', function(e) {
|
||||
textGenState.arrowLength = parseInt(e.target.value);
|
||||
arrowLengthValue.textContent = e.target.value + 'px';
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Arrow angle
|
||||
if (arrowAngleInput) {
|
||||
arrowAngleInput.addEventListener('input', function(e) {
|
||||
textGenState.arrowAngle = parseInt(e.target.value);
|
||||
arrowAngleValue.textContent = e.target.value + '\u00B0';
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Arrow bend position
|
||||
if (arrowBendInput) {
|
||||
arrowBendInput.addEventListener('input', function(e) {
|
||||
textGenState.arrowBend = parseInt(e.target.value);
|
||||
arrowBendValue.textContent = e.target.value + '%';
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Arrow size
|
||||
if (arrowSizeInput) {
|
||||
arrowSizeInput.addEventListener('input', function(e) {
|
||||
textGenState.arrowSize = parseInt(e.target.value);
|
||||
arrowSizeValue.textContent = e.target.value + 'px';
|
||||
updateTextPreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Shape buttons
|
||||
document.querySelectorAll('.shape-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.shape-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
textGenState.shape = btn.dataset.shape;
|
||||
updateFrameScaleVisibility();
|
||||
updateTextPreview();
|
||||
});
|
||||
});
|
||||
|
||||
// Line style buttons
|
||||
document.querySelectorAll('.line-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.line-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
textGenState.lineStyle = btn.dataset.style;
|
||||
updateTextPreview();
|
||||
});
|
||||
});
|
||||
|
||||
// Line weight buttons
|
||||
document.querySelectorAll('.weight-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.weight-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
textGenState.lineWeight = parseInt(btn.dataset.weight);
|
||||
updateTextPreview();
|
||||
});
|
||||
});
|
||||
|
||||
// Arrow buttons
|
||||
document.querySelectorAll('.arrow-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.arrow-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
textGenState.arrow = btn.dataset.arrow;
|
||||
updateArrowDetailsVisibility();
|
||||
updateTextPreview();
|
||||
});
|
||||
});
|
||||
|
||||
updateFrameScaleVisibility();
|
||||
updateArrowDetailsVisibility();
|
||||
updateTextPreview();
|
||||
}
|
||||
|
||||
function updateFrameScaleVisibility() {
|
||||
var row = document.getElementById('frameScaleRow');
|
||||
if (row) {
|
||||
row.style.display = textGenState.shape === 'none' ? 'none' : 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
function updateArrowDetailsVisibility() {
|
||||
var row = document.getElementById('arrowDetailsRow');
|
||||
if (row) {
|
||||
row.style.display = textGenState.arrow === 'none' ? 'none' : 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
// ========== TEXT-GENERATOR UI ==========
|
||||
function toggleTextGenerator() {
|
||||
var generator = document.querySelector('.text-generator');
|
||||
generator.classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
function getStrokeDasharray(style) {
|
||||
switch(style) {
|
||||
case 'dashed': return '8,4';
|
||||
case 'dotted': return '2,3';
|
||||
default: return 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function generateTextSVG() {
|
||||
var text = textGenState.text || 'Text';
|
||||
var fontSize = textGenState.fontSize;
|
||||
var textColor = textGenState.textColor;
|
||||
var frameColor = textGenState.frameColor;
|
||||
var shape = textGenState.shape;
|
||||
var frameScale = textGenState.frameScale / 100;
|
||||
var padding = textGenState.framePadding;
|
||||
var lineStyle = textGenState.lineStyle;
|
||||
var lineWeight = textGenState.lineWeight;
|
||||
var arrow = textGenState.arrow;
|
||||
var arrowLength = textGenState.arrowLength;
|
||||
var arrowAngle = textGenState.arrowAngle;
|
||||
var arrowBend = textGenState.arrowBend / 100;
|
||||
var arrowSize = textGenState.arrowSize;
|
||||
|
||||
// Zeilen aufteilen
|
||||
var lines = text.split('\n');
|
||||
if (lines.length === 0) lines = ['Text'];
|
||||
|
||||
// Berechne die laengste Zeile
|
||||
var maxLineLength = 0;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].length > maxLineLength) {
|
||||
maxLineLength = lines[i].length;
|
||||
}
|
||||
}
|
||||
if (maxLineLength === 0) maxLineLength = 4;
|
||||
|
||||
// Dimensionen berechnen
|
||||
var charWidth = fontSize * 0.6;
|
||||
var lineHeight = fontSize * 1.3;
|
||||
var textWidth = maxLineLength * charWidth;
|
||||
var textHeight = lines.length * lineHeight;
|
||||
|
||||
// Basis-SVG-Groesse (Text + Padding)
|
||||
var baseWidth = textWidth + (padding * 2);
|
||||
var baseHeight = textHeight + (padding * 2);
|
||||
|
||||
// Rahmen mit Skalierung
|
||||
var frameWidth = baseWidth * frameScale;
|
||||
var frameHeight = baseHeight * frameScale;
|
||||
|
||||
// Mindestgroesse
|
||||
if (frameWidth < 40) frameWidth = 40;
|
||||
if (frameHeight < 30) frameHeight = 30;
|
||||
|
||||
// Pfeil-Raum berechnen (inkl. Pfeilspitze)
|
||||
var arrowSpace = (arrow !== 'none') ? arrowLength + arrowSize + 5 : 0;
|
||||
|
||||
// SVG-Groesse mit Pfeil-Raum
|
||||
var svgWidth = frameWidth;
|
||||
var svgHeight = frameHeight;
|
||||
var offsetX = 0;
|
||||
var offsetY = 0;
|
||||
|
||||
if (arrow === 'left') {
|
||||
svgWidth += arrowSpace;
|
||||
offsetX = arrowSpace;
|
||||
} else if (arrow === 'right') {
|
||||
svgWidth += arrowSpace;
|
||||
} else if (arrow === 'top') {
|
||||
svgHeight += arrowSpace;
|
||||
offsetY = arrowSpace;
|
||||
} else if (arrow === 'bottom') {
|
||||
svgHeight += arrowSpace;
|
||||
}
|
||||
|
||||
var cx = offsetX + frameWidth / 2;
|
||||
var cy = offsetY + frameHeight / 2;
|
||||
|
||||
var dashArray = getStrokeDasharray(lineStyle);
|
||||
var dashAttr = dashArray !== 'none' ? ' stroke-dasharray="' + dashArray + '"' : '';
|
||||
|
||||
var shapeSvg = '';
|
||||
var shapeX = offsetX + lineWeight / 2;
|
||||
var shapeY = offsetY + lineWeight / 2;
|
||||
var shapeW = frameWidth - lineWeight;
|
||||
var shapeH = frameHeight - lineWeight;
|
||||
|
||||
switch(shape) {
|
||||
case 'rect':
|
||||
shapeSvg = '<rect x="' + shapeX + '" y="' + shapeY + '" width="' + shapeW + '" height="' + shapeH + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
|
||||
break;
|
||||
case 'square':
|
||||
var squareSize = Math.max(shapeW, shapeH);
|
||||
var sqX = cx - squareSize / 2;
|
||||
var sqY = cy - squareSize / 2;
|
||||
shapeSvg = '<rect x="' + sqX + '" y="' + sqY + '" width="' + squareSize + '" height="' + squareSize + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
|
||||
break;
|
||||
case 'circle':
|
||||
var radius = Math.max(shapeW, shapeH) / 2;
|
||||
shapeSvg = '<circle cx="' + cx + '" cy="' + cy + '" r="' + (radius - lineWeight / 2) + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
|
||||
break;
|
||||
case 'oval':
|
||||
var rx = shapeW / 2;
|
||||
var ry = shapeH / 2;
|
||||
shapeSvg = '<ellipse cx="' + cx + '" cy="' + cy + '" rx="' + (rx - lineWeight / 2) + '" ry="' + (ry - lineWeight / 2) + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
|
||||
break;
|
||||
case 'diamond':
|
||||
var halfW = shapeW / 2;
|
||||
var halfH = shapeH / 2;
|
||||
shapeSvg = '<polygon points="' + cx + ',' + (cy - halfH) + ' ' + (cx + halfW) + ',' + cy + ' ' + cx + ',' + (cy + halfH) + ' ' + (cx - halfW) + ',' + cy + '" fill="none" stroke="' + frameColor + '" stroke-width="' + lineWeight + '"' + dashAttr + '/>';
|
||||
break;
|
||||
}
|
||||
|
||||
// Pfeil generieren
|
||||
var arrowSvg = '';
|
||||
if (arrow !== 'none') {
|
||||
arrowSvg = generateArrowPath(arrow, cx, cy, frameWidth, frameHeight, offsetX, offsetY, arrowLength, arrowAngle, arrowBend, frameColor, lineWeight, dashAttr, arrowSize);
|
||||
}
|
||||
|
||||
// Text-Elemente erzeugen
|
||||
var textSvg = '<text x="' + cx + '" font-family="Arial, sans-serif" font-size="' + fontSize + '" fill="' + textColor + '" text-anchor="middle">';
|
||||
|
||||
var totalTextHeight = lines.length * lineHeight;
|
||||
var startY = cy - (totalTextHeight / 2) + (fontSize * 0.8);
|
||||
|
||||
for (var j = 0; j < lines.length; j++) {
|
||||
var lineY = startY + (j * lineHeight);
|
||||
var lineText = lines[j] || ' ';
|
||||
lineText = lineText.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
textSvg += '<tspan x="' + cx + '" y="' + lineY + '">' + lineText + '</tspan>';
|
||||
}
|
||||
textSvg += '</text>';
|
||||
|
||||
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + svgWidth + ' ' + svgHeight + '" width="' + svgWidth + '" height="' + svgHeight + '">' + shapeSvg + arrowSvg + textSvg + '</svg>';
|
||||
}
|
||||
|
||||
function generateArrowPath(direction, cx, cy, frameWidth, frameHeight, offsetX, offsetY, length, angle, bendPos, color, weight, dashAttr, arrowSize) {
|
||||
var startX, startY, midX, midY, endX, endY;
|
||||
var angleRad = angle * Math.PI / 180;
|
||||
|
||||
// Startpunkt am Rahmen
|
||||
switch(direction) {
|
||||
case 'top':
|
||||
startX = cx;
|
||||
startY = offsetY;
|
||||
// Erster Teil geht gerade nach oben bis zum Knickpunkt
|
||||
midX = startX;
|
||||
midY = startY - length * bendPos;
|
||||
// Zweiter Teil knickt ab
|
||||
endX = midX + Math.sin(angleRad) * (length * (1 - bendPos));
|
||||
endY = midY - Math.cos(angleRad) * (length * (1 - bendPos));
|
||||
break;
|
||||
case 'bottom':
|
||||
startX = cx;
|
||||
startY = offsetY + frameHeight;
|
||||
midX = startX;
|
||||
midY = startY + length * bendPos;
|
||||
endX = midX + Math.sin(angleRad) * (length * (1 - bendPos));
|
||||
endY = midY + Math.cos(angleRad) * (length * (1 - bendPos));
|
||||
break;
|
||||
case 'left':
|
||||
startX = offsetX;
|
||||
startY = cy;
|
||||
midX = startX - length * bendPos;
|
||||
midY = startY;
|
||||
endX = midX - Math.cos(angleRad) * (length * (1 - bendPos));
|
||||
endY = midY + Math.sin(angleRad) * (length * (1 - bendPos));
|
||||
break;
|
||||
case 'right':
|
||||
startX = offsetX + frameWidth;
|
||||
startY = cy;
|
||||
midX = startX + length * bendPos;
|
||||
midY = startY;
|
||||
endX = midX + Math.cos(angleRad) * (length * (1 - bendPos));
|
||||
endY = midY + Math.sin(angleRad) * (length * (1 - bendPos));
|
||||
break;
|
||||
}
|
||||
|
||||
// Pfad erstellen
|
||||
var pathD;
|
||||
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
|
||||
// Mit Knick
|
||||
pathD = 'M ' + startX + ' ' + startY + ' L ' + midX + ' ' + midY + ' L ' + endX + ' ' + endY;
|
||||
} else {
|
||||
// Ohne Knick - gerade Linie
|
||||
pathD = 'M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY;
|
||||
}
|
||||
|
||||
// Pfeilspitze: Richtung basiert auf dem LETZTEN Segment
|
||||
var lastSegmentStartX, lastSegmentStartY;
|
||||
if (angle !== 0 && bendPos > 0 && bendPos < 1) {
|
||||
// Letztes Segment ist von mid zu end
|
||||
lastSegmentStartX = midX;
|
||||
lastSegmentStartY = midY;
|
||||
} else {
|
||||
// Letztes Segment ist von start zu end
|
||||
lastSegmentStartX = startX;
|
||||
lastSegmentStartY = startY;
|
||||
}
|
||||
|
||||
// Winkel des letzten Segments berechnen
|
||||
var arrowHeadAngle = Math.atan2(endY - lastSegmentStartY, endX - lastSegmentStartX);
|
||||
|
||||
// Pfeilspitze Punkte
|
||||
var ah1x = endX - arrowSize * Math.cos(arrowHeadAngle - Math.PI / 6);
|
||||
var ah1y = endY - arrowSize * Math.sin(arrowHeadAngle - Math.PI / 6);
|
||||
var ah2x = endX - arrowSize * Math.cos(arrowHeadAngle + Math.PI / 6);
|
||||
var ah2y = endY - arrowSize * Math.sin(arrowHeadAngle + Math.PI / 6);
|
||||
|
||||
var arrowHead = '<polygon points="' + endX + ',' + endY + ' ' + ah1x + ',' + ah1y + ' ' + ah2x + ',' + ah2y + '" fill="' + color + '"/>';
|
||||
|
||||
return '<path d="' + pathD + '" fill="none" stroke="' + color + '" stroke-width="' + weight + '"' + dashAttr + '/>' + arrowHead;
|
||||
}
|
||||
|
||||
function updateTextPreview() {
|
||||
var preview = document.getElementById('textPreview');
|
||||
if (preview) {
|
||||
preview.innerHTML = generateTextSVG();
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfsfunktion: SVG zu Canvas rendern
|
||||
async function svgToCanvas(svg, scale) {
|
||||
var parser = new DOMParser();
|
||||
var svgDoc = parser.parseFromString(svg, 'image/svg+xml');
|
||||
var svgEl = svgDoc.documentElement;
|
||||
var svgWidth = parseFloat(svgEl.getAttribute('width')) || 100;
|
||||
var svgHeight = parseFloat(svgEl.getAttribute('height')) || 100;
|
||||
|
||||
if (!scale) {
|
||||
scale = Math.max(200 / Math.min(svgWidth, svgHeight), 1);
|
||||
scale = Math.min(scale, 512 / Math.max(svgWidth, svgHeight));
|
||||
}
|
||||
|
||||
var canvasWidth = Math.round(svgWidth * scale);
|
||||
var canvasHeight = Math.round(svgHeight * scale);
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
canvas.width = canvasWidth;
|
||||
canvas.height = canvasHeight;
|
||||
|
||||
var img = new Image();
|
||||
var svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
|
||||
var url = URL.createObjectURL(svgBlob);
|
||||
|
||||
await new Promise(function(resolve, reject) {
|
||||
img.onload = resolve;
|
||||
img.onerror = reject;
|
||||
img.src = url;
|
||||
});
|
||||
|
||||
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
async function copyTextAsImage() {
|
||||
var svg = generateTextSVG();
|
||||
|
||||
try {
|
||||
var canvas = await svgToCanvas(svg);
|
||||
|
||||
canvas.toBlob(async function(blob) {
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({ 'image/png': blob })
|
||||
]);
|
||||
showNotification('In Zwischenablage kopiert!');
|
||||
} catch (err) {
|
||||
var link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = 'text-symbol.png';
|
||||
link.click();
|
||||
showNotification('PNG heruntergeladen');
|
||||
}
|
||||
}, 'image/png');
|
||||
} catch (err) {
|
||||
console.error('Fehler:', err);
|
||||
showNotification('Fehler beim Kopieren', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function downloadTextSVG() {
|
||||
var svg = generateTextSVG();
|
||||
var blob = new Blob([svg], { type: 'image/svg+xml' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var link = document.createElement('a');
|
||||
link.href = url;
|
||||
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
|
||||
link.download = 'text-' + filename + '.svg';
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showNotification('SVG heruntergeladen!');
|
||||
}
|
||||
|
||||
async function downloadTextPNG() {
|
||||
var svg = generateTextSVG();
|
||||
try {
|
||||
var canvas = await svgToCanvas(svg, 2); // 2x Skalierung fuer bessere Qualitaet
|
||||
|
||||
canvas.toBlob(function(blob) {
|
||||
var link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
|
||||
link.download = 'text-' + filename + '.png';
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
showNotification('PNG heruntergeladen!');
|
||||
}, 'image/png');
|
||||
} catch (err) {
|
||||
console.error('Fehler:', err);
|
||||
showNotification('Fehler beim PNG-Export', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadTextJPG() {
|
||||
var svg = generateTextSVG();
|
||||
try {
|
||||
var canvas = await svgToCanvas(svg, 2);
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Weisser Hintergrund fuer JPG
|
||||
var tempCanvas = document.createElement('canvas');
|
||||
tempCanvas.width = canvas.width;
|
||||
tempCanvas.height = canvas.height;
|
||||
var tempCtx = tempCanvas.getContext('2d');
|
||||
tempCtx.fillStyle = '#FFFFFF';
|
||||
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
tempCtx.drawImage(canvas, 0, 0);
|
||||
|
||||
tempCanvas.toBlob(function(blob) {
|
||||
var link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
|
||||
link.download = 'text-' + filename + '.jpg';
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
showNotification('JPG heruntergeladen!');
|
||||
}, 'image/jpeg', 0.95);
|
||||
} catch (err) {
|
||||
console.error('Fehler:', err);
|
||||
showNotification('Fehler beim JPG-Export', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function downloadTextDXF() {
|
||||
var svg = generateTextSVG();
|
||||
var dxf = svgToDxf(svg, 1);
|
||||
var blob = new Blob([dxf], { type: 'application/dxf' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var link = document.createElement('a');
|
||||
link.href = url;
|
||||
var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol';
|
||||
link.download = 'text-' + filename + '.dxf';
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showNotification('DXF heruntergeladen!');
|
||||
}
|
||||
|
||||
function addTextToLegend() {
|
||||
var svg = generateTextSVG();
|
||||
var text = textGenState.text || 'Text';
|
||||
var displayName = text.replace(/\n/g, ' ').substring(0, 30);
|
||||
|
||||
legendItems.push({
|
||||
id: 'custom_text_' + Date.now(),
|
||||
name: displayName,
|
||||
svg: svg,
|
||||
description: ''
|
||||
});
|
||||
|
||||
updateLegendCount();
|
||||
saveLegendToStorage();
|
||||
showNotification('Zur Legende hinzugefuegt!');
|
||||
}
|
||||
// Custom Symbols aus localStorage
|
||||
var customSymbols = [];
|
||||
|
||||
// ========== CUSTOM SYMBOLS STORAGE ==========
|
||||
|
||||
|
||||
Reference in New Issue
Block a user