1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
import { Note } from "./scale.mjs";
// open string notes, starting from the deepest string.
const strings = {
string1: 'E',
string2: 'A',
string3: 'D',
string4: 'G',
string5: 'B',
string6: 'E'
};
// convert ‘string1’ ‘fret2’ in ‘form’ to F#
export function fretToNote(form, stringName, fretName) {
const string = strings[stringName];
if (!string) {
return null;
}
if (form.querySelector(`.${stringName}.muted`)) {
return 'x';
} else if (!fretName?.startsWith('fret')) {
return form.querySelector(`.${stringName} .open`).getAttribute("x-data-note");
} else {
return form.querySelector(`.${stringName} [value="${fretName}"]`).parentNode.getAttribute("x-data-note");
}
}
function formChanged(form) {
const formData = new FormData(form);
form.querySelectorAll('tbody .selected').forEach(elt => {
const string = Array.from(elt.parentNode.classList).filter(x => x.startsWith('string'))[0];
const val = formData.get(string);
const note = Note.fromString(fretToNote(form, string, val));
if (note.isSharp) {
elt.innerText = `${note} / ${note.toAlternateString()}`;
} else {
elt.innerText = note;
}
});
}
function handleFormChanged(e) {
formChanged(e.target.form);
}
let mousedownVal = undefined;
function fretMousedown(e) {
// at mousedown time, the form hasn't yet been changed. to support
// deselecting elements, we need to know what the previous
// selection was, so store it here.
//
// it'd be nice to be able to do this just in the click handler.
mousedownVal = e.target.checked;
}
function fretClick(e) {
const elt = e.target;
// if this element was selected at mousedown time, deselect it
// now.
if (mousedownVal) {
elt.checked = false;
// doesn't get called automatically.
formChanged(elt.form);
}
}
function openClick(e) {
const elt = e.target.parentNode;
if (elt.classList.contains('muted')) {
elt.classList.remove('muted');
elt.querySelectorAll('input[type="radio"]').forEach(input => {
input.disabled = false;
})
} else {
elt.classList.add('muted');
elt.querySelectorAll('input[type="radio"]').forEach(input => {
input.checked = false;
input.disabled = true;
})
}
formChanged(elt.closest('form'));
}
// export default function (form, { onSave }) {
// }
export default class extends HTMLElement {
static name = 'x-fretboard'
static register() {
console.debug('Registering Element', this.name, this);
customElements.define(this.name, this);
}
constructor() {
super();
console.debug('Fretboard#constructor', this);
}
connectedCallback() {
console.debug('Fretboard#connectedCallback', this);
// TODO: don't trawl the dom, custom element means we know
// what's inside.
const form = this.querySelector('form');
form.onchange = handleFormChanged;
form.querySelectorAll('.open').forEach(elt => {
elt.onclick = openClick;
});
form.querySelectorAll('input[type="radio"]').forEach(elt => {
elt.onmousedown = fretMousedown;
elt.onclick = fretClick;
});
form.querySelectorAll('.save').forEach(elt => {
elt.onclick = (e) => {
e.preventDefault();
console.log('Fretboard#onSave');
};
})
formChanged(form);
}
}
|