@gblp/chord-finder 0.3.3 → 0.3.4
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 +41 -41
- package/fesm2022/gblp-chord-finder.mjs +162 -36
- package/fesm2022/gblp-chord-finder.mjs.map +1 -1
- package/package.json +1 -1
- package/types/gblp-chord-finder.d.ts +30 -8
package/README.md
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
# @gblp/chord-finder
|
|
2
|
-
|
|
3
|
-
Standalone Angular 22 component for finding guitar chords and rendering SVG chord diagrams.
|
|
4
|
-
|
|
5
|
-
[View the package on npm](https://www.npmjs.com/package/@gblp/chord-finder)
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @gblp/chord-finder
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
import { Component } from "@angular/core";
|
|
13
|
-
import { ChordFinderComponent } from "@gblp/chord-finder";
|
|
14
|
-
|
|
15
|
-
@Component({
|
|
16
|
-
imports: [ChordFinderComponent],
|
|
17
|
-
template: `<the-chords-chord-finder [language]="'es'" />`,
|
|
18
|
-
})
|
|
19
|
-
export class App {}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
`language` accepts `en` or `es` and defaults to `en`.
|
|
23
|
-
|
|
24
|
-
Override the component theme from any ancestor with these inherited CSS variables:
|
|
25
|
-
|
|
26
|
-
```css
|
|
27
|
-
--chords-background;
|
|
28
|
-
--chords-background-alt;
|
|
29
|
-
--chords-surface;
|
|
30
|
-
--chords-surface-elevated;
|
|
31
|
-
--chords-text;
|
|
32
|
-
--chords-muted;
|
|
33
|
-
--chords-primary;
|
|
34
|
-
--chords-secondary;
|
|
35
|
-
--chords-highlight;
|
|
36
|
-
--chords-danger;
|
|
37
|
-
--chords-border;
|
|
38
|
-
--chords-on-primary;
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Build the package with `npm run build:lib`. The publishable Angular package is written to `dist/chord-finder`.
|
|
1
|
+
# @gblp/chord-finder
|
|
2
|
+
|
|
3
|
+
Standalone Angular 22 component for finding guitar chords and rendering SVG chord diagrams.
|
|
4
|
+
|
|
5
|
+
[View the package on npm](https://www.npmjs.com/package/@gblp/chord-finder)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @gblp/chord-finder
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Component } from "@angular/core";
|
|
13
|
+
import { ChordFinderComponent } from "@gblp/chord-finder";
|
|
14
|
+
|
|
15
|
+
@Component({
|
|
16
|
+
imports: [ChordFinderComponent],
|
|
17
|
+
template: `<the-chords-chord-finder [language]="'es'" />`,
|
|
18
|
+
})
|
|
19
|
+
export class App {}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`language` accepts `en` or `es` and defaults to `en`.
|
|
23
|
+
|
|
24
|
+
Override the component theme from any ancestor with these inherited CSS variables:
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
--chords-background;
|
|
28
|
+
--chords-background-alt;
|
|
29
|
+
--chords-surface;
|
|
30
|
+
--chords-surface-elevated;
|
|
31
|
+
--chords-text;
|
|
32
|
+
--chords-muted;
|
|
33
|
+
--chords-primary;
|
|
34
|
+
--chords-secondary;
|
|
35
|
+
--chords-highlight;
|
|
36
|
+
--chords-danger;
|
|
37
|
+
--chords-border;
|
|
38
|
+
--chords-on-primary;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Build the package with `npm run build:lib`. The publishable Angular package is written to `dist/chord-finder`.
|
|
@@ -110,15 +110,26 @@ const ERROR_COPY = {
|
|
|
110
110
|
invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',
|
|
111
111
|
missingRoot: 'Chord root not found in chords-db.',
|
|
112
112
|
missingType: (suffix) => `The type "${suffix}" is not available for this chord.`,
|
|
113
|
+
tooManySections: (max) => `Too many sections — maximum allowed is ${max}.`,
|
|
114
|
+
tooManyChords: (section, max) => `Section "${section}" exceeds the maximum of ${max} chords.`,
|
|
115
|
+
emptySectionName: 'Section name cannot be empty.',
|
|
116
|
+
emptySection: (name) => `Section "${name}" has no chords.`,
|
|
117
|
+
noValidSections: 'No valid sections found. Expected format: "name: chord1, chord2; name2: chord3".',
|
|
113
118
|
},
|
|
114
119
|
es: {
|
|
115
120
|
invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',
|
|
116
121
|
missingRoot: 'Raíz no encontrada en chords-db.',
|
|
117
122
|
missingType: (suffix) => `El tipo "${suffix}" no existe para este acorde en la base actual.`,
|
|
123
|
+
tooManySections: (max) => `Demasiadas secciones — el máximo permitido es ${max}.`,
|
|
124
|
+
tooManyChords: (section, max) => `La sección "${section}" supera el máximo de ${max} acordes.`,
|
|
125
|
+
emptySectionName: 'El nombre de la sección no puede estar vacío.',
|
|
126
|
+
emptySection: (name) => `La sección "${name}" no tiene acordes.`,
|
|
127
|
+
noValidSections: 'No se encontraron secciones válidas. Formato esperado: "nombre: acorde1, acorde2; nombre2: acorde3".',
|
|
118
128
|
},
|
|
119
129
|
};
|
|
120
130
|
class ChordService {
|
|
121
|
-
|
|
131
|
+
MAX_SECTIONS = 6;
|
|
132
|
+
MAX_CHORDS_PER_SECTION = 6;
|
|
122
133
|
guitarDb = guitarDbJson;
|
|
123
134
|
dbRootMap = {
|
|
124
135
|
C: 'C',
|
|
@@ -168,15 +179,59 @@ class ChordService {
|
|
|
168
179
|
4: 'sus4',
|
|
169
180
|
};
|
|
170
181
|
search(input, language = 'en') {
|
|
171
|
-
|
|
182
|
+
return input
|
|
172
183
|
.split(',')
|
|
173
184
|
.map((token) => token.trim())
|
|
185
|
+
.filter(Boolean)
|
|
186
|
+
.map((token, index) => this.searchSingle(token, index, language));
|
|
187
|
+
}
|
|
188
|
+
isSectionFormat(input) {
|
|
189
|
+
return input.includes(':');
|
|
190
|
+
}
|
|
191
|
+
searchSections(input, language = 'en') {
|
|
192
|
+
const copy = ERROR_COPY[language];
|
|
193
|
+
const rawSections = input
|
|
194
|
+
.split(';')
|
|
195
|
+
.map((s) => s.trim())
|
|
174
196
|
.filter(Boolean);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
197
|
+
if (!rawSections.length) {
|
|
198
|
+
return { sections: [], error: copy.noValidSections };
|
|
199
|
+
}
|
|
200
|
+
if (rawSections.length > this.MAX_SECTIONS) {
|
|
201
|
+
return { sections: [], error: copy.tooManySections(this.MAX_SECTIONS) };
|
|
202
|
+
}
|
|
203
|
+
const sections = [];
|
|
204
|
+
for (const [si, raw] of rawSections.entries()) {
|
|
205
|
+
const colonIndex = raw.indexOf(':');
|
|
206
|
+
if (colonIndex === -1) {
|
|
207
|
+
return { sections: [], error: copy.noValidSections };
|
|
208
|
+
}
|
|
209
|
+
const name = raw.slice(0, colonIndex).trim();
|
|
210
|
+
const chordsStr = raw.slice(colonIndex + 1).trim();
|
|
211
|
+
if (!name) {
|
|
212
|
+
return { sections: [], error: copy.emptySectionName };
|
|
213
|
+
}
|
|
214
|
+
const tokens = chordsStr
|
|
215
|
+
.split(',')
|
|
216
|
+
.map((t) => t.trim())
|
|
217
|
+
.filter(Boolean);
|
|
218
|
+
if (!tokens.length) {
|
|
219
|
+
return { sections: [], error: copy.emptySection(name) };
|
|
220
|
+
}
|
|
221
|
+
if (tokens.length > this.MAX_CHORDS_PER_SECTION) {
|
|
222
|
+
return {
|
|
223
|
+
sections: [],
|
|
224
|
+
error: copy.tooManyChords(name, this.MAX_CHORDS_PER_SECTION),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
sections.push({
|
|
228
|
+
name,
|
|
229
|
+
results: tokens.map((token, ci) =>
|
|
230
|
+
// ponytail: offset by si*10 to keep IDs unique across sections (max 6 chords × 6 sections = 36 < 60)
|
|
231
|
+
this.searchSingle(token, si * 10 + ci, language)),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return { sections, error: null };
|
|
180
235
|
}
|
|
181
236
|
suffixes() {
|
|
182
237
|
return this.guitarDb.suffixes;
|
|
@@ -290,7 +345,10 @@ const COPY = {
|
|
|
290
345
|
descriptionEnd: 'separated by commas. Sharps and flats are supported:',
|
|
291
346
|
inputLabel: 'Chords',
|
|
292
347
|
exportPng: 'Export PNG',
|
|
293
|
-
|
|
348
|
+
bgColor: 'Background',
|
|
349
|
+
lineColor: 'Diagram color',
|
|
350
|
+
transparent: 'Transparent',
|
|
351
|
+
download: 'Download',
|
|
294
352
|
availableTypes: 'Available chord types:',
|
|
295
353
|
resultsLabel: 'Chord results',
|
|
296
354
|
chord: 'Chord',
|
|
@@ -309,7 +367,10 @@ const COPY = {
|
|
|
309
367
|
descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',
|
|
310
368
|
inputLabel: 'Acordes',
|
|
311
369
|
exportPng: 'Exportar PNG',
|
|
312
|
-
|
|
370
|
+
bgColor: 'Fondo',
|
|
371
|
+
lineColor: 'Color del diagrama',
|
|
372
|
+
transparent: 'Transparente',
|
|
373
|
+
download: 'Descargar',
|
|
313
374
|
availableTypes: 'Tipos de acorde disponibles:',
|
|
314
375
|
resultsLabel: 'Resultados de acordes',
|
|
315
376
|
chord: 'Acorde',
|
|
@@ -330,20 +391,60 @@ class ChordFinderComponent {
|
|
|
330
391
|
query = 'C';
|
|
331
392
|
results = signal([], /* @ts-ignore */
|
|
332
393
|
...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
|
|
333
|
-
|
|
334
|
-
...(ngDevMode ? [{ debugName: "
|
|
394
|
+
sections = signal([], /* @ts-ignore */
|
|
395
|
+
...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
|
|
396
|
+
inputMode = signal('plain', /* @ts-ignore */
|
|
397
|
+
...(ngDevMode ? [{ debugName: "inputMode" }] : /* istanbul ignore next */ []));
|
|
398
|
+
inputError = signal(null, /* @ts-ignore */
|
|
399
|
+
...(ngDevMode ? [{ debugName: "inputError" }] : /* istanbul ignore next */ []));
|
|
335
400
|
selectedPositionIndex = {};
|
|
336
401
|
supportedSuffixes = computed(() => this.chordService.suffixes().slice(0, 18).join(', '), /* @ts-ignore */
|
|
337
402
|
...(ngDevMode ? [{ debugName: "supportedSuffixes" }] : /* istanbul ignore next */ []));
|
|
338
|
-
|
|
403
|
+
hasResults = computed(() => this.inputMode() === 'sections'
|
|
404
|
+
? this.sections().length > 0
|
|
405
|
+
: this.results().length > 0, /* @ts-ignore */
|
|
406
|
+
...(ngDevMode ? [{ debugName: "hasResults" }] : /* istanbul ignore next */ []));
|
|
407
|
+
resultsRow = viewChild('resultsRow', /* @ts-ignore */
|
|
408
|
+
...(ngDevMode ? [{ debugName: "resultsRow" }] : /* istanbul ignore next */ []));
|
|
409
|
+
sectionsContainer = viewChild('sectionsContainer', /* @ts-ignore */
|
|
410
|
+
...(ngDevMode ? [{ debugName: "sectionsContainer" }] : /* istanbul ignore next */ []));
|
|
411
|
+
exportPanelOpen = signal(false, /* @ts-ignore */
|
|
412
|
+
...(ngDevMode ? [{ debugName: "exportPanelOpen" }] : /* istanbul ignore next */ []));
|
|
413
|
+
exportBgColor = '#ffffff';
|
|
414
|
+
exportLineColor = '#000000';
|
|
415
|
+
exportTransparent = false;
|
|
339
416
|
constructor(chordService) {
|
|
340
417
|
this.chordService = chordService;
|
|
341
418
|
effect(() => this.runSearch(this.language()));
|
|
342
419
|
}
|
|
343
420
|
async exportPng() {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
421
|
+
if (this.inputMode() === 'sections') {
|
|
422
|
+
const container = this.sectionsContainer()?.nativeElement;
|
|
423
|
+
if (!container)
|
|
424
|
+
return;
|
|
425
|
+
for (const [i, section] of this.sections().entries()) {
|
|
426
|
+
const sectionEl = container.querySelector(`[data-section="${i}"]`);
|
|
427
|
+
if (!sectionEl)
|
|
428
|
+
continue;
|
|
429
|
+
const svgs = Array.from(sectionEl.querySelectorAll('svg.chord-svg'));
|
|
430
|
+
if (!svgs.length)
|
|
431
|
+
continue;
|
|
432
|
+
const filename = section.name
|
|
433
|
+
.trim()
|
|
434
|
+
.toLowerCase()
|
|
435
|
+
.replace(/\s+/g, '-')
|
|
436
|
+
.replace(/[^a-z0-9-]/g, '') || 'section';
|
|
437
|
+
await this.renderPng(svgs, filename);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
const svgs = Array.from(this.resultsRow()?.nativeElement.querySelectorAll('svg.chord-svg') ?? []);
|
|
442
|
+
if (!svgs.length)
|
|
443
|
+
return;
|
|
444
|
+
await this.renderPng(svgs, 'chords');
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async renderPng(svgs, filename) {
|
|
347
448
|
const padding = 32;
|
|
348
449
|
const gap = 24;
|
|
349
450
|
const width = 240;
|
|
@@ -357,19 +458,25 @@ class ChordFinderComponent {
|
|
|
357
458
|
if (!ctx)
|
|
358
459
|
return;
|
|
359
460
|
ctx.scale(scale, scale);
|
|
360
|
-
|
|
361
|
-
|
|
461
|
+
if (!this.exportTransparent) {
|
|
462
|
+
ctx.fillStyle = this.exportBgColor;
|
|
463
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
464
|
+
}
|
|
465
|
+
const bg = this.exportTransparent ? 'transparent' : this.exportBgColor;
|
|
466
|
+
const fg = this.exportLineColor;
|
|
467
|
+
// ponytail: text inside filled dots/barres inverts bg; transparent defaults to white
|
|
468
|
+
const fgInverse = this.exportTransparent ? '#ffffff' : this.exportBgColor;
|
|
362
469
|
for (const [index, svg] of svgs.entries()) {
|
|
363
470
|
const clone = svg.cloneNode(true);
|
|
364
|
-
clone.insertAdjacentHTML('afterbegin', `<style>
|
|
365
|
-
.chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}
|
|
366
|
-
.card-bg{fill
|
|
367
|
-
.grid line{stroke
|
|
368
|
-
.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill
|
|
369
|
-
.barres text,.dots text{fill
|
|
370
|
-
.markers text{fill
|
|
371
|
-
.fret-number{fill
|
|
372
|
-
.string-labels text{fill
|
|
471
|
+
clone.insertAdjacentHTML('afterbegin', `<style>
|
|
472
|
+
.chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}
|
|
473
|
+
.card-bg{fill:${bg}}.title{font-size:42px;font-weight:400;fill:${fg}}
|
|
474
|
+
.grid line{stroke:${fg};stroke-width:2.6;stroke-linecap:square}
|
|
475
|
+
.grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:${fg}}
|
|
476
|
+
.barres text,.dots text{fill:${fgInverse};font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}
|
|
477
|
+
.markers text{fill:${fg};font-size:30px;font-weight:400}
|
|
478
|
+
.fret-number{fill:${fg};font-size:42px;font-weight:400}
|
|
479
|
+
.string-labels text{fill:${fg};font-size:18px;font-weight:400}
|
|
373
480
|
</style>`);
|
|
374
481
|
const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], {
|
|
375
482
|
type: 'image/svg+xml',
|
|
@@ -381,17 +488,36 @@ class ChordFinderComponent {
|
|
|
381
488
|
URL.revokeObjectURL(url);
|
|
382
489
|
}
|
|
383
490
|
const link = document.createElement('a');
|
|
384
|
-
link.download =
|
|
491
|
+
link.download = `${filename}.png`;
|
|
385
492
|
link.href = canvas.toDataURL('image/png');
|
|
386
493
|
link.click();
|
|
387
494
|
}
|
|
388
495
|
runSearch(language = this.language()) {
|
|
389
|
-
const
|
|
390
|
-
this.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
496
|
+
const query = this.query.trim();
|
|
497
|
+
if (this.chordService.isSectionFormat(query)) {
|
|
498
|
+
this.inputMode.set('sections');
|
|
499
|
+
const { sections, error } = this.chordService.searchSections(query, language);
|
|
500
|
+
this.inputError.set(error);
|
|
501
|
+
this.sections.set(sections);
|
|
502
|
+
this.results.set([]);
|
|
503
|
+
for (const section of sections) {
|
|
504
|
+
for (const result of section.results) {
|
|
505
|
+
if (this.selectedPositionIndex[result.id] === undefined) {
|
|
506
|
+
this.selectedPositionIndex[result.id] = 0;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
this.inputMode.set('plain');
|
|
513
|
+
this.inputError.set(null);
|
|
514
|
+
const results = this.chordService.search(query, language);
|
|
515
|
+
this.results.set(results);
|
|
516
|
+
this.sections.set([]);
|
|
517
|
+
for (const result of results) {
|
|
518
|
+
if (this.selectedPositionIndex[result.id] === undefined) {
|
|
519
|
+
this.selectedPositionIndex[result.id] = 0;
|
|
520
|
+
}
|
|
395
521
|
}
|
|
396
522
|
}
|
|
397
523
|
}
|
|
@@ -411,12 +537,12 @@ class ChordFinderComponent {
|
|
|
411
537
|
return result.id;
|
|
412
538
|
}
|
|
413
539
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, deps: [{ token: ChordService }], target: i0.ɵɵFactoryTarget.Component });
|
|
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 });
|
|
540
|
+
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 }, { propertyName: "sectionsContainer", first: true, predicate: ["sectionsContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<main class=\"page-shell\">\r\n <section class=\"hero-card\">\r\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\r\n <h1>{{ text().title }}</h1>\r\n\r\n <p class=\"description\">\r\n {{ text().descriptionStart }}\r\n <strong>{{ text().descriptionLimit }}</strong>\r\n {{ text().descriptionEnd }}\r\n <strong>C, F#, C#m, Bb</strong>.\r\n </p>\r\n\r\n <div class=\"search-row\">\r\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\r\n <input\r\n id=\"chordInput\"\r\n type=\"text\"\r\n [(ngModel)]=\"query\"\r\n (input)=\"runSearch()\"\r\n placeholder=\"C, F#, C#m, Bb\"\r\n autocomplete=\"off\"\r\n />\r\n <div class=\"export-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"export-btn\"\r\n (click)=\"exportPanelOpen.set(!exportPanelOpen())\"\r\n [disabled]=\"!hasResults()\"\r\n [class.open]=\"exportPanelOpen()\"\r\n >\r\n {{ text().exportPng }}\r\n <span class=\"chevron\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n <div class=\"export-panel\" [class.open]=\"exportPanelOpen()\">\r\n <div class=\"export-panel-inner\">\r\n <div class=\"export-options\">\r\n <label class=\"color-opt\">\r\n <span>{{ text().bgColor }}</span>\r\n <div class=\"color-row\">\r\n <input\r\n type=\"color\"\r\n [(ngModel)]=\"exportBgColor\"\r\n [disabled]=\"exportTransparent\"\r\n />\r\n <label class=\"check-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"exportTransparent\" />\r\n {{ text().transparent }}\r\n </label>\r\n </div>\r\n </label>\r\n <label class=\"color-opt\">\r\n <span>{{ text().lineColor }}</span>\r\n <input type=\"color\" [(ngModel)]=\"exportLineColor\" />\r\n </label>\r\n <button\r\n type=\"button\"\r\n class=\"download-btn\"\r\n (click)=\"exportPng()\"\r\n [disabled]=\"!hasResults()\"\r\n >\r\n {{ text().download }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (inputError()) {\r\n <p class=\"input-error\">{{ inputError() }}</p>\r\n }\r\n\r\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\r\n </section>\r\n\r\n @if (inputMode() === 'plain') {\r\n <section\r\n #resultsRow\r\n class=\"results-row\"\r\n [attr.aria-label]=\"text().resultsLabel\"\r\n >\r\n @for (result of results(); track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </section>\r\n } @else if (inputMode() === 'sections') {\r\n <div #sectionsContainer class=\"sections-container\">\r\n @for (section of sections(); let si = $index; track si) {\r\n <section\r\n class=\"section-block\"\r\n [attr.data-section]=\"si\"\r\n [attr.aria-label]=\"section.name\"\r\n >\r\n <h2 class=\"section-name\">{{ section.name }}</h2>\r\n <div class=\"results-row\">\r\n @for (result of section.results; track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">\r\n {{ text().normalizedAs }} {{ result.dbName }}\r\n </p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n\r\n <footer class=\"app-footer\">\r\n <p>\r\n {{ text().openSource }}\r\n <a\r\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n elparaquecosadeque/chord-generator </a\r\n >. {{ text().contributions }}\r\n </p>\r\n <p>{{ text().assistance }}</p>\r\n </footer>\r\n</main>\r\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:not([type=color]):not([type=checkbox]){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:not([type=color]):not([type=checkbox]):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}.input-error{margin:12px 0 0;color:var(--_chords-danger);font-size:14px;font-weight:700}.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}.export-wrap{display:flex;flex-direction:column;align-items:flex-start}.export-btn{display:flex;align-items:center;gap:8px}.export-btn .chevron{display:inline-block;transition:transform .28s ease}.export-btn.open .chevron{transform:rotate(180deg)}.export-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .28s ease}.export-panel.open{grid-template-rows:1fr}.export-panel-inner{overflow:hidden}.export-options{display:flex;flex-wrap:wrap;align-items:flex-end;gap:16px;padding:14px 2px 4px}.color-opt{display:flex;flex-direction:column;gap:6px;color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase;cursor:default}.color-row{display:flex;align-items:center;gap:10px}.check-label{display:flex;align-items:center;gap:6px;color:var(--_chords-muted);font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;cursor:pointer}.check-label input[type=checkbox]{accent-color:var(--_chords-primary);width:16px;height:16px;cursor:pointer}input[type=color]{width:44px;height:36px;padding:2px;background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:8px;cursor:pointer}input[type=color]:disabled{opacity:.35;cursor:not-allowed}.search-row .download-btn{background:var(--_chords-secondary)}.sections-container{display:flex;flex-direction:column;gap:40px;margin-top:34px}.section-block{display:flex;flex-direction:column;gap:0}.section-name{margin:0 0 16px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.2em;text-transform:uppercase;border-bottom:1px solid color-mix(in srgb,var(--_chords-primary) 35%,transparent);padding-bottom:10px}.section-block .results-row{margin-top:0}\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.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { 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 });
|
|
415
541
|
}
|
|
416
542
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, decorators: [{
|
|
417
543
|
type: Component,
|
|
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 }] }] } });
|
|
544
|
+
args: [{ selector: 'the-chords-chord-finder', standalone: true, imports: [FormsModule, ChordDiagram], changeDetection: ChangeDetectionStrategy.Eager, host: { '[attr.lang]': 'language()' }, template: "<main class=\"page-shell\">\r\n <section class=\"hero-card\">\r\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\r\n <h1>{{ text().title }}</h1>\r\n\r\n <p class=\"description\">\r\n {{ text().descriptionStart }}\r\n <strong>{{ text().descriptionLimit }}</strong>\r\n {{ text().descriptionEnd }}\r\n <strong>C, F#, C#m, Bb</strong>.\r\n </p>\r\n\r\n <div class=\"search-row\">\r\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\r\n <input\r\n id=\"chordInput\"\r\n type=\"text\"\r\n [(ngModel)]=\"query\"\r\n (input)=\"runSearch()\"\r\n placeholder=\"C, F#, C#m, Bb\"\r\n autocomplete=\"off\"\r\n />\r\n <div class=\"export-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"export-btn\"\r\n (click)=\"exportPanelOpen.set(!exportPanelOpen())\"\r\n [disabled]=\"!hasResults()\"\r\n [class.open]=\"exportPanelOpen()\"\r\n >\r\n {{ text().exportPng }}\r\n <span class=\"chevron\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n <div class=\"export-panel\" [class.open]=\"exportPanelOpen()\">\r\n <div class=\"export-panel-inner\">\r\n <div class=\"export-options\">\r\n <label class=\"color-opt\">\r\n <span>{{ text().bgColor }}</span>\r\n <div class=\"color-row\">\r\n <input\r\n type=\"color\"\r\n [(ngModel)]=\"exportBgColor\"\r\n [disabled]=\"exportTransparent\"\r\n />\r\n <label class=\"check-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"exportTransparent\" />\r\n {{ text().transparent }}\r\n </label>\r\n </div>\r\n </label>\r\n <label class=\"color-opt\">\r\n <span>{{ text().lineColor }}</span>\r\n <input type=\"color\" [(ngModel)]=\"exportLineColor\" />\r\n </label>\r\n <button\r\n type=\"button\"\r\n class=\"download-btn\"\r\n (click)=\"exportPng()\"\r\n [disabled]=\"!hasResults()\"\r\n >\r\n {{ text().download }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (inputError()) {\r\n <p class=\"input-error\">{{ inputError() }}</p>\r\n }\r\n\r\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\r\n </section>\r\n\r\n @if (inputMode() === 'plain') {\r\n <section\r\n #resultsRow\r\n class=\"results-row\"\r\n [attr.aria-label]=\"text().resultsLabel\"\r\n >\r\n @for (result of results(); track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </section>\r\n } @else if (inputMode() === 'sections') {\r\n <div #sectionsContainer class=\"sections-container\">\r\n @for (section of sections(); let si = $index; track si) {\r\n <section\r\n class=\"section-block\"\r\n [attr.data-section]=\"si\"\r\n [attr.aria-label]=\"section.name\"\r\n >\r\n <h2 class=\"section-name\">{{ section.name }}</h2>\r\n <div class=\"results-row\">\r\n @for (result of section.results; track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">\r\n {{ text().normalizedAs }} {{ result.dbName }}\r\n </p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n\r\n <footer class=\"app-footer\">\r\n <p>\r\n {{ text().openSource }}\r\n <a\r\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n elparaquecosadeque/chord-generator </a\r\n >. {{ text().contributions }}\r\n </p>\r\n <p>{{ text().assistance }}</p>\r\n </footer>\r\n</main>\r\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:not([type=color]):not([type=checkbox]){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:not([type=color]):not([type=checkbox]):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}.input-error{margin:12px 0 0;color:var(--_chords-danger);font-size:14px;font-weight:700}.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}.export-wrap{display:flex;flex-direction:column;align-items:flex-start}.export-btn{display:flex;align-items:center;gap:8px}.export-btn .chevron{display:inline-block;transition:transform .28s ease}.export-btn.open .chevron{transform:rotate(180deg)}.export-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .28s ease}.export-panel.open{grid-template-rows:1fr}.export-panel-inner{overflow:hidden}.export-options{display:flex;flex-wrap:wrap;align-items:flex-end;gap:16px;padding:14px 2px 4px}.color-opt{display:flex;flex-direction:column;gap:6px;color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase;cursor:default}.color-row{display:flex;align-items:center;gap:10px}.check-label{display:flex;align-items:center;gap:6px;color:var(--_chords-muted);font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;cursor:pointer}.check-label input[type=checkbox]{accent-color:var(--_chords-primary);width:16px;height:16px;cursor:pointer}input[type=color]{width:44px;height:36px;padding:2px;background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:8px;cursor:pointer}input[type=color]:disabled{opacity:.35;cursor:not-allowed}.search-row .download-btn{background:var(--_chords-secondary)}.sections-container{display:flex;flex-direction:column;gap:40px;margin-top:34px}.section-block{display:flex;flex-direction:column;gap:0}.section-name{margin:0 0 16px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.2em;text-transform:uppercase;border-bottom:1px solid color-mix(in srgb,var(--_chords-primary) 35%,transparent);padding-bottom:10px}.section-block .results-row{margin-top:0}\n"] }]
|
|
545
|
+
}], ctorParameters: () => [{ type: ChordService }], propDecorators: { language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], resultsRow: [{ type: i0.ViewChild, args: ['resultsRow', { isSignal: true }] }], sectionsContainer: [{ type: i0.ViewChild, args: ['sectionsContainer', { isSignal: true }] }] } });
|
|
420
546
|
|
|
421
547
|
/*
|
|
422
548
|
* 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';\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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"}
|
|
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';\r\nimport guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';\r\nimport {\r\n ChordSearchResult,\r\n ChordSection,\r\n ChordsDbInstrument,\r\n Language,\r\n ParsedChord,\r\n} from '../models/chord.model';\r\n\r\nconst ERROR_COPY = {\r\n en: {\r\n invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',\r\n missingRoot: 'Chord root not found in chords-db.',\r\n missingType: (suffix: string) =>\r\n `The type \"${suffix}\" is not available for this chord.`,\r\n tooManySections: (max: number) =>\r\n `Too many sections — maximum allowed is ${max}.`,\r\n tooManyChords: (section: string, max: number) =>\r\n `Section \"${section}\" exceeds the maximum of ${max} chords.`,\r\n emptySectionName: 'Section name cannot be empty.',\r\n emptySection: (name: string) => `Section \"${name}\" has no chords.`,\r\n noValidSections:\r\n 'No valid sections found. Expected format: \"name: chord1, chord2; name2: chord3\".',\r\n },\r\n es: {\r\n invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',\r\n missingRoot: 'Raíz no encontrada en chords-db.',\r\n missingType: (suffix: string) =>\r\n `El tipo \"${suffix}\" no existe para este acorde en la base actual.`,\r\n tooManySections: (max: number) =>\r\n `Demasiadas secciones — el máximo permitido es ${max}.`,\r\n tooManyChords: (section: string, max: number) =>\r\n `La sección \"${section}\" supera el máximo de ${max} acordes.`,\r\n emptySectionName: 'El nombre de la sección no puede estar vacío.',\r\n emptySection: (name: string) =>\r\n `La sección \"${name}\" no tiene acordes.`,\r\n noValidSections:\r\n 'No se encontraron secciones válidas. Formato esperado: \"nombre: acorde1, acorde2; nombre2: acorde3\".',\r\n },\r\n} satisfies Record<\r\n Language,\r\n {\r\n invalid: string;\r\n missingRoot: string;\r\n missingType: (suffix: string) => string;\r\n tooManySections: (max: number) => string;\r\n tooManyChords: (section: string, max: number) => string;\r\n emptySectionName: string;\r\n emptySection: (name: string) => string;\r\n noValidSections: string;\r\n }\r\n>;\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class ChordService {\r\n private readonly MAX_SECTIONS = 6;\r\n private readonly MAX_CHORDS_PER_SECTION = 6;\r\n\r\n private readonly guitarDb = guitarDbJson as ChordsDbInstrument;\r\n\r\n private readonly dbRootMap: Record<string, string> = {\r\n C: 'C',\r\n 'C#': 'Csharp',\r\n Db: 'Csharp',\r\n D: 'D',\r\n 'D#': 'Eb',\r\n Eb: 'Eb',\r\n E: 'E',\r\n Fb: 'E',\r\n 'E#': 'F',\r\n F: 'F',\r\n 'F#': 'Fsharp',\r\n Gb: 'Fsharp',\r\n G: 'G',\r\n 'G#': 'Ab',\r\n Ab: 'Ab',\r\n A: 'A',\r\n 'A#': 'Bb',\r\n Bb: 'Bb',\r\n B: 'B',\r\n Cb: 'B',\r\n 'B#': 'C',\r\n };\r\n\r\n private readonly suffixAliases: Record<string, string> = {\r\n '': 'major',\r\n M: 'major',\r\n maj: 'major',\r\n major: 'major',\r\n m: 'minor',\r\n min: 'minor',\r\n '-': 'minor',\r\n minor: 'minor',\r\n Δ: 'maj7',\r\n maj7: 'maj7',\r\n M7: 'maj7',\r\n m7: 'm7',\r\n min7: 'm7',\r\n dim: 'dim',\r\n diminished: 'dim',\r\n aug: 'aug',\r\n augmented: 'aug',\r\n sus: 'sus4',\r\n sus2: 'sus2',\r\n sus4: 'sus4',\r\n add9: 'add9',\r\n 4: 'sus4',\r\n };\r\n\r\n search(\r\n input: string,\r\n language: Language = 'en',\r\n ): ChordSearchResult[] {\r\n return input\r\n .split(',')\r\n .map((token) => token.trim())\r\n .filter(Boolean)\r\n .map((token, index) => this.searchSingle(token, index, language));\r\n }\r\n\r\n isSectionFormat(input: string): boolean {\r\n return input.includes(':');\r\n }\r\n\r\n searchSections(\r\n input: string,\r\n language: Language = 'en',\r\n ): { sections: ChordSection[]; error: string | null } {\r\n const copy = ERROR_COPY[language];\r\n\r\n const rawSections = input\r\n .split(';')\r\n .map((s) => s.trim())\r\n .filter(Boolean);\r\n\r\n if (!rawSections.length) {\r\n return { sections: [], error: copy.noValidSections };\r\n }\r\n\r\n if (rawSections.length > this.MAX_SECTIONS) {\r\n return { sections: [], error: copy.tooManySections(this.MAX_SECTIONS) };\r\n }\r\n\r\n const sections: ChordSection[] = [];\r\n\r\n for (const [si, raw] of rawSections.entries()) {\r\n const colonIndex = raw.indexOf(':');\r\n if (colonIndex === -1) {\r\n return { sections: [], error: copy.noValidSections };\r\n }\r\n\r\n const name = raw.slice(0, colonIndex).trim();\r\n const chordsStr = raw.slice(colonIndex + 1).trim();\r\n\r\n if (!name) {\r\n return { sections: [], error: copy.emptySectionName };\r\n }\r\n\r\n const tokens = chordsStr\r\n .split(',')\r\n .map((t) => t.trim())\r\n .filter(Boolean);\r\n\r\n if (!tokens.length) {\r\n return { sections: [], error: copy.emptySection(name) };\r\n }\r\n\r\n if (tokens.length > this.MAX_CHORDS_PER_SECTION) {\r\n return {\r\n sections: [],\r\n error: copy.tooManyChords(name, this.MAX_CHORDS_PER_SECTION),\r\n };\r\n }\r\n\r\n sections.push({\r\n name,\r\n results: tokens.map((token, ci) =>\r\n // ponytail: offset by si*10 to keep IDs unique across sections (max 6 chords × 6 sections = 36 < 60)\r\n this.searchSingle(token, si * 10 + ci, language),\r\n ),\r\n });\r\n }\r\n\r\n return { sections, error: null };\r\n }\r\n\r\n suffixes(): string[] {\r\n return this.guitarDb.suffixes;\r\n }\r\n\r\n private searchSingle(\r\n token: string,\r\n index: number,\r\n language: Language,\r\n ): ChordSearchResult {\r\n const parsed = this.parseChordName(token);\r\n const copy = ERROR_COPY[language];\r\n\r\n if (!parsed) {\r\n return {\r\n id: `${index}-${token}`,\r\n raw: token,\r\n displayName: token,\r\n positions: [],\r\n error: copy.invalid,\r\n };\r\n }\r\n const chordFamily = this.guitarDb.chords[parsed.dbRoot];\r\n\r\n if (!chordFamily) {\r\n return {\r\n id: `${index}-${parsed.displayName}`,\r\n raw: token,\r\n displayName: parsed.displayName,\r\n positions: [],\r\n error: copy.missingRoot,\r\n };\r\n }\r\n\r\n const chord = chordFamily.find((item) => item.suffix === parsed.suffix);\r\n\r\n if (!chord) {\r\n return {\r\n id: `${index}-${parsed.displayName}`,\r\n raw: token,\r\n displayName: parsed.displayName,\r\n dbName: `${parsed.dbRoot} ${parsed.suffix}`,\r\n positions: [],\r\n error: copy.missingType(parsed.suffix),\r\n };\r\n }\r\n\r\n return {\r\n id: `${index}-${parsed.dbRoot}-${parsed.suffix}`,\r\n raw: token,\r\n displayName: parsed.displayName,\r\n dbName: `${chord.key} ${chord.suffix}`,\r\n chord,\r\n positions: chord.positions,\r\n };\r\n }\r\n\r\n private parseChordName(raw: string): ParsedChord | null {\r\n const cleaned = raw\r\n .replaceAll('♯', '#')\r\n .replaceAll('♭', 'b')\r\n .replace(/\\s+/g, '');\r\n\r\n const match = cleaned.match(/^([A-Ga-g])([#b]?)(.*)$/);\r\n if (!match) return null;\r\n\r\n const letter = match[1].toUpperCase();\r\n const accidental = match[2] ?? '';\r\n const suffixRaw = match[3] ?? '';\r\n const root = `${letter}${accidental}`;\r\n const dbRoot = this.dbRootMap[root];\r\n\r\n if (!dbRoot) return null;\r\n\r\n const suffix = this.normalizeSuffix(suffixRaw);\r\n if (!suffix) return null;\r\n\r\n return {\r\n raw,\r\n root,\r\n dbRoot,\r\n displayName: `${root}${this.displaySuffix(suffix)}`,\r\n suffix,\r\n };\r\n }\r\n\r\n private normalizeSuffix(rawSuffix: string): string | null {\r\n const trimmed = rawSuffix.trim();\r\n\r\n if (this.suffixAliases[trimmed] !== undefined) {\r\n return this.suffixAliases[trimmed];\r\n }\r\n\r\n const lower = trimmed.toLowerCase();\r\n if (this.suffixAliases[lower] !== undefined) {\r\n return this.suffixAliases[lower];\r\n }\r\n\r\n if (this.guitarDb.suffixes.includes(trimmed)) {\r\n return trimmed;\r\n }\r\n\r\n if (this.guitarDb.suffixes.includes(lower)) {\r\n return lower;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private displaySuffix(suffix: string): string {\r\n if (suffix === 'major') return '';\r\n if (suffix === 'minor') return 'm';\r\n return suffix;\r\n }\r\n}\r\n","import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n computed,\r\n effect,\r\n input,\r\n signal,\r\n viewChild,\r\n} from '@angular/core';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { ChordDiagram } from './components/chord-diagram/chord-diagram';\r\nimport {\r\n ChordSearchResult,\r\n ChordSection,\r\n ChordsDbPosition,\r\n Language,\r\n} from './models/chord.model';\r\nimport { ChordService } from './services/chord.service';\r\n\r\nconst COPY = {\r\n en: {\r\n eyebrow: 'Angular · SVG · chords-db',\r\n title: 'Chord Finder',\r\n descriptionStart: 'Enter up to',\r\n descriptionLimit: '5 chords',\r\n descriptionEnd: 'separated by commas. Sharps and flats are supported:',\r\n inputLabel: 'Chords',\r\n exportPng: 'Export PNG',\r\n bgColor: 'Background',\r\n lineColor: 'Diagram color',\r\n transparent: 'Transparent',\r\n download: 'Download',\r\n availableTypes: 'Available chord types:',\r\n resultsLabel: 'Chord results',\r\n chord: 'Chord',\r\n normalizedAs: 'Normalized as',\r\n position: 'Position',\r\n of: 'of',\r\n openSource: 'Open source project:',\r\n contributions: 'Contributions are welcome.',\r\n assistance:\r\n 'Built with AI assistance under supervision from the repository owner.',\r\n },\r\n es: {\r\n eyebrow: 'Angular · SVG · chords-db',\r\n title: 'Buscador de acordes',\r\n descriptionStart: 'Escribe hasta',\r\n descriptionLimit: '5 acordes',\r\n descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',\r\n inputLabel: 'Acordes',\r\n exportPng: 'Exportar PNG',\r\n bgColor: 'Fondo',\r\n lineColor: 'Color del diagrama',\r\n transparent: 'Transparente',\r\n download: 'Descargar',\r\n availableTypes: 'Tipos de acorde disponibles:',\r\n resultsLabel: 'Resultados de acordes',\r\n chord: 'Acorde',\r\n normalizedAs: 'Normalizado como',\r\n position: 'Posición',\r\n of: 'de',\r\n openSource: 'Proyecto de código abierto:',\r\n contributions: 'Las contribuciones son bienvenidas.',\r\n assistance:\r\n 'Creado con asistencia de IA bajo la supervisión del propietario del repositorio.',\r\n },\r\n} as const;\r\n\r\n@Component({\r\n selector: 'the-chords-chord-finder',\r\n standalone: true,\r\n imports: [FormsModule, ChordDiagram],\r\n templateUrl: './chord-finder.html',\r\n changeDetection: ChangeDetectionStrategy.Eager,\r\n styleUrl: './chord-finder.scss',\r\n host: { '[attr.lang]': 'language()' },\r\n})\r\nexport class ChordFinderComponent {\r\n readonly language = input<Language>('en');\r\n readonly text = computed(() => COPY[this.language()]);\r\n query = 'C';\r\n results = signal<ChordSearchResult[]>([]);\r\n sections = signal<ChordSection[]>([]);\r\n inputMode = signal<'plain' | 'sections'>('plain');\r\n inputError = signal<string | null>(null);\r\n selectedPositionIndex: Record<string, number> = {};\r\n supportedSuffixes = computed(() =>\r\n this.chordService.suffixes().slice(0, 18).join(', '),\r\n );\r\n readonly hasResults = computed(() =>\r\n this.inputMode() === 'sections'\r\n ? this.sections().length > 0\r\n : this.results().length > 0,\r\n );\r\n\r\n resultsRow = viewChild<ElementRef<HTMLElement>>('resultsRow');\r\n sectionsContainer = viewChild<ElementRef<HTMLElement>>('sectionsContainer');\r\n\r\n exportPanelOpen = signal(false);\r\n exportBgColor = '#ffffff';\r\n exportLineColor = '#000000';\r\n exportTransparent = false;\r\n\r\n constructor(private readonly chordService: ChordService) {\r\n effect(() => this.runSearch(this.language()));\r\n }\r\n\r\n async exportPng(): Promise<void> {\r\n if (this.inputMode() === 'sections') {\r\n const container = this.sectionsContainer()?.nativeElement;\r\n if (!container) return;\r\n for (const [i, section] of this.sections().entries()) {\r\n const sectionEl = container.querySelector<HTMLElement>(\r\n `[data-section=\"${i}\"]`,\r\n );\r\n if (!sectionEl) continue;\r\n const svgs = Array.from(\r\n sectionEl.querySelectorAll<SVGSVGElement>('svg.chord-svg'),\r\n );\r\n if (!svgs.length) continue;\r\n const filename =\r\n section.name\r\n .trim()\r\n .toLowerCase()\r\n .replace(/\\s+/g, '-')\r\n .replace(/[^a-z0-9-]/g, '') || 'section';\r\n await this.renderPng(svgs, filename);\r\n }\r\n } else {\r\n const svgs = Array.from(\r\n this.resultsRow()?.nativeElement.querySelectorAll<SVGSVGElement>(\r\n 'svg.chord-svg',\r\n ) ?? [],\r\n );\r\n if (!svgs.length) return;\r\n await this.renderPng(svgs, 'chords');\r\n }\r\n }\r\n\r\n private async renderPng(\r\n svgs: SVGSVGElement[],\r\n filename: string,\r\n ): Promise<void> {\r\n const padding = 32;\r\n const gap = 24;\r\n const width = 240;\r\n const height = 330;\r\n const scale = 2;\r\n\r\n const canvas = document.createElement('canvas');\r\n canvas.width =\r\n (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;\r\n canvas.height = (padding * 2 + height) * scale;\r\n\r\n const ctx = canvas.getContext('2d');\r\n if (!ctx) return;\r\n\r\n ctx.scale(scale, scale);\r\n\r\n if (!this.exportTransparent) {\r\n ctx.fillStyle = this.exportBgColor;\r\n ctx.fillRect(0, 0, canvas.width, canvas.height);\r\n }\r\n\r\n const bg = this.exportTransparent ? 'transparent' : this.exportBgColor;\r\n const fg = this.exportLineColor;\r\n // ponytail: text inside filled dots/barres inverts bg; transparent defaults to white\r\n const fgInverse = this.exportTransparent ? '#ffffff' : this.exportBgColor;\r\n\r\n for (const [index, svg] of svgs.entries()) {\r\n const clone = svg.cloneNode(true) as SVGSVGElement;\r\n\r\n clone.insertAdjacentHTML(\r\n 'afterbegin',\r\n `<style>\r\n .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}\r\n .card-bg{fill:${bg}}.title{font-size:42px;font-weight:400;fill:${fg}}\r\n .grid line{stroke:${fg};stroke-width:2.6;stroke-linecap:square}\r\n .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:${fg}}\r\n .barres text,.dots text{fill:${fgInverse};font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}\r\n .markers text{fill:${fg};font-size:30px;font-weight:400}\r\n .fret-number{fill:${fg};font-size:42px;font-weight:400}\r\n .string-labels text{fill:${fg};font-size:18px;font-weight:400}\r\n </style>`,\r\n );\r\n\r\n const url = URL.createObjectURL(\r\n new Blob([new XMLSerializer().serializeToString(clone)], {\r\n type: 'image/svg+xml',\r\n }),\r\n );\r\n const image = new Image();\r\n image.src = url;\r\n await image.decode();\r\n\r\n ctx.drawImage(\r\n image,\r\n padding + index * (width + gap),\r\n padding,\r\n width,\r\n height,\r\n );\r\n URL.revokeObjectURL(url);\r\n }\r\n\r\n const link = document.createElement('a');\r\n link.download = `${filename}.png`;\r\n link.href = canvas.toDataURL('image/png');\r\n link.click();\r\n }\r\n\r\n runSearch(language: Language = this.language()): void {\r\n const query = this.query.trim();\r\n\r\n if (this.chordService.isSectionFormat(query)) {\r\n this.inputMode.set('sections');\r\n const { sections, error } = this.chordService.searchSections(\r\n query,\r\n language,\r\n );\r\n this.inputError.set(error);\r\n this.sections.set(sections);\r\n this.results.set([]);\r\n for (const section of sections) {\r\n for (const result of section.results) {\r\n if (this.selectedPositionIndex[result.id] === undefined) {\r\n this.selectedPositionIndex[result.id] = 0;\r\n }\r\n }\r\n }\r\n } else {\r\n this.inputMode.set('plain');\r\n this.inputError.set(null);\r\n const results = this.chordService.search(query, language);\r\n this.results.set(results);\r\n this.sections.set([]);\r\n for (const result of results) {\r\n if (this.selectedPositionIndex[result.id] === undefined) {\r\n this.selectedPositionIndex[result.id] = 0;\r\n }\r\n }\r\n }\r\n }\r\n\r\n selectPosition(resultId: string, value: string | number): void {\r\n this.selectedPositionIndex[resultId] = Number(value);\r\n }\r\n\r\n selectedIndex(resultId: string): number {\r\n return this.selectedPositionIndex[resultId] ?? 0;\r\n }\r\n\r\n selectedPosition(result: ChordSearchResult): ChordsDbPosition | null {\r\n if (!result.positions.length) return null;\r\n return (\r\n result.positions[this.selectedPositionIndex[result.id] ?? 0] ??\r\n result.positions[0]\r\n );\r\n }\r\n\r\n trackByResultId(_: number, result: ChordSearchResult): string {\r\n return result.id;\r\n }\r\n}\r\n","<main class=\"page-shell\">\r\n <section class=\"hero-card\">\r\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\r\n <h1>{{ text().title }}</h1>\r\n\r\n <p class=\"description\">\r\n {{ text().descriptionStart }}\r\n <strong>{{ text().descriptionLimit }}</strong>\r\n {{ text().descriptionEnd }}\r\n <strong>C, F#, C#m, Bb</strong>.\r\n </p>\r\n\r\n <div class=\"search-row\">\r\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\r\n <input\r\n id=\"chordInput\"\r\n type=\"text\"\r\n [(ngModel)]=\"query\"\r\n (input)=\"runSearch()\"\r\n placeholder=\"C, F#, C#m, Bb\"\r\n autocomplete=\"off\"\r\n />\r\n <div class=\"export-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"export-btn\"\r\n (click)=\"exportPanelOpen.set(!exportPanelOpen())\"\r\n [disabled]=\"!hasResults()\"\r\n [class.open]=\"exportPanelOpen()\"\r\n >\r\n {{ text().exportPng }}\r\n <span class=\"chevron\" aria-hidden=\"true\">▾</span>\r\n </button>\r\n <div class=\"export-panel\" [class.open]=\"exportPanelOpen()\">\r\n <div class=\"export-panel-inner\">\r\n <div class=\"export-options\">\r\n <label class=\"color-opt\">\r\n <span>{{ text().bgColor }}</span>\r\n <div class=\"color-row\">\r\n <input\r\n type=\"color\"\r\n [(ngModel)]=\"exportBgColor\"\r\n [disabled]=\"exportTransparent\"\r\n />\r\n <label class=\"check-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"exportTransparent\" />\r\n {{ text().transparent }}\r\n </label>\r\n </div>\r\n </label>\r\n <label class=\"color-opt\">\r\n <span>{{ text().lineColor }}</span>\r\n <input type=\"color\" [(ngModel)]=\"exportLineColor\" />\r\n </label>\r\n <button\r\n type=\"button\"\r\n class=\"download-btn\"\r\n (click)=\"exportPng()\"\r\n [disabled]=\"!hasResults()\"\r\n >\r\n {{ text().download }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (inputError()) {\r\n <p class=\"input-error\">{{ inputError() }}</p>\r\n }\r\n\r\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\r\n </section>\r\n\r\n @if (inputMode() === 'plain') {\r\n <section\r\n #resultsRow\r\n class=\"results-row\"\r\n [attr.aria-label]=\"text().resultsLabel\"\r\n >\r\n @for (result of results(); track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </section>\r\n } @else if (inputMode() === 'sections') {\r\n <div #sectionsContainer class=\"sections-container\">\r\n @for (section of sections(); let si = $index; track si) {\r\n <section\r\n class=\"section-block\"\r\n [attr.data-section]=\"si\"\r\n [attr.aria-label]=\"section.name\"\r\n >\r\n <h2 class=\"section-name\">{{ section.name }}</h2>\r\n <div class=\"results-row\">\r\n @for (result of section.results; track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">\r\n {{ text().normalizedAs }} {{ result.dbName }}\r\n </p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n\r\n <footer class=\"app-footer\">\r\n <p>\r\n {{ text().openSource }}\r\n <a\r\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n elparaquecosadeque/chord-generator </a\r\n >. {{ text().contributions }}\r\n </p>\r\n <p>{{ text().assistance }}</p>\r\n </footer>\r\n</main>\r\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
|
@@ -24,15 +24,22 @@ interface ChordSearchResult {
|
|
|
24
24
|
positions: ChordsDbPosition[];
|
|
25
25
|
error?: string;
|
|
26
26
|
}
|
|
27
|
+
interface ChordSection {
|
|
28
|
+
name: string;
|
|
29
|
+
results: ChordSearchResult[];
|
|
30
|
+
}
|
|
27
31
|
|
|
28
32
|
declare class ChordService {
|
|
29
|
-
readonly
|
|
33
|
+
private readonly MAX_SECTIONS;
|
|
34
|
+
private readonly MAX_CHORDS_PER_SECTION;
|
|
30
35
|
private readonly guitarDb;
|
|
31
36
|
private readonly dbRootMap;
|
|
32
37
|
private readonly suffixAliases;
|
|
33
|
-
search(input: string, language?: Language):
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
search(input: string, language?: Language): ChordSearchResult[];
|
|
39
|
+
isSectionFormat(input: string): boolean;
|
|
40
|
+
searchSections(input: string, language?: Language): {
|
|
41
|
+
sections: ChordSection[];
|
|
42
|
+
error: string | null;
|
|
36
43
|
};
|
|
37
44
|
suffixes(): string[];
|
|
38
45
|
private searchSingle;
|
|
@@ -54,7 +61,10 @@ declare class ChordFinderComponent {
|
|
|
54
61
|
readonly descriptionEnd: "separated by commas. Sharps and flats are supported:";
|
|
55
62
|
readonly inputLabel: "Chords";
|
|
56
63
|
readonly exportPng: "Export PNG";
|
|
57
|
-
readonly
|
|
64
|
+
readonly bgColor: "Background";
|
|
65
|
+
readonly lineColor: "Diagram color";
|
|
66
|
+
readonly transparent: "Transparent";
|
|
67
|
+
readonly download: "Download";
|
|
58
68
|
readonly availableTypes: "Available chord types:";
|
|
59
69
|
readonly resultsLabel: "Chord results";
|
|
60
70
|
readonly chord: "Chord";
|
|
@@ -72,7 +82,10 @@ declare class ChordFinderComponent {
|
|
|
72
82
|
readonly descriptionEnd: "separados por coma. Soporta sostenidos y bemoles:";
|
|
73
83
|
readonly inputLabel: "Acordes";
|
|
74
84
|
readonly exportPng: "Exportar PNG";
|
|
75
|
-
readonly
|
|
85
|
+
readonly bgColor: "Fondo";
|
|
86
|
+
readonly lineColor: "Color del diagrama";
|
|
87
|
+
readonly transparent: "Transparente";
|
|
88
|
+
readonly download: "Descargar";
|
|
76
89
|
readonly availableTypes: "Tipos de acorde disponibles:";
|
|
77
90
|
readonly resultsLabel: "Resultados de acordes";
|
|
78
91
|
readonly chord: "Acorde";
|
|
@@ -85,12 +98,21 @@ declare class ChordFinderComponent {
|
|
|
85
98
|
}>;
|
|
86
99
|
query: string;
|
|
87
100
|
results: _angular_core.WritableSignal<ChordSearchResult[]>;
|
|
88
|
-
|
|
101
|
+
sections: _angular_core.WritableSignal<ChordSection[]>;
|
|
102
|
+
inputMode: _angular_core.WritableSignal<"plain" | "sections">;
|
|
103
|
+
inputError: _angular_core.WritableSignal<string | null>;
|
|
89
104
|
selectedPositionIndex: Record<string, number>;
|
|
90
105
|
supportedSuffixes: _angular_core.Signal<string>;
|
|
91
|
-
|
|
106
|
+
readonly hasResults: _angular_core.Signal<boolean>;
|
|
107
|
+
resultsRow: _angular_core.Signal<ElementRef<HTMLElement> | undefined>;
|
|
108
|
+
sectionsContainer: _angular_core.Signal<ElementRef<HTMLElement> | undefined>;
|
|
109
|
+
exportPanelOpen: _angular_core.WritableSignal<boolean>;
|
|
110
|
+
exportBgColor: string;
|
|
111
|
+
exportLineColor: string;
|
|
112
|
+
exportTransparent: boolean;
|
|
92
113
|
constructor(chordService: ChordService);
|
|
93
114
|
exportPng(): Promise<void>;
|
|
115
|
+
private renderPng;
|
|
94
116
|
runSearch(language?: Language): void;
|
|
95
117
|
selectPosition(resultId: string, value: string | number): void;
|
|
96
118
|
selectedIndex(resultId: string): number;
|