Styles updated. Bend notes support added
This commit is contained in:
@@ -1,32 +1,33 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
|
||||
const DEMO_TABS = `
|
||||
const DEMO_TABS = `
|
||||
4 6 -5 5 -4 4 3
|
||||
6 7 -6 6 -6 6 5
|
||||
4 4 4 5 5 5 5 5 -5 5 4
|
||||
6 7 -6 6 -6' 6 5
|
||||
4 4 4 5 5' 5 5 5 -5 5 4
|
||||
`;
|
||||
const MAX_LINES_PER_COLUMN = 10;
|
||||
|
||||
const HarmonicaTabGenerator = () => {
|
||||
const [input, setInput] = useState(DEMO_TABS);
|
||||
const [title, setTitle] = useState("My Harmonica Tab");
|
||||
const [maxLinesPerColumn, setMaxLinesPerColumn] = useState(10);
|
||||
const svgRef = useRef(null);
|
||||
|
||||
// Parse input and add + signs to positive numbers
|
||||
const parseInput = (text) => {
|
||||
const lines = text.split("\n");
|
||||
return lines.map((line) => {
|
||||
// Check if line contains tab notation (numbers with optional minus signs and spaces)
|
||||
// Check if line contains tab notation (numbers with optional minus signs, quotes for bends, and spaces)
|
||||
const isTabLine =
|
||||
/^[\s\d-]+$/.test(line.trim()) && line.trim().length > 0;
|
||||
/^[\s\d-'"]+$/.test(line.trim()) && line.trim().length > 0;
|
||||
|
||||
let processedContent = line;
|
||||
|
||||
// If it's a tab line, add + signs to positive numbers
|
||||
// If it's a tab line, add + signs to positive numbers (but preserve bend notation)
|
||||
if (isTabLine) {
|
||||
processedContent = line.replace(
|
||||
/(\s|^)(\d+)/g,
|
||||
(match, space, number) => {
|
||||
return space + "+" + number;
|
||||
/(\s|^)(\d+)(['"]?)/g,
|
||||
(match, space, number, bend) => {
|
||||
return space + "+" + number + bend;
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -38,10 +39,10 @@ const HarmonicaTabGenerator = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Calculate layout (columns based on max 10 lines per column)
|
||||
// Calculate layout (columns based on max lines per column)
|
||||
const calculateLayout = (parsedLines) => {
|
||||
const tabLines = parsedLines.filter((l) => l.isTab);
|
||||
const numColumns = Math.ceil(tabLines.length / MAX_LINES_PER_COLUMN);
|
||||
const numColumns = Math.ceil(tabLines.length / maxLinesPerColumn);
|
||||
|
||||
const columns = [];
|
||||
let currentIndex = 0;
|
||||
@@ -52,7 +53,7 @@ const HarmonicaTabGenerator = () => {
|
||||
|
||||
while (
|
||||
currentIndex < parsedLines.length &&
|
||||
tabCount < MAX_LINES_PER_COLUMN
|
||||
tabCount < maxLinesPerColumn
|
||||
) {
|
||||
const line = parsedLines[currentIndex];
|
||||
columnLines.push(line);
|
||||
@@ -76,6 +77,8 @@ const HarmonicaTabGenerator = () => {
|
||||
const lineHeight = 40;
|
||||
const fontSize = 28;
|
||||
const annotationFontSize = 12;
|
||||
const titleFontSize = 36;
|
||||
const titleHeight = 80;
|
||||
const columnWidth = 400;
|
||||
const padding = 40;
|
||||
const columnGap = 60;
|
||||
@@ -93,7 +96,8 @@ const HarmonicaTabGenerator = () => {
|
||||
columns.length * columnWidth +
|
||||
(columns.length - 1) * columnGap +
|
||||
padding * 2;
|
||||
const svgHeight = maxLinesInAnyColumn * lineHeight + padding * 2;
|
||||
const svgHeight =
|
||||
maxLinesInAnyColumn * lineHeight + padding * 2 + titleHeight;
|
||||
|
||||
// Export as PNG
|
||||
const exportAsPNG = () => {
|
||||
@@ -142,23 +146,109 @@ const HarmonicaTabGenerator = () => {
|
||||
return (
|
||||
<div className="gradient-bg">
|
||||
<div className="container">
|
||||
<h1 className="title">Harmonica Tabs Image Generator</h1>
|
||||
<h1 className="title">🎵 Harmonica Tabs Image Generator</h1>
|
||||
<p className="subtitle">
|
||||
Create beautiful, readable images of your harmonica tabs
|
||||
</p>
|
||||
|
||||
{/* Instructions Section - MOVED TO TOP */}
|
||||
<div className="card instructions-card">
|
||||
<h2 className="section-title">📋 How to Use</h2>
|
||||
<ul className="instructions">
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">1</span>
|
||||
<div className="instruction-content">
|
||||
<strong>Enter your tabs</strong> - Type your harmonica tab
|
||||
numbers with spaces
|
||||
</div>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">2</span>
|
||||
<div className="instruction-content">
|
||||
<strong>Auto-formatting</strong> - Positive numbers get a + sign
|
||||
automatically. Use ' or " for bends (e.g., -3', 4")
|
||||
</div>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">3</span>
|
||||
<div className="instruction-content">
|
||||
<strong>Add annotations</strong> - Plain text appears smaller
|
||||
and italicized
|
||||
</div>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">4</span>
|
||||
<div className="instruction-content">
|
||||
<strong>Customize layout</strong> - Set your title and rows per
|
||||
column
|
||||
</div>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">5</span>
|
||||
<div className="instruction-content">
|
||||
<strong>Download</strong> - Save as PNG or SVG when ready
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Settings Section */}
|
||||
<div className="card settings-card">
|
||||
<h2 className="section-title">⚙️ Settings</h2>
|
||||
<div className="settings-grid">
|
||||
<div className="setting-item">
|
||||
<label className="setting-label">
|
||||
Tab Title
|
||||
<span className="setting-hint">
|
||||
Appears at the top of your image
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="input-field"
|
||||
placeholder="Enter a title for your tabs..."
|
||||
/>
|
||||
</div>
|
||||
<div className="setting-item">
|
||||
<label className="setting-label">
|
||||
Rows Per Column
|
||||
<span className="setting-hint">
|
||||
How many lines before splitting into columns
|
||||
</span>
|
||||
</label>
|
||||
<div className="slider-container">
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
max="20"
|
||||
value={maxLinesPerColumn}
|
||||
onChange={(e) =>
|
||||
setMaxLinesPerColumn(parseInt(e.target.value))
|
||||
}
|
||||
className="slider"
|
||||
/>
|
||||
<span className="slider-value">{maxLinesPerColumn}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Section */}
|
||||
<div className="card">
|
||||
<h2 className="section-title">Input Your Tabs</h2>
|
||||
<h2 className="section-title">✏️ Input Your Tabs</h2>
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
className="textarea"
|
||||
placeholder="Enter your harmonica tabs here...
|
||||
|
||||
Example:
|
||||
4 6 -5 5 -4 4 3
|
||||
6 7 -6 6 -6 6 5"
|
||||
Example:
|
||||
4 6 -5 5 -4 4 3
|
||||
-3' 6 7 -6' 6 5
|
||||
Use ' or " for bends"
|
||||
rows={12}
|
||||
/>
|
||||
<div className="button-group">
|
||||
<button onClick={exportAsPNG} className="button button-primary">
|
||||
@@ -169,28 +259,45 @@ const HarmonicaTabGenerator = () => {
|
||||
</button>
|
||||
</div>
|
||||
<p className="tip">
|
||||
💡 Tip: Positive numbers will automatically get a + sign. Add text
|
||||
for annotations - they'll appear smaller.
|
||||
💡 <strong>Pro tip:</strong> Mix tab lines with text for
|
||||
annotations. Tab lines contain only numbers, spaces, and bend
|
||||
markers (' or ").
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Preview Section */}
|
||||
<div className="card">
|
||||
<h2 className="section-title">Preview</h2>
|
||||
<div className="card preview-card">
|
||||
<h2 className="section-title">👁️ Preview</h2>
|
||||
<div className="preview-container">
|
||||
<svg
|
||||
ref={svgRef}
|
||||
width={svgWidth}
|
||||
height={svgHeight}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ background: "#ffffff", display: "block" }}
|
||||
style={{ background: "#fffef5", display: "block" }}
|
||||
>
|
||||
{/* Background */}
|
||||
<rect width={svgWidth} height={svgHeight} fill="#ffffff" />
|
||||
{/* Background with soft cream color */}
|
||||
<rect width={svgWidth} height={svgHeight} fill="#fffef5" />
|
||||
|
||||
{/* Title - centered at top */}
|
||||
{title && (
|
||||
<text
|
||||
x={svgWidth / 2}
|
||||
y={padding + titleFontSize}
|
||||
fontFamily="'Segoe UI', 'Arial Rounded MT Bold', Arial, sans-serif"
|
||||
fontSize={titleFontSize}
|
||||
fontWeight="700"
|
||||
fill="#5c4a72"
|
||||
textAnchor="middle"
|
||||
letterSpacing="1"
|
||||
>
|
||||
{title}
|
||||
</text>
|
||||
)}
|
||||
|
||||
{/* Render columns */}
|
||||
{columns.map((column, colIndex) => {
|
||||
let yOffset = padding;
|
||||
let yOffset = padding + titleHeight;
|
||||
const xOffset = padding + colIndex * (columnWidth + columnGap);
|
||||
|
||||
return (
|
||||
@@ -199,7 +306,7 @@ const HarmonicaTabGenerator = () => {
|
||||
const currentY = yOffset;
|
||||
|
||||
if (line.isTab) {
|
||||
// Render tab line with monospace font
|
||||
// Render tab line with monospace font - softer color
|
||||
yOffset += lineHeight;
|
||||
return (
|
||||
<text
|
||||
@@ -209,14 +316,14 @@ const HarmonicaTabGenerator = () => {
|
||||
fontFamily="'Courier New', monospace"
|
||||
fontSize={fontSize}
|
||||
fontWeight="600"
|
||||
fill="#1f2937"
|
||||
fill="#3d5a6c"
|
||||
letterSpacing="2"
|
||||
>
|
||||
{line.content}
|
||||
</text>
|
||||
);
|
||||
} else if (line.content.trim()) {
|
||||
// Render annotation with smaller font
|
||||
// Render annotation with smaller font - soft gray
|
||||
yOffset += lineHeight * 0.6;
|
||||
return (
|
||||
<text
|
||||
@@ -226,7 +333,7 @@ const HarmonicaTabGenerator = () => {
|
||||
fontFamily="Arial, sans-serif"
|
||||
fontSize={annotationFontSize}
|
||||
fontStyle="italic"
|
||||
fill="#6b7280"
|
||||
fill="#8b7e99"
|
||||
>
|
||||
{line.content}
|
||||
</text>
|
||||
@@ -240,45 +347,6 @@ const HarmonicaTabGenerator = () => {
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="card">
|
||||
<h2 className="section-title">How to Use</h2>
|
||||
<ul className="instructions">
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">1.</span>
|
||||
<span>
|
||||
Enter your harmonica tabs in the input field (numbers with
|
||||
spaces)
|
||||
</span>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">2.</span>
|
||||
<span>
|
||||
Positive numbers automatically get a + sign, negative numbers
|
||||
keep the - sign
|
||||
</span>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">3.</span>
|
||||
<span>
|
||||
Add text annotations if needed - they'll appear smaller and
|
||||
italicized
|
||||
</span>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">4.</span>
|
||||
<span>
|
||||
Preview updates automatically - tabs are split into columns (max
|
||||
10 lines per column)
|
||||
</span>
|
||||
</li>
|
||||
<li className="instruction-item">
|
||||
<span className="instruction-number">5.</span>
|
||||
<span>Download as PNG for sharing or SVG for editing</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user