@gblp/chord-finder 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -3,14 +3,33 @@
|
|
|
3
3
|
Standalone Angular 22 component for finding guitar chords and rendering SVG chord diagrams.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import { Component } from
|
|
7
|
-
import { ChordFinderComponent } from
|
|
6
|
+
import { Component } from "@angular/core";
|
|
7
|
+
import { ChordFinderComponent } from "@gblp/chord-finder";
|
|
8
8
|
|
|
9
9
|
@Component({
|
|
10
10
|
imports: [ChordFinderComponent],
|
|
11
|
-
template:
|
|
11
|
+
template: `<the-chords-chord-finder [language]="'es'" />`,
|
|
12
12
|
})
|
|
13
13
|
export class App {}
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
`language` accepts `en` or `es` and defaults to `en`.
|
|
17
|
+
|
|
18
|
+
Override the component theme from any ancestor with these inherited CSS variables:
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
--chords-background;
|
|
22
|
+
--chords-background-alt;
|
|
23
|
+
--chords-surface;
|
|
24
|
+
--chords-surface-elevated;
|
|
25
|
+
--chords-text;
|
|
26
|
+
--chords-muted;
|
|
27
|
+
--chords-primary;
|
|
28
|
+
--chords-secondary;
|
|
29
|
+
--chords-highlight;
|
|
30
|
+
--chords-danger;
|
|
31
|
+
--chords-border;
|
|
32
|
+
--chords-on-primary;
|
|
33
|
+
```
|
|
34
|
+
|
|
16
35
|
Build the package with `npm run build:lib`. The publishable Angular package is written to `dist/chord-finder`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, computed, ChangeDetectionStrategy, Component, Injectable, signal, viewChild } from '@angular/core';
|
|
2
|
+
import { input, computed, ChangeDetectionStrategy, Component, Injectable, signal, viewChild, effect } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/forms';
|
|
4
4
|
import { FormsModule } from '@angular/forms';
|
|
5
5
|
import guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';
|
|
@@ -98,13 +98,25 @@ class ChordDiagram {
|
|
|
98
98
|
return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');
|
|
99
99
|
}
|
|
100
100
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordDiagram, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
101
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordDiagram, isStandalone: true, selector: "app-chord-diagram", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: true, transformFunction: null }, positionIndex: { classPropertyName: "positionIndex", publicName: "positionIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\n\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\n\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\n\n <g class=\"markers\">\n @for (fret of position().frets; track $index) {\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\n }\n </g>\n\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\n\n <g class=\"grid\">\n @for (line of fretLines(); track line) {\n <line\n [attr.x1]=\"stringX(0)\"\n [attr.x2]=\"stringX(5)\"\n [attr.y1]=\"fretY(line)\"\n [attr.y2]=\"fretY(line)\"\n [class.nut]=\"line === 0 && baseFret() === 1\"\n />\n }\n\n @for (line of stringLines(); track line) {\n <line\n [attr.x1]=\"stringX(line)\"\n [attr.x2]=\"stringX(line)\"\n [attr.y1]=\"fretY(0)\"\n [attr.y2]=\"fretY(fretCount())\"\n />\n }\n </g>\n\n <g class=\"barres\">\n @for (barre of barreSpans(); track barre.fret) {\n <rect\n [attr.x]=\"barre.x\"\n [attr.y]=\"barre.y\"\n [attr.width]=\"barre.width\"\n [attr.height]=\"dotRadius * 2\"\n rx=\"13\"\n />\n @if (barre.finger) {\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\n }\n }\n </g>\n\n <g class=\"dots\">\n @for (dot of visibleDots(); track dot.stringIndex) {\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\n @if (dot.finger) {\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\n }\n }\n </g>\n\n <g class=\"string-labels\">\n @for (label of stringLabels; track label + $index) {\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\n }\n </g>\n</svg>\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
101
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordDiagram, isStandalone: true, selector: "app-chord-diagram", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: true, transformFunction: null }, positionIndex: { classPropertyName: "positionIndex", publicName: "positionIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
102
102
|
}
|
|
103
103
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordDiagram, decorators: [{
|
|
104
104
|
type: Component,
|
|
105
|
-
args: [{ selector: 'app-chord-diagram', imports: [], changeDetection: ChangeDetectionStrategy.Eager, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\n\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\n\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\n\n <g class=\"markers\">\n @for (fret of position().frets; track $index) {\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\n }\n </g>\n\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\n\n <g class=\"grid\">\n @for (line of fretLines(); track line) {\n <line\n [attr.x1]=\"stringX(0)\"\n [attr.x2]=\"stringX(5)\"\n [attr.y1]=\"fretY(line)\"\n [attr.y2]=\"fretY(line)\"\n [class.nut]=\"line === 0 && baseFret() === 1\"\n />\n }\n\n @for (line of stringLines(); track line) {\n <line\n [attr.x1]=\"stringX(line)\"\n [attr.x2]=\"stringX(line)\"\n [attr.y1]=\"fretY(0)\"\n [attr.y2]=\"fretY(fretCount())\"\n />\n }\n </g>\n\n <g class=\"barres\">\n @for (barre of barreSpans(); track barre.fret) {\n <rect\n [attr.x]=\"barre.x\"\n [attr.y]=\"barre.y\"\n [attr.width]=\"barre.width\"\n [attr.height]=\"dotRadius * 2\"\n rx=\"13\"\n />\n @if (barre.finger) {\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\n }\n }\n </g>\n\n <g class=\"dots\">\n @for (dot of visibleDots(); track dot.stringIndex) {\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\n @if (dot.finger) {\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\n }\n }\n </g>\n\n <g class=\"string-labels\">\n @for (label of stringLabels; track label + $index) {\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\n }\n </g>\n</svg>\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"] }]
|
|
105
|
+
args: [{ selector: 'app-chord-diagram', imports: [], changeDetection: ChangeDetectionStrategy.Eager, template: "<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posici\u00F3n {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n", styles: [":host{display:block}.chord-svg{width:240px;height:330px;display:block;font-family:Roboto,Arial,sans-serif;font-weight:400;border-radius:24px;background:#fff;box-shadow:0 18px 36px #00000059}.card-bg{fill:#fff}.title{font-size:42px;font-weight:400;letter-spacing:-.03em;fill:#000}.grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square;vector-effect:non-scaling-stroke}.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}.barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle;pointer-events:none}.markers text{fill:#000;font-size:30px;font-weight:400}.fret-number{fill:#000;font-size:42px;font-weight:400}.string-labels text{fill:#000;font-size:18px;font-weight:400}\n"] }]
|
|
106
106
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: true }] }], positionIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionIndex", required: false }] }] } });
|
|
107
107
|
|
|
108
|
+
const ERROR_COPY = {
|
|
109
|
+
en: {
|
|
110
|
+
invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',
|
|
111
|
+
missingRoot: 'Chord root not found in chords-db.',
|
|
112
|
+
missingType: (suffix) => `The type "${suffix}" is not available for this chord.`,
|
|
113
|
+
},
|
|
114
|
+
es: {
|
|
115
|
+
invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',
|
|
116
|
+
missingRoot: 'Raíz no encontrada en chords-db.',
|
|
117
|
+
missingType: (suffix) => `El tipo "${suffix}" no existe para este acorde en la base actual.`,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
108
120
|
class ChordService {
|
|
109
121
|
maxBatchSize = 5;
|
|
110
122
|
guitarDb = guitarDbJson;
|
|
@@ -129,7 +141,7 @@ class ChordService {
|
|
|
129
141
|
Bb: 'Bb',
|
|
130
142
|
B: 'B',
|
|
131
143
|
Cb: 'B',
|
|
132
|
-
'B#': 'C'
|
|
144
|
+
'B#': 'C',
|
|
133
145
|
};
|
|
134
146
|
suffixAliases = {
|
|
135
147
|
'': 'major',
|
|
@@ -153,31 +165,32 @@ class ChordService {
|
|
|
153
165
|
sus2: 'sus2',
|
|
154
166
|
sus4: 'sus4',
|
|
155
167
|
add9: 'add9',
|
|
156
|
-
4: 'sus4'
|
|
168
|
+
4: 'sus4',
|
|
157
169
|
};
|
|
158
|
-
search(input) {
|
|
170
|
+
search(input, language = 'en') {
|
|
159
171
|
const tokens = input
|
|
160
172
|
.split(',')
|
|
161
173
|
.map((token) => token.trim())
|
|
162
174
|
.filter(Boolean);
|
|
163
175
|
const limitedTokens = tokens.slice(0, this.maxBatchSize);
|
|
164
176
|
return {
|
|
165
|
-
results: limitedTokens.map((token, index) => this.searchSingle(token, index)),
|
|
166
|
-
wasLimited: tokens.length > this.maxBatchSize
|
|
177
|
+
results: limitedTokens.map((token, index) => this.searchSingle(token, index, language)),
|
|
178
|
+
wasLimited: tokens.length > this.maxBatchSize,
|
|
167
179
|
};
|
|
168
180
|
}
|
|
169
181
|
suffixes() {
|
|
170
182
|
return this.guitarDb.suffixes;
|
|
171
183
|
}
|
|
172
|
-
searchSingle(token, index) {
|
|
184
|
+
searchSingle(token, index, language) {
|
|
173
185
|
const parsed = this.parseChordName(token);
|
|
186
|
+
const copy = ERROR_COPY[language];
|
|
174
187
|
if (!parsed) {
|
|
175
188
|
return {
|
|
176
189
|
id: `${index}-${token}`,
|
|
177
190
|
raw: token,
|
|
178
191
|
displayName: token,
|
|
179
192
|
positions: [],
|
|
180
|
-
error:
|
|
193
|
+
error: copy.invalid,
|
|
181
194
|
};
|
|
182
195
|
}
|
|
183
196
|
const chordFamily = this.guitarDb.chords[parsed.dbRoot];
|
|
@@ -187,7 +200,7 @@ class ChordService {
|
|
|
187
200
|
raw: token,
|
|
188
201
|
displayName: parsed.displayName,
|
|
189
202
|
positions: [],
|
|
190
|
-
error:
|
|
203
|
+
error: copy.missingRoot,
|
|
191
204
|
};
|
|
192
205
|
}
|
|
193
206
|
const chord = chordFamily.find((item) => item.suffix === parsed.suffix);
|
|
@@ -198,7 +211,7 @@ class ChordService {
|
|
|
198
211
|
displayName: parsed.displayName,
|
|
199
212
|
dbName: `${parsed.dbRoot} ${parsed.suffix}`,
|
|
200
213
|
positions: [],
|
|
201
|
-
error:
|
|
214
|
+
error: copy.missingType(parsed.suffix),
|
|
202
215
|
};
|
|
203
216
|
}
|
|
204
217
|
return {
|
|
@@ -233,7 +246,7 @@ class ChordService {
|
|
|
233
246
|
root,
|
|
234
247
|
dbRoot,
|
|
235
248
|
displayName: `${root}${this.displaySuffix(suffix)}`,
|
|
236
|
-
suffix
|
|
249
|
+
suffix,
|
|
237
250
|
};
|
|
238
251
|
}
|
|
239
252
|
normalizeSuffix(rawSuffix) {
|
|
@@ -268,8 +281,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
268
281
|
args: [{ providedIn: 'root' }]
|
|
269
282
|
}] });
|
|
270
283
|
|
|
284
|
+
const COPY = {
|
|
285
|
+
en: {
|
|
286
|
+
eyebrow: 'Angular · SVG · chords-db',
|
|
287
|
+
title: 'Chord Finder',
|
|
288
|
+
descriptionStart: 'Enter up to',
|
|
289
|
+
descriptionLimit: '5 chords',
|
|
290
|
+
descriptionEnd: 'separated by commas. Sharps and flats are supported:',
|
|
291
|
+
inputLabel: 'Chords',
|
|
292
|
+
exportPng: 'Export PNG',
|
|
293
|
+
limited: 'Only the first 5 chords are rendered.',
|
|
294
|
+
availableTypes: 'Available chord types:',
|
|
295
|
+
resultsLabel: 'Chord results',
|
|
296
|
+
chord: 'Chord',
|
|
297
|
+
normalizedAs: 'Normalized as',
|
|
298
|
+
position: 'Position',
|
|
299
|
+
of: 'of',
|
|
300
|
+
openSource: 'Open source project:',
|
|
301
|
+
contributions: 'Contributions are welcome.',
|
|
302
|
+
assistance: 'Built with AI assistance under supervision from the repository owner.',
|
|
303
|
+
},
|
|
304
|
+
es: {
|
|
305
|
+
eyebrow: 'Angular · SVG · chords-db',
|
|
306
|
+
title: 'Buscador de acordes',
|
|
307
|
+
descriptionStart: 'Escribe hasta',
|
|
308
|
+
descriptionLimit: '5 acordes',
|
|
309
|
+
descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',
|
|
310
|
+
inputLabel: 'Acordes',
|
|
311
|
+
exportPng: 'Exportar PNG',
|
|
312
|
+
limited: 'Solo se renderizan los primeros 5 acordes.',
|
|
313
|
+
availableTypes: 'Tipos de acorde disponibles:',
|
|
314
|
+
resultsLabel: 'Resultados de acordes',
|
|
315
|
+
chord: 'Acorde',
|
|
316
|
+
normalizedAs: 'Normalizado como',
|
|
317
|
+
position: 'Posición',
|
|
318
|
+
of: 'de',
|
|
319
|
+
openSource: 'Proyecto de código abierto:',
|
|
320
|
+
contributions: 'Las contribuciones son bienvenidas.',
|
|
321
|
+
assistance: 'Creado con asistencia de IA bajo la supervisión del propietario del repositorio.',
|
|
322
|
+
},
|
|
323
|
+
};
|
|
271
324
|
class ChordFinderComponent {
|
|
272
325
|
chordService;
|
|
326
|
+
language = input('en', /* @ts-ignore */
|
|
327
|
+
...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
|
|
328
|
+
text = computed(() => COPY[this.language()], /* @ts-ignore */
|
|
329
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
273
330
|
query = 'C';
|
|
274
331
|
results = signal([], /* @ts-ignore */
|
|
275
332
|
...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
|
|
@@ -281,7 +338,7 @@ class ChordFinderComponent {
|
|
|
281
338
|
resultsRow = viewChild.required('resultsRow');
|
|
282
339
|
constructor(chordService) {
|
|
283
340
|
this.chordService = chordService;
|
|
284
|
-
this.runSearch();
|
|
341
|
+
effect(() => this.runSearch(this.language()));
|
|
285
342
|
}
|
|
286
343
|
async exportPng() {
|
|
287
344
|
const svgs = Array.from(this.resultsRow().nativeElement.querySelectorAll('svg.chord-svg'));
|
|
@@ -293,7 +350,8 @@ class ChordFinderComponent {
|
|
|
293
350
|
const height = 330;
|
|
294
351
|
const scale = 2;
|
|
295
352
|
const canvas = document.createElement('canvas');
|
|
296
|
-
canvas.width =
|
|
353
|
+
canvas.width =
|
|
354
|
+
(padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;
|
|
297
355
|
canvas.height = (padding * 2 + height) * scale;
|
|
298
356
|
const ctx = canvas.getContext('2d');
|
|
299
357
|
if (!ctx)
|
|
@@ -313,7 +371,9 @@ class ChordFinderComponent {
|
|
|
313
371
|
.fret-number{fill:#000;font-size:42px;font-weight:400}
|
|
314
372
|
.string-labels text{fill:#000;font-size:18px;font-weight:400}
|
|
315
373
|
</style>`);
|
|
316
|
-
const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], {
|
|
374
|
+
const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], {
|
|
375
|
+
type: 'image/svg+xml',
|
|
376
|
+
}));
|
|
317
377
|
const image = new Image();
|
|
318
378
|
image.src = url;
|
|
319
379
|
await image.decode();
|
|
@@ -325,8 +385,8 @@ class ChordFinderComponent {
|
|
|
325
385
|
link.href = canvas.toDataURL('image/png');
|
|
326
386
|
link.click();
|
|
327
387
|
}
|
|
328
|
-
runSearch() {
|
|
329
|
-
const { results, wasLimited } = this.chordService.search(this.query);
|
|
388
|
+
runSearch(language = this.language()) {
|
|
389
|
+
const { results, wasLimited } = this.chordService.search(this.query, language);
|
|
330
390
|
this.results.set(results);
|
|
331
391
|
this.wasLimited.set(wasLimited);
|
|
332
392
|
for (const result of results) {
|
|
@@ -344,18 +404,19 @@ class ChordFinderComponent {
|
|
|
344
404
|
selectedPosition(result) {
|
|
345
405
|
if (!result.positions.length)
|
|
346
406
|
return null;
|
|
347
|
-
return result.positions[this.selectedPositionIndex[result.id] ?? 0] ??
|
|
407
|
+
return (result.positions[this.selectedPositionIndex[result.id] ?? 0] ??
|
|
408
|
+
result.positions[0]);
|
|
348
409
|
}
|
|
349
410
|
trackByResultId(_, result) {
|
|
350
411
|
return result.id;
|
|
351
412
|
}
|
|
352
413
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, deps: [{ token: ChordService }], target: i0.ɵɵFactoryTarget.Component });
|
|
353
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordFinderComponent, isStandalone: true, selector: "the-chords-chord-finder", viewQueries: [{ propertyName: "resultsRow", first: true, predicate: ["resultsRow"], descendants: true, isSignal: true }], ngImport: i0, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">
|
|
414
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordFinderComponent, isStandalone: true, selector: "the-chords-chord-finder", inputs: { language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.lang": "language()" } }, viewQueries: [{ propertyName: "resultsRow", first: true, predicate: ["resultsRow"], descendants: true, isSignal: true }], ngImport: i0, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChordDiagram, selector: "app-chord-diagram", inputs: ["title", "position", "positionIndex"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
354
415
|
}
|
|
355
416
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, decorators: [{
|
|
356
417
|
type: Component,
|
|
357
|
-
args: [{ selector: 'the-chords-chord-finder', standalone: true, imports: [FormsModule, ChordDiagram], changeDetection: ChangeDetectionStrategy.Eager, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">
|
|
358
|
-
}], ctorParameters: () => [{ type: ChordService }], propDecorators: { resultsRow: [{ type: i0.ViewChild, args: ['resultsRow', { isSignal: true }] }] } });
|
|
418
|
+
args: [{ selector: 'the-chords-chord-finder', standalone: true, imports: [FormsModule, ChordDiagram], changeDetection: ChangeDetectionStrategy.Eager, host: { '[attr.lang]': 'language()' }, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}\n"] }]
|
|
419
|
+
}], ctorParameters: () => [{ type: ChordService }], propDecorators: { language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], resultsRow: [{ type: i0.ViewChild, args: ['resultsRow', { isSignal: true }] }] } });
|
|
359
420
|
|
|
360
421
|
/*
|
|
361
422
|
* Public API Surface of chord-finder
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gblp-chord-finder.mjs","sources":["../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.ts","../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.html","../../../projects/chord-finder/src/lib/services/chord.service.ts","../../../projects/chord-finder/src/lib/chord-finder.ts","../../../projects/chord-finder/src/lib/chord-finder.html","../../../projects/chord-finder/src/public-api.ts","../../../projects/chord-finder/src/gblp-chord-finder.ts"],"sourcesContent":["import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';\nimport { ChordsDbPosition } from '../../models/chord.model';\n\ninterface BarreSpan {\n fret: number;\n x: number;\n y: number;\n width: number;\n labelX: number;\n labelY: number;\n finger: number | undefined;\n}\n\ninterface DotMarker {\n fret: number;\n stringIndex: number;\n finger?: number;\n}\n\n@Component({\n selector: 'app-chord-diagram',\n imports: [],\n templateUrl: './chord-diagram.html',\n changeDetection: ChangeDetectionStrategy.Eager,\n styleUrl: './chord-diagram.scss'\n})\nexport class ChordDiagram {\n title = input.required<string>();\n position = input.required<ChordsDbPosition>();\n positionIndex = input<number>(0);\n\n readonly width = 240;\n readonly height = 330;\n readonly left = 54;\n readonly top = 112;\n readonly stringGap = 28;\n readonly fretGap = 34;\n readonly dotRadius = 13;\n readonly stringLabels = ['E', 'A', 'D', 'G', 'B', 'E'];\n\n fretCount = computed(() => Math.max(5, ...this.position().frets.filter((fret) => fret > 0)));\n\n fretLines = computed(() => Array.from({ length: this.fretCount() + 1 }, (_, index) => index));\n\n stringLines = computed(() => Array.from({ length: 6 }, (_, index) => index));\n\n visibleDots = computed<DotMarker[]>(() => {\n const barres = new Set(this.position().barres ?? []);\n const fingers = this.position().fingers ?? [];\n\n return this.position().frets\n .map((fret, stringIndex) => ({\n fret,\n stringIndex,\n finger: fingers[stringIndex] > 0 ? fingers[stringIndex] : undefined\n }))\n .filter(({ fret }) => fret > 0)\n .filter(({ fret }) => !barres.has(fret));\n });\n\n barreSpans = computed<BarreSpan[]>(() => {\n const barres = this.position().barres ?? [];\n const fingers = this.position().fingers ?? [];\n\n return barres\n .map((barreFret) => {\n const playableStrings = this.position().frets\n .map((fret, index) => ({ fret, index }))\n .filter(({ fret }) => fret >= barreFret);\n\n if (playableStrings.length < 2) return null;\n\n const firstString = playableStrings[0].index;\n const lastString = playableStrings[playableStrings.length - 1].index;\n const x = this.stringX(firstString) - this.dotRadius;\n const y = this.dotY(barreFret) - this.dotRadius;\n const width = this.stringX(lastString) - this.stringX(firstString) + this.dotRadius * 2;\n const labelX = x + width / 2;\n const labelY = y + this.dotRadius;\n const finger = fingers[firstString] > 0 ? fingers[firstString] : undefined;\n\n return {\n fret: barreFret,\n x,\n y,\n width,\n labelX,\n labelY,\n finger\n } satisfies BarreSpan;\n })\n .filter((span): span is BarreSpan => span !== null);\n });\n\n get viewBox(): string {\n return `0 0 ${this.width} ${this.height}`;\n }\n\n baseFret(): number {\n return this.position().baseFret ?? 1;\n }\n\n stringX(index: number): number {\n return this.left + index * this.stringGap;\n }\n\n fretY(index: number): number {\n return this.top + index * this.fretGap;\n }\n\n dotY(fret: number): number {\n return this.top + (fret - 0.5) * this.fretGap;\n }\n\n markerFor(fret: number): string {\n if (fret === -1) return '×';\n if (fret === 0) return '○';\n return '';\n }\n\n fretLabel(): string {\n return String(this.baseFret());\n }\n\n positionCode(): string {\n return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');\n }\n}\n","<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\n <title>{{ title() }} - posición {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\n\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\n\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\n\n <g class=\"markers\">\n @for (fret of position().frets; track $index) {\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\n }\n </g>\n\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\n\n <g class=\"grid\">\n @for (line of fretLines(); track line) {\n <line\n [attr.x1]=\"stringX(0)\"\n [attr.x2]=\"stringX(5)\"\n [attr.y1]=\"fretY(line)\"\n [attr.y2]=\"fretY(line)\"\n [class.nut]=\"line === 0 && baseFret() === 1\"\n />\n }\n\n @for (line of stringLines(); track line) {\n <line\n [attr.x1]=\"stringX(line)\"\n [attr.x2]=\"stringX(line)\"\n [attr.y1]=\"fretY(0)\"\n [attr.y2]=\"fretY(fretCount())\"\n />\n }\n </g>\n\n <g class=\"barres\">\n @for (barre of barreSpans(); track barre.fret) {\n <rect\n [attr.x]=\"barre.x\"\n [attr.y]=\"barre.y\"\n [attr.width]=\"barre.width\"\n [attr.height]=\"dotRadius * 2\"\n rx=\"13\"\n />\n @if (barre.finger) {\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\n }\n }\n </g>\n\n <g class=\"dots\">\n @for (dot of visibleDots(); track dot.stringIndex) {\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\n @if (dot.finger) {\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\n }\n }\n </g>\n\n <g class=\"string-labels\">\n @for (label of stringLabels; track label + $index) {\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\n }\n </g>\n</svg>\n","import { Injectable } from '@angular/core';\nimport guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';\nimport { ChordSearchResult, ChordsDbInstrument, ParsedChord } from '../models/chord.model';\n\n@Injectable({ providedIn: 'root' })\nexport class ChordService {\n readonly maxBatchSize = 5;\n\n private readonly guitarDb = guitarDbJson as ChordsDbInstrument;\n\n private readonly dbRootMap: Record<string, string> = {\n C: 'C',\n 'C#': 'Csharp',\n Db: 'Csharp',\n D: 'D',\n 'D#': 'Eb',\n Eb: 'Eb',\n E: 'E',\n Fb: 'E',\n 'E#': 'F',\n F: 'F',\n 'F#': 'Fsharp',\n Gb: 'Fsharp',\n G: 'G',\n 'G#': 'Ab',\n Ab: 'Ab',\n A: 'A',\n 'A#': 'Bb',\n Bb: 'Bb',\n B: 'B',\n Cb: 'B',\n 'B#': 'C'\n };\n\n private readonly suffixAliases: Record<string, string> = {\n '': 'major',\n M: 'major',\n maj: 'major',\n major: 'major',\n m: 'minor',\n min: 'minor',\n '-': 'minor',\n minor: 'minor',\n Δ: 'maj7',\n maj7: 'maj7',\n M7: 'maj7',\n m7: 'm7',\n min7: 'm7',\n dim: 'dim',\n diminished: 'dim',\n aug: 'aug',\n augmented: 'aug',\n sus: 'sus4',\n sus2: 'sus2',\n sus4: 'sus4',\n add9: 'add9',\n 4: 'sus4'\n };\n\n search(input: string): { results: ChordSearchResult[]; wasLimited: boolean } {\n const tokens = input\n .split(',')\n .map((token) => token.trim())\n .filter(Boolean);\n\n const limitedTokens = tokens.slice(0, this.maxBatchSize);\n\n return {\n results: limitedTokens.map((token, index) => this.searchSingle(token, index)),\n wasLimited: tokens.length > this.maxBatchSize\n };\n }\n\n suffixes(): string[] {\n return this.guitarDb.suffixes;\n }\n\n private searchSingle(token: string, index: number): ChordSearchResult {\n const parsed = this.parseChordName(token);\n\n if (!parsed) {\n return {\n id: `${index}-${token}`,\n raw: token,\n displayName: token,\n positions: [],\n error: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.'\n };\n }\n const chordFamily = this.guitarDb.chords[parsed.dbRoot];\n\n if (!chordFamily) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n positions: [],\n error: 'Raíz no encontrada en chords-db.'\n };\n }\n\n const chord = chordFamily.find((item) => item.suffix === parsed.suffix);\n\n if (!chord) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${parsed.dbRoot} ${parsed.suffix}`,\n positions: [],\n error: `El tipo \"${parsed.suffix}\" no existe para este acorde en la base actual.`\n };\n }\n\n return {\n id: `${index}-${parsed.dbRoot}-${parsed.suffix}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${chord.key} ${chord.suffix}`,\n chord,\n positions: chord.positions,\n };\n }\n\n private parseChordName(raw: string): ParsedChord | null {\n const cleaned = raw\n .replaceAll('♯', '#')\n .replaceAll('♭', 'b')\n .replace(/\\s+/g, '');\n\n const match = cleaned.match(/^([A-Ga-g])([#b]?)(.*)$/);\n if (!match) return null;\n\n const letter = match[1].toUpperCase();\n const accidental = match[2] ?? '';\n const suffixRaw = match[3] ?? '';\n const root = `${letter}${accidental}`;\n const dbRoot = this.dbRootMap[root];\n\n if (!dbRoot) return null;\n\n const suffix = this.normalizeSuffix(suffixRaw);\n if (!suffix) return null;\n\n return {\n raw,\n root,\n dbRoot,\n displayName: `${root}${this.displaySuffix(suffix)}`,\n suffix\n };\n }\n\n private normalizeSuffix(rawSuffix: string): string | null {\n const trimmed = rawSuffix.trim();\n\n if (this.suffixAliases[trimmed] !== undefined) {\n return this.suffixAliases[trimmed];\n }\n\n const lower = trimmed.toLowerCase();\n if (this.suffixAliases[lower] !== undefined) {\n return this.suffixAliases[lower];\n }\n\n if (this.guitarDb.suffixes.includes(trimmed)) {\n return trimmed;\n }\n\n if (this.guitarDb.suffixes.includes(lower)) {\n return lower;\n }\n\n return null;\n }\n\n private displaySuffix(suffix: string): string {\n if (suffix === 'major') return '';\n if (suffix === 'minor') return 'm';\n return suffix;\n }\n}\n","import { Component, ElementRef, computed, signal, viewChild, ChangeDetectionStrategy } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ChordDiagram } from './components/chord-diagram/chord-diagram';\nimport { ChordSearchResult, ChordsDbPosition } from './models/chord.model';\nimport { ChordService } from './services/chord.service';\n\n@Component({\n selector: 'the-chords-chord-finder',\n standalone: true,\n imports: [FormsModule, ChordDiagram],\n templateUrl: './chord-finder.html',\n changeDetection: ChangeDetectionStrategy.Eager,\n styleUrl: './chord-finder.scss'\n})\nexport class ChordFinderComponent {\n query = 'C';\n results = signal<ChordSearchResult[]>([]);\n wasLimited = signal(false);\n selectedPositionIndex: Record<string, number> = {};\n supportedSuffixes = computed(() => this.chordService.suffixes().slice(0, 18).join(', '));\n\n resultsRow = viewChild.required<ElementRef<HTMLElement>>('resultsRow');\n\n constructor(private readonly chordService: ChordService) {\n this.runSearch();\n }\n\n async exportPng(): Promise<void> {\n const svgs = Array.from(this.resultsRow().nativeElement.querySelectorAll<SVGSVGElement>('svg.chord-svg'));\n if (!svgs.length) return;\n\n const padding = 32;\n const gap = 24;\n const width = 240;\n const height = 330;\n const scale = 2;\n\n const canvas = document.createElement('canvas');\n canvas.width = (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;\n canvas.height = (padding * 2 + height) * scale;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.scale(scale, scale);\n ctx.fillStyle = '#ffffff';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n for (const [index, svg] of svgs.entries()) {\n const clone = svg.cloneNode(true) as SVGSVGElement;\n\n clone.insertAdjacentHTML('afterbegin', `<style>\n .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}\n .card-bg{fill:#fff}.title{font-size:42px;font-weight:400;fill:#000}\n .grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square}\n .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}\n .barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}\n .markers text{fill:#000;font-size:30px;font-weight:400}\n .fret-number{fill:#000;font-size:42px;font-weight:400}\n .string-labels text{fill:#000;font-size:18px;font-weight:400}\n </style>`);\n\n const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], { type: 'image/svg+xml' }));\n const image = new Image();\n image.src = url;\n await image.decode();\n\n ctx.drawImage(image, padding + index * (width + gap), padding, width, height);\n URL.revokeObjectURL(url);\n }\n\n const link = document.createElement('a');\n link.download = 'chords.png';\n link.href = canvas.toDataURL('image/png');\n link.click();\n }\n\n\n runSearch(): void {\n const { results, wasLimited } = this.chordService.search(this.query);\n this.results.set(results);\n this.wasLimited.set(wasLimited);\n\n for (const result of results) {\n if (this.selectedPositionIndex[result.id] === undefined) {\n this.selectedPositionIndex[result.id] = 0;\n }\n }\n }\n\n selectPosition(resultId: string, value: string | number): void {\n this.selectedPositionIndex[resultId] = Number(value);\n }\n\n selectedIndex(resultId: string): number {\n return this.selectedPositionIndex[resultId] ?? 0;\n }\n\n selectedPosition(result: ChordSearchResult): ChordsDbPosition | null {\n if (!result.positions.length) return null;\n return result.positions[this.selectedPositionIndex[result.id] ?? 0] ?? result.positions[0];\n }\n\n trackByResultId(_: number, result: ChordSearchResult): string {\n return result.id;\n }\n}\n","<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">Angular · SVG · chords-db</p>\n <h1>Chord Finder</h1>\n\n <p class=\"description\">\n Escribe hasta <strong>5 acordes</strong> separados por coma. Soporta sostenidos y bemoles:\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">Input de acordes</label>\n <input id=\"chordInput\" type=\"text\" [(ngModel)]=\"query\" (input)=\"runSearch()\" placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\" />\n <button type=\"button\" (click)=\"exportPng()\" [disabled]=\"!results().length\">\n Exportar PNG\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">Solo se renderizan los primeros 5 acordes por ahora.</p>\n }\n\n <p class=\"hint\">\n Tipos disponibles desde la base: {{ supportedSuffixes() }}...\n </p>\n </section>\n\n <section #resultsRow class=\"results-row\" aria-label=\"Chord results\">\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">Acorde</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">Normalizado como {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n Posición\n <select [ngModel]=\"selectedPositionIndex[result.id]\" (ngModelChange)=\"selectPosition(result.id, $event)\">\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">{{ $index + 1 }} de {{ result.positions.length }}</option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram [title]=\"result.displayName\" [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\" />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n Open source project:\n <a href=\"https://github.com/elparaquecosadeque/chord-generator\" target=\"_blank\" rel=\"noreferrer\">\n elparaquecosadeque/chord-generator\n </a>.\n Contributions are welcome.\n </p>\n <p>\n Built with AI assistance under supervision from the repository owner.\n </p>\n </footer>\n</main>\n","/*\r\n * Public API Surface of chord-finder\r\n */\r\n\r\nexport * from './lib/chord-finder';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.ChordService"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gblp-chord-finder.mjs","sources":["../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.ts","../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.html","../../../projects/chord-finder/src/lib/services/chord.service.ts","../../../projects/chord-finder/src/lib/chord-finder.ts","../../../projects/chord-finder/src/lib/chord-finder.html","../../../projects/chord-finder/src/public-api.ts","../../../projects/chord-finder/src/gblp-chord-finder.ts"],"sourcesContent":["import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';\r\nimport { ChordsDbPosition } from '../../models/chord.model';\r\n\r\ninterface BarreSpan {\r\n fret: number;\r\n x: number;\r\n y: number;\r\n width: number;\r\n labelX: number;\r\n labelY: number;\r\n finger: number | undefined;\r\n}\r\n\r\ninterface DotMarker {\r\n fret: number;\r\n stringIndex: number;\r\n finger?: number;\r\n}\r\n\r\n@Component({\r\n selector: 'app-chord-diagram',\r\n imports: [],\r\n templateUrl: './chord-diagram.html',\r\n changeDetection: ChangeDetectionStrategy.Eager,\r\n styleUrl: './chord-diagram.scss'\r\n})\r\nexport class ChordDiagram {\r\n title = input.required<string>();\r\n position = input.required<ChordsDbPosition>();\r\n positionIndex = input<number>(0);\r\n\r\n readonly width = 240;\r\n readonly height = 330;\r\n readonly left = 54;\r\n readonly top = 112;\r\n readonly stringGap = 28;\r\n readonly fretGap = 34;\r\n readonly dotRadius = 13;\r\n readonly stringLabels = ['E', 'A', 'D', 'G', 'B', 'E'];\r\n\r\n fretCount = computed(() => Math.max(5, ...this.position().frets.filter((fret) => fret > 0)));\r\n\r\n fretLines = computed(() => Array.from({ length: this.fretCount() + 1 }, (_, index) => index));\r\n\r\n stringLines = computed(() => Array.from({ length: 6 }, (_, index) => index));\r\n\r\n visibleDots = computed<DotMarker[]>(() => {\r\n const barres = new Set(this.position().barres ?? []);\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return this.position().frets\r\n .map((fret, stringIndex) => ({\r\n fret,\r\n stringIndex,\r\n finger: fingers[stringIndex] > 0 ? fingers[stringIndex] : undefined\r\n }))\r\n .filter(({ fret }) => fret > 0)\r\n .filter(({ fret }) => !barres.has(fret));\r\n });\r\n\r\n barreSpans = computed<BarreSpan[]>(() => {\r\n const barres = this.position().barres ?? [];\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return barres\r\n .map((barreFret) => {\r\n const playableStrings = this.position().frets\r\n .map((fret, index) => ({ fret, index }))\r\n .filter(({ fret }) => fret >= barreFret);\r\n\r\n if (playableStrings.length < 2) return null;\r\n\r\n const firstString = playableStrings[0].index;\r\n const lastString = playableStrings[playableStrings.length - 1].index;\r\n const x = this.stringX(firstString) - this.dotRadius;\r\n const y = this.dotY(barreFret) - this.dotRadius;\r\n const width = this.stringX(lastString) - this.stringX(firstString) + this.dotRadius * 2;\r\n const labelX = x + width / 2;\r\n const labelY = y + this.dotRadius;\r\n const finger = fingers[firstString] > 0 ? fingers[firstString] : undefined;\r\n\r\n return {\r\n fret: barreFret,\r\n x,\r\n y,\r\n width,\r\n labelX,\r\n labelY,\r\n finger\r\n } satisfies BarreSpan;\r\n })\r\n .filter((span): span is BarreSpan => span !== null);\r\n });\r\n\r\n get viewBox(): string {\r\n return `0 0 ${this.width} ${this.height}`;\r\n }\r\n\r\n baseFret(): number {\r\n return this.position().baseFret ?? 1;\r\n }\r\n\r\n stringX(index: number): number {\r\n return this.left + index * this.stringGap;\r\n }\r\n\r\n fretY(index: number): number {\r\n return this.top + index * this.fretGap;\r\n }\r\n\r\n dotY(fret: number): number {\r\n return this.top + (fret - 0.5) * this.fretGap;\r\n }\r\n\r\n markerFor(fret: number): string {\r\n if (fret === -1) return '×';\r\n if (fret === 0) return '○';\r\n return '';\r\n }\r\n\r\n fretLabel(): string {\r\n return String(this.baseFret());\r\n }\r\n\r\n positionCode(): string {\r\n return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');\r\n }\r\n}\r\n","<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posición {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n","import { Injectable } from '@angular/core';\nimport guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';\nimport {\n ChordSearchResult,\n ChordsDbInstrument,\n Language,\n ParsedChord,\n} from '../models/chord.model';\n\nconst ERROR_COPY = {\n en: {\n invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',\n missingRoot: 'Chord root not found in chords-db.',\n missingType: (suffix: string) =>\n `The type \"${suffix}\" is not available for this chord.`,\n },\n es: {\n invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',\n missingRoot: 'Raíz no encontrada en chords-db.',\n missingType: (suffix: string) =>\n `El tipo \"${suffix}\" no existe para este acorde en la base actual.`,\n },\n} satisfies Record<\n Language,\n {\n invalid: string;\n missingRoot: string;\n missingType: (suffix: string) => string;\n }\n>;\n\n@Injectable({ providedIn: 'root' })\nexport class ChordService {\n readonly maxBatchSize = 5;\n\n private readonly guitarDb = guitarDbJson as ChordsDbInstrument;\n\n private readonly dbRootMap: Record<string, string> = {\n C: 'C',\n 'C#': 'Csharp',\n Db: 'Csharp',\n D: 'D',\n 'D#': 'Eb',\n Eb: 'Eb',\n E: 'E',\n Fb: 'E',\n 'E#': 'F',\n F: 'F',\n 'F#': 'Fsharp',\n Gb: 'Fsharp',\n G: 'G',\n 'G#': 'Ab',\n Ab: 'Ab',\n A: 'A',\n 'A#': 'Bb',\n Bb: 'Bb',\n B: 'B',\n Cb: 'B',\n 'B#': 'C',\n };\n\n private readonly suffixAliases: Record<string, string> = {\n '': 'major',\n M: 'major',\n maj: 'major',\n major: 'major',\n m: 'minor',\n min: 'minor',\n '-': 'minor',\n minor: 'minor',\n Δ: 'maj7',\n maj7: 'maj7',\n M7: 'maj7',\n m7: 'm7',\n min7: 'm7',\n dim: 'dim',\n diminished: 'dim',\n aug: 'aug',\n augmented: 'aug',\n sus: 'sus4',\n sus2: 'sus2',\n sus4: 'sus4',\n add9: 'add9',\n 4: 'sus4',\n };\n\n search(\n input: string,\n language: Language = 'en',\n ): { results: ChordSearchResult[]; wasLimited: boolean } {\n const tokens = input\n .split(',')\n .map((token) => token.trim())\n .filter(Boolean);\n\n const limitedTokens = tokens.slice(0, this.maxBatchSize);\n\n return {\n results: limitedTokens.map((token, index) =>\n this.searchSingle(token, index, language),\n ),\n wasLimited: tokens.length > this.maxBatchSize,\n };\n }\n\n suffixes(): string[] {\n return this.guitarDb.suffixes;\n }\n\n private searchSingle(\n token: string,\n index: number,\n language: Language,\n ): ChordSearchResult {\n const parsed = this.parseChordName(token);\n const copy = ERROR_COPY[language];\n\n if (!parsed) {\n return {\n id: `${index}-${token}`,\n raw: token,\n displayName: token,\n positions: [],\n error: copy.invalid,\n };\n }\n const chordFamily = this.guitarDb.chords[parsed.dbRoot];\n\n if (!chordFamily) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n positions: [],\n error: copy.missingRoot,\n };\n }\n\n const chord = chordFamily.find((item) => item.suffix === parsed.suffix);\n\n if (!chord) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${parsed.dbRoot} ${parsed.suffix}`,\n positions: [],\n error: copy.missingType(parsed.suffix),\n };\n }\n\n return {\n id: `${index}-${parsed.dbRoot}-${parsed.suffix}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${chord.key} ${chord.suffix}`,\n chord,\n positions: chord.positions,\n };\n }\n\n private parseChordName(raw: string): ParsedChord | null {\n const cleaned = raw\n .replaceAll('♯', '#')\n .replaceAll('♭', 'b')\n .replace(/\\s+/g, '');\n\n const match = cleaned.match(/^([A-Ga-g])([#b]?)(.*)$/);\n if (!match) return null;\n\n const letter = match[1].toUpperCase();\n const accidental = match[2] ?? '';\n const suffixRaw = match[3] ?? '';\n const root = `${letter}${accidental}`;\n const dbRoot = this.dbRootMap[root];\n\n if (!dbRoot) return null;\n\n const suffix = this.normalizeSuffix(suffixRaw);\n if (!suffix) return null;\n\n return {\n raw,\n root,\n dbRoot,\n displayName: `${root}${this.displaySuffix(suffix)}`,\n suffix,\n };\n }\n\n private normalizeSuffix(rawSuffix: string): string | null {\n const trimmed = rawSuffix.trim();\n\n if (this.suffixAliases[trimmed] !== undefined) {\n return this.suffixAliases[trimmed];\n }\n\n const lower = trimmed.toLowerCase();\n if (this.suffixAliases[lower] !== undefined) {\n return this.suffixAliases[lower];\n }\n\n if (this.guitarDb.suffixes.includes(trimmed)) {\n return trimmed;\n }\n\n if (this.guitarDb.suffixes.includes(lower)) {\n return lower;\n }\n\n return null;\n }\n\n private displaySuffix(suffix: string): string {\n if (suffix === 'major') return '';\n if (suffix === 'minor') return 'm';\n return suffix;\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n effect,\n input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ChordDiagram } from './components/chord-diagram/chord-diagram';\nimport {\n ChordSearchResult,\n ChordsDbPosition,\n Language,\n} from './models/chord.model';\nimport { ChordService } from './services/chord.service';\n\nconst COPY = {\n en: {\n eyebrow: 'Angular · SVG · chords-db',\n title: 'Chord Finder',\n descriptionStart: 'Enter up to',\n descriptionLimit: '5 chords',\n descriptionEnd: 'separated by commas. Sharps and flats are supported:',\n inputLabel: 'Chords',\n exportPng: 'Export PNG',\n limited: 'Only the first 5 chords are rendered.',\n availableTypes: 'Available chord types:',\n resultsLabel: 'Chord results',\n chord: 'Chord',\n normalizedAs: 'Normalized as',\n position: 'Position',\n of: 'of',\n openSource: 'Open source project:',\n contributions: 'Contributions are welcome.',\n assistance:\n 'Built with AI assistance under supervision from the repository owner.',\n },\n es: {\n eyebrow: 'Angular · SVG · chords-db',\n title: 'Buscador de acordes',\n descriptionStart: 'Escribe hasta',\n descriptionLimit: '5 acordes',\n descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',\n inputLabel: 'Acordes',\n exportPng: 'Exportar PNG',\n limited: 'Solo se renderizan los primeros 5 acordes.',\n availableTypes: 'Tipos de acorde disponibles:',\n resultsLabel: 'Resultados de acordes',\n chord: 'Acorde',\n normalizedAs: 'Normalizado como',\n position: 'Posición',\n of: 'de',\n openSource: 'Proyecto de código abierto:',\n contributions: 'Las contribuciones son bienvenidas.',\n assistance:\n 'Creado con asistencia de IA bajo la supervisión del propietario del repositorio.',\n },\n} as const;\n\n@Component({\n selector: 'the-chords-chord-finder',\n standalone: true,\n imports: [FormsModule, ChordDiagram],\n templateUrl: './chord-finder.html',\n changeDetection: ChangeDetectionStrategy.Eager,\n styleUrl: './chord-finder.scss',\n host: { '[attr.lang]': 'language()' },\n})\nexport class ChordFinderComponent {\n readonly language = input<Language>('en');\n readonly text = computed(() => COPY[this.language()]);\n query = 'C';\n results = signal<ChordSearchResult[]>([]);\n wasLimited = signal(false);\n selectedPositionIndex: Record<string, number> = {};\n supportedSuffixes = computed(() =>\n this.chordService.suffixes().slice(0, 18).join(', '),\n );\n\n resultsRow = viewChild.required<ElementRef<HTMLElement>>('resultsRow');\n\n constructor(private readonly chordService: ChordService) {\n effect(() => this.runSearch(this.language()));\n }\n\n async exportPng(): Promise<void> {\n const svgs = Array.from(\n this.resultsRow().nativeElement.querySelectorAll<SVGSVGElement>(\n 'svg.chord-svg',\n ),\n );\n if (!svgs.length) return;\n\n const padding = 32;\n const gap = 24;\n const width = 240;\n const height = 330;\n const scale = 2;\n\n const canvas = document.createElement('canvas');\n canvas.width =\n (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;\n canvas.height = (padding * 2 + height) * scale;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.scale(scale, scale);\n ctx.fillStyle = '#ffffff';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n for (const [index, svg] of svgs.entries()) {\n const clone = svg.cloneNode(true) as SVGSVGElement;\n\n clone.insertAdjacentHTML(\n 'afterbegin',\n `<style>\n .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}\n .card-bg{fill:#fff}.title{font-size:42px;font-weight:400;fill:#000}\n .grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square}\n .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}\n .barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}\n .markers text{fill:#000;font-size:30px;font-weight:400}\n .fret-number{fill:#000;font-size:42px;font-weight:400}\n .string-labels text{fill:#000;font-size:18px;font-weight:400}\n </style>`,\n );\n\n const url = URL.createObjectURL(\n new Blob([new XMLSerializer().serializeToString(clone)], {\n type: 'image/svg+xml',\n }),\n );\n const image = new Image();\n image.src = url;\n await image.decode();\n\n ctx.drawImage(\n image,\n padding + index * (width + gap),\n padding,\n width,\n height,\n );\n URL.revokeObjectURL(url);\n }\n\n const link = document.createElement('a');\n link.download = 'chords.png';\n link.href = canvas.toDataURL('image/png');\n link.click();\n }\n\n runSearch(language: Language = this.language()): void {\n const { results, wasLimited } = this.chordService.search(\n this.query,\n language,\n );\n this.results.set(results);\n this.wasLimited.set(wasLimited);\n\n for (const result of results) {\n if (this.selectedPositionIndex[result.id] === undefined) {\n this.selectedPositionIndex[result.id] = 0;\n }\n }\n }\n\n selectPosition(resultId: string, value: string | number): void {\n this.selectedPositionIndex[resultId] = Number(value);\n }\n\n selectedIndex(resultId: string): number {\n return this.selectedPositionIndex[resultId] ?? 0;\n }\n\n selectedPosition(result: ChordSearchResult): ChordsDbPosition | null {\n if (!result.positions.length) return null;\n return (\n result.positions[this.selectedPositionIndex[result.id] ?? 0] ??\n result.positions[0]\n );\n }\n\n trackByResultId(_: number, result: ChordSearchResult): string {\n return result.id;\n }\n}\n","<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n","/*\r\n * Public API Surface of chord-finder\r\n */\r\n\r\nexport * from './lib/chord-finder';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.ChordService"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
2
|
import { ElementRef } from '@angular/core';
|
|
3
3
|
|
|
4
|
+
type Language = 'en' | 'es';
|
|
4
5
|
interface ChordsDbPosition {
|
|
5
6
|
frets: number[];
|
|
6
7
|
fingers?: number[];
|
|
@@ -29,7 +30,7 @@ declare class ChordService {
|
|
|
29
30
|
private readonly guitarDb;
|
|
30
31
|
private readonly dbRootMap;
|
|
31
32
|
private readonly suffixAliases;
|
|
32
|
-
search(input: string): {
|
|
33
|
+
search(input: string, language?: Language): {
|
|
33
34
|
results: ChordSearchResult[];
|
|
34
35
|
wasLimited: boolean;
|
|
35
36
|
};
|
|
@@ -44,6 +45,44 @@ declare class ChordService {
|
|
|
44
45
|
|
|
45
46
|
declare class ChordFinderComponent {
|
|
46
47
|
private readonly chordService;
|
|
48
|
+
readonly language: _angular_core.InputSignal<Language>;
|
|
49
|
+
readonly text: _angular_core.Signal<{
|
|
50
|
+
readonly eyebrow: "Angular · SVG · chords-db";
|
|
51
|
+
readonly title: "Chord Finder";
|
|
52
|
+
readonly descriptionStart: "Enter up to";
|
|
53
|
+
readonly descriptionLimit: "5 chords";
|
|
54
|
+
readonly descriptionEnd: "separated by commas. Sharps and flats are supported:";
|
|
55
|
+
readonly inputLabel: "Chords";
|
|
56
|
+
readonly exportPng: "Export PNG";
|
|
57
|
+
readonly limited: "Only the first 5 chords are rendered.";
|
|
58
|
+
readonly availableTypes: "Available chord types:";
|
|
59
|
+
readonly resultsLabel: "Chord results";
|
|
60
|
+
readonly chord: "Chord";
|
|
61
|
+
readonly normalizedAs: "Normalized as";
|
|
62
|
+
readonly position: "Position";
|
|
63
|
+
readonly of: "of";
|
|
64
|
+
readonly openSource: "Open source project:";
|
|
65
|
+
readonly contributions: "Contributions are welcome.";
|
|
66
|
+
readonly assistance: "Built with AI assistance under supervision from the repository owner.";
|
|
67
|
+
} | {
|
|
68
|
+
readonly eyebrow: "Angular · SVG · chords-db";
|
|
69
|
+
readonly title: "Buscador de acordes";
|
|
70
|
+
readonly descriptionStart: "Escribe hasta";
|
|
71
|
+
readonly descriptionLimit: "5 acordes";
|
|
72
|
+
readonly descriptionEnd: "separados por coma. Soporta sostenidos y bemoles:";
|
|
73
|
+
readonly inputLabel: "Acordes";
|
|
74
|
+
readonly exportPng: "Exportar PNG";
|
|
75
|
+
readonly limited: "Solo se renderizan los primeros 5 acordes.";
|
|
76
|
+
readonly availableTypes: "Tipos de acorde disponibles:";
|
|
77
|
+
readonly resultsLabel: "Resultados de acordes";
|
|
78
|
+
readonly chord: "Acorde";
|
|
79
|
+
readonly normalizedAs: "Normalizado como";
|
|
80
|
+
readonly position: "Posición";
|
|
81
|
+
readonly of: "de";
|
|
82
|
+
readonly openSource: "Proyecto de código abierto:";
|
|
83
|
+
readonly contributions: "Las contribuciones son bienvenidas.";
|
|
84
|
+
readonly assistance: "Creado con asistencia de IA bajo la supervisión del propietario del repositorio.";
|
|
85
|
+
}>;
|
|
47
86
|
query: string;
|
|
48
87
|
results: _angular_core.WritableSignal<ChordSearchResult[]>;
|
|
49
88
|
wasLimited: _angular_core.WritableSignal<boolean>;
|
|
@@ -52,13 +91,13 @@ declare class ChordFinderComponent {
|
|
|
52
91
|
resultsRow: _angular_core.Signal<ElementRef<HTMLElement>>;
|
|
53
92
|
constructor(chordService: ChordService);
|
|
54
93
|
exportPng(): Promise<void>;
|
|
55
|
-
runSearch(): void;
|
|
94
|
+
runSearch(language?: Language): void;
|
|
56
95
|
selectPosition(resultId: string, value: string | number): void;
|
|
57
96
|
selectedIndex(resultId: string): number;
|
|
58
97
|
selectedPosition(result: ChordSearchResult): ChordsDbPosition | null;
|
|
59
98
|
trackByResultId(_: number, result: ChordSearchResult): string;
|
|
60
99
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ChordFinderComponent, never>;
|
|
61
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ChordFinderComponent, "the-chords-chord-finder", never, {}, {}, never, never, true, never>;
|
|
100
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ChordFinderComponent, "the-chords-chord-finder", never, { "language": { "alias": "language"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
62
101
|
}
|
|
63
102
|
|
|
64
103
|
export { ChordFinderComponent };
|