summaryrefslogtreecommitdiffstats
path: root/fretboard.mjs
blob: 38fdc0f8eface5ada7f7cf45996b15063db7d3a7 (plain)
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);
    }
}