diff options
| author | Brian Cully <bjc@spork.org> | 2025-05-12 21:52:00 -0400 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-05-12 21:52:00 -0400 |
| commit | ceff45422d39530b09d3d797ef88b97190c4f23c (patch) | |
| tree | d3e6387bef8bd19d6eccdb750c67a407b296fcc4 | |
| parent | d1c8f67d699fcd5e4ce262688b89503d3ddc0f88 (diff) | |
| download | chords-ceff45422d39530b09d3d797ef88b97190c4f23c.tar.gz chords-ceff45422d39530b09d3d797ef88b97190c4f23c.zip | |
wip: major refactor for web components and grid layout
- hovering on notes from the key doesn't work.
- need to show fret on/off, not notes, they're too distracting
- save button broken
| -rw-r--r-- | fretboard.mjs | 172 | ||||
| -rw-r--r-- | index.html | 100 | ||||
| -rw-r--r-- | main.css | 22 | ||||
| -rw-r--r-- | main.mjs | 17 | ||||
| -rw-r--r-- | scale.mjs | 2 |
5 files changed, 123 insertions, 190 deletions
diff --git a/fretboard.mjs b/fretboard.mjs index ef9e0b5..7ba0dab 100644 --- a/fretboard.mjs +++ b/fretboard.mjs @@ -1,22 +1,14 @@ -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' -}; +const openStrings = ['E', 'A', 'D', 'G', 'B', 'E']; // convert ‘string1’ ‘fret2’ in ‘form’ to F# export function fretToNote(form, stringName, fretName) { - const string = strings[stringName]; - if (!string) { - return null; + if (!form) { + console.error('fretToNote with no form!'); + return 'C'; } + console.log('lookingforfret', stringName, form, form.querySelector(`.${stringName} .open`)); if (form.querySelector(`.${stringName}.muted`)) { return 'x'; } else if (!fretName?.startsWith('fret')) { @@ -26,64 +18,9 @@ export function fretToNote(form, stringName, fretName) { } } -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 class extends HTMLElement { + static observedAttributes = ['strings', 'frets']; + static name = 'x-fretboard'; static register() { console.debug('Registering Element', this.name, this); @@ -98,22 +35,15 @@ export default class extends HTMLElement { console.debug('Fretboard#constructor', this); } + attributeChangedCallback(name, old, v) { + console.debug('Fretboard#attributeChangedCallback', + this, name, old, v); + this.formChanged(); + } + connectedCallback() { console.debug('Fretboard#connectedCallback', this); - - // TODO: don't trawl the dom, custom element means we know - // what's inside. - this.form = this.querySelector('form'); - this.form.onchange = handleFormChanged; - this.form.querySelectorAll('.open').forEach(elt => { - elt.onclick = openClick; - }); - this.form.querySelectorAll('input[type="radio"]').forEach(elt => { - elt.onmousedown = fretMousedown; - elt.onclick = fretClick; - }); - - this.form.querySelectorAll('.save').forEach(elt => { + this.querySelectorAll('.save').forEach(elt => { elt.onclick = (e) => { console.log('Fretboard#onSave'); e.preventDefault(); @@ -125,16 +55,78 @@ export default class extends HTMLElement { this.dispatchEvent(event); }; }) - formChanged(this.form); + this.formChanged(); } get notes() { console.debug('Fretboard#notes', this); - const formData = new FormData(this.form); - return Array.from(this.form.querySelectorAll('tbody .selected')).map(elt => { - const string = Array.from(elt.parentNode.classList).filter(x => x.startsWith('string'))[0]; - const val = formData.get(string); - return fretToNote(this.form, string, val); + return []; + // const formData = new FormData(this.form); + // return Array.from(this.form.querySelectorAll('tbody .selected')).map(elt => { + // const string = Array.from(elt.parentNode.classList).filter(x => x.startsWith('string'))[0]; + // const val = formData.get(string); + // return fretToNote(this.form, string, val); + // }) + } + + updateSelected(formData) { + // this.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(this, string, val)); + // if (note.isSharp) { + // elt.innerText = `${note} / ${note.toAlternateString()}`; + // } else { + // elt.innerText = note; + // } + // }); + } + + zotStrings() { + console.debug('Fretboard#zotStrings', this); + const tmpl = this.querySelector('template.string'); + console.log(' -- tmpl', tmpl); + const parent = tmpl.parentNode; + parent.querySelectorAll('[slot="string"]').forEach(elt => { + console.debug(' -- del', elt); + parent.removeChild(elt); }) } + + zotForm() { + console.debug('Fretboard#zotForm', this); + this.zotStrings(); + } + + #strings; #frets; + formChanged() { + console.debug('Fretboard#formChanged', this); + // TODO: this is inelegant. ideally, none of this stuff is + // triggered until the form is set, and doing it here is not + // robust. + const [strings, frets] = + [this.getAttribute('strings'), this.getAttribute('frets')].map(s => Number(s)); + console.debug(' -- strings', strings, 'frets', frets); + if (strings && frets && strings !== this.#strings && frets != this.#frets) { + console.debug(' -- rerender table'); + this.zotForm(); + + const tmpl = this.querySelector('template.string'); + for (let i = 0; i < strings; i++) { + console.debug(' -- appending string', i, tmpl); + const item = tmpl.content.cloneNode(true); + console.debug(' -- item', item); + const tonic = openStrings[i]; + item.querySelectorAll('[slot="string"]').forEach(s => { + console.debug(' -- setting tonic', tonic, 'on', s); + s.setAttribute('tonic', tonic); + s.setAttribute('selected', tonic); + s.setAttribute('frets', frets.toString()); + }) + tmpl.parentNode.insertBefore(item, tmpl); + } + this.#frets = frets; + this.#strings = strings; + } + } } @@ -8,95 +8,19 @@ </head> <body> - <x-fretboard> - <form id='fretboard'> - <table> - <thead> - <tr> - <td class='open'>open</td> - <td class='fret1'></td> - <td class='fret2'></td> - <td class='fret3'>•</td> - <td class='fret4'></td> - <td class='fret5'>••</td> - <td class='fret6'></td> - <td class='fret7'>•</td> - <td class='selected'>selected</td> - </tr> - </thead> + <x-fretboard strings='6' frets='7'> + <template class='string'> + <x-string tonic='' frets='' slot='string'> + <li><input type='checkbox' name='muted'></li> + <li class='fret open'><slot name='open'></slot></li> + <template> + <li class='fret'><slot name='fret'></slot></li> + </template> + <li class='selected'><slot name='selected'>selected</slot></li> + </x-string> + </template> - <tbody> - <tr class='string1'> - <td class='open' x-data-note='E'>E</td> - <td x-data-note='F'><input type='radio' name='string1' value='fret1'></td> - <td x-data-note='F♯'><input type='radio' name='string1' value='fret2'></td> - <td x-data-note='G'><input type='radio' name='string1' value='fret3'></td> - <td x-data-note='G♯'><input type='radio' name='string1' value='fret4'></td> - <td x-data-note='A'><input type='radio' name='string1' value='fret5'></td> - <td x-data-note='A♯'><input type='radio' name='string1' value='fret6'></td> - <td x-data-note='B'><input type='radio' name='string1' value='fret7'></td> - <td class='selected'></td> - </tr> - <tr class='string2'> - <td class='open' x-data-note='A'>A</td> - <td x-data-note='A♯'><input type='radio' name='string2' value='fret1'></td> - <td x-data-note='B'><input type='radio' name='string2' value='fret2'></td> - <td x-data-note='C'><input type='radio' name='string2' value='fret3'></td> - <td x-data-note='C♯'><input type='radio' name='string2' value='fret4'></td> - <td x-data-note='D'><input type='radio' name='string2' value='fret5'></td> - <td x-data-note='D♯'><input type='radio' name='string2' value='fret6'></td> - <td x-data-note='E'><input type='radio' name='string2' value='fret7'></td> - <td class='selected'></td> - </tr> - <tr class='string3'> - <td class='open' x-data-note='D'>D</td> - <td x-data-note='D♯'><input type='radio' name='string3' value='fret1'></td> - <td x-data-note='E'><input type='radio' name='string3' value='fret2'></td> - <td x-data-note='F'><input type='radio' name='string3' value='fret3'></td> - <td x-data-note='F♯'><input type='radio' name='string3' value='fret4'></td> - <td x-data-note='G'><input type='radio' name='string3' value='fret5'></td> - <td x-data-note='G♯'><input type='radio' name='string3' value='fret6'></td> - <td x-data-note='A'><input type='radio' name='string3' value='fret7'></td> - <td class='selected'></td> - </tr> - <tr class='string4'> - <td class='open' x-data-note='G'>G</td> - <td x-data-note='G♯'><input type='radio' name='string4' value='fret1'></td> - <td x-data-note='A'><input type='radio' name='string4' value='fret2'></td> - <td x-data-note='A♯'><input type='radio' name='string4' value='fret3'></td> - <td x-data-note='B'><input type='radio' name='string4' value='fret4'></td> - <td x-data-note='C'><input type='radio' name='string4' value='fret5'></td> - <td x-data-note='C♯'><input type='radio' name='string4' value='fret6'></td> - <td x-data-note='D'><input type='radio' name='string4' value='fret7'></td> - <td class='selected'></td> - </tr> - <tr class='string5'> - <td class='open' x-data-note='B'>B</td> - <td x-data-note='C'><input type='radio' name='string5' value='fret1'></td> - <td x-data-note='C♯'><input type='radio' name='string5' value='fret2'></td> - <td x-data-note='D'><input type='radio' name='string5' value='fret3'></td> - <td x-data-note='D♯'><input type='radio' name='string5' value='fret4'></td> - <td x-data-note='E'><input type='radio' name='string5' value='fret5'></td> - <td x-data-note='F'><input type='radio' name='string5' value='fret6'></td> - <td x-data-note='F♯'><input type='radio' name='string5' value='fret7'></td> - <td class='selected'></td> - </tr> - <tr class='string6'> - <td class='open' x-data-note='E'>E</td> - <td x-data-note='F'><input type='radio' name='string6' value='fret1'></td> - <td x-data-note='F♯'><input type='radio' name='string6' value='fret2'></td> - <td x-data-note='G'><input type='radio' name='string6' value='fret3'></td> - <td x-data-note='G♯'><input type='radio' name='string6' value='fret4'></td> - <td x-data-note='A'><input type='radio' name='string6' value='fret5'></td> - <td x-data-note='A♯'><input type='radio' name='string6' value='fret6'></td> - <td x-data-note='B'><input type='radio' name='string6' value='fret7'></td> - <td class='selected'></td> - </tr> - </tbody> - </table> - - <button class='save'>+</button> - </form> + <button class='save'>+</button> </x-fretboard> <x-key-picker> @@ -38,6 +38,24 @@ body { background-color: var(--fret-bg-color-3); } +x-string { + display: grid; + padding: 5px; + cursor: pointer; + list-style: none; + grid-template-rows: 1ex; + /* TODO: this should be put on the element when it's attached. */ + grid-template-columns: repeat(10, 1fr); +} + +x-string li { + text-align: center; +} + +x-string .selected { + border-left: 1px solid black; +} + x-fretboard { display: block; float: left; @@ -45,6 +63,10 @@ x-fretboard { margin-right: 1em; } +x-fretboard .save { + position: right; +} + x-fretboard table { border-collapse: collapse; } @@ -1,20 +1,15 @@ -import Fretboard, { fretToNote } from './fretboard.mjs'; -import KeyPicker from './key-picker.mjs'; +import Fretboard from './fretboard.mjs'; import History from './history.mjs'; - -function notes(form) { - const strings = ['string1', 'string2', 'string3', 'string4', 'string5', 'string6']; - const formData = new FormData(form); - console.debug('notes', form, formData); - return strings.map((klass) => fretToNote(form, klass, formData.get(klass))); -} +import KeyPicker from './key-picker.mjs'; +import String from './string.mjs'; function init() { console.debug('init()', this); + String.register(); Fretboard.register(); - KeyPicker.register(); - History.register(); + // KeyPicker.register(); + // History.register(); // todo: maybe just attach the listener to document? document.querySelectorAll(Fretboard.name).forEach(f => { @@ -103,7 +103,7 @@ const ringHandler = { } }; -const chromaticScale = new Proxy( +export const chromaticScale = new Proxy( ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B'], ringHandler); |
