summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Cully <bjc@spork.org>2025-05-12 22:29:56 -0400
committerBrian Cully <bjc@spork.org>2025-05-12 22:29:56 -0400
commit314a84382ecb31a2f1faa879855ce20b9f442036 (patch)
tree96de3a0e3e5b2685f5cd58b84383fc7d85c47aea
parentceff45422d39530b09d3d797ef88b97190c4f23c (diff)
downloadchords-314a84382ecb31a2f1faa879855ce20b9f442036.tar.gz
chords-314a84382ecb31a2f1faa879855ce20b9f442036.zip
wip: making some progress on the grid
- got radio buttons back, but open/selected still have different interfaces. should use getAttribute() everywhere
-rw-r--r--index.html16
-rw-r--r--main.css17
-rw-r--r--string.mjs155
3 files changed, 180 insertions, 8 deletions
diff --git a/index.html b/index.html
index 52c6ca8..76284f2 100644
--- a/index.html
+++ b/index.html
@@ -11,12 +11,16 @@
<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>
+ <form>
+ <input type='checkbox' name='muted'>
+ <li class='fret open'><slot name='open'></slot></li>
+ <template>
+ <li class='fret'>
+ <input slot='fret' type='radio' name='' value=''>
+ </li>
+ </template>
+ <p class='selected'><slot name='selected'>selected</slot></p>
+ </form>
</x-string>
</template>
diff --git a/main.css b/main.css
index f739fba..425d49e 100644
--- a/main.css
+++ b/main.css
@@ -38,22 +38,35 @@ body {
background-color: var(--fret-bg-color-3);
}
-x-string {
+x-string form {
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);
+ grid-template-columns: repeat(10, 1.5em); /* and use ‘fr’ */
+}
+
+x-string input[type="checkbox"] {
+ text-align: center;
+ width: 1em;
+ height: 1em;
+ display: grid;
+ grid-template-columns: subgrid;
}
x-string li {
text-align: center;
+ display: grid;
+ grid-template-columns: subgrid;
}
x-string .selected {
border-left: 1px solid black;
+ text-align: center;
+ margin: 0;
+ margin-left: 5px;
}
x-fretboard {
diff --git a/string.mjs b/string.mjs
new file mode 100644
index 0000000..4e1f198
--- /dev/null
+++ b/string.mjs
@@ -0,0 +1,155 @@
+import { chromaticScale } from "./scale.mjs";
+
+function dots(i) {
+ switch (i+1) {
+ case 3:
+ case 7:
+ case 9:
+ return '•'
+ case 5:
+ case 12:
+ return '••'
+ }
+}
+
+/*
+ this.querySelectorAll('.open').forEach(elt => {
+ elt.onclick = this.openClicked.bind(this);
+ });
+ this.querySelectorAll('input[type="radio"]').forEach(elt => {
+ elt.onmousedown = this.fretMousedown.bind(this);
+ elt.onclick = this.fretClicked.bind(this);
+ });
+
+
+ #mousedownVal;
+ 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.
+ this.#mousedownVal = e.target.checked;
+ }
+
+ openClicked(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;
+ })
+ }
+ this.formChanged();
+ }
+
+ fretClicked(e) {
+ const elt = e.target;
+ // if this element was selected at mousedown time, deselect it
+ // now.
+ if (this.#mousedownVal) {
+ elt.checked = false;
+ // doesn't get called automatically.
+ this.formChanged();
+ }
+ }
+ */
+
+export default class extends HTMLElement {
+ // TODO: probably not worth observing frets since everything just
+ // gets re-rendered when they change.
+ static observedAttributes = ['frets', 'tonic', 'selected'];
+
+ static name = 'x-string';
+ static register() {
+ console.debug('Registering Element', this.name, this);
+ customElements.define(this.name, this);
+ }
+
+ // TODO: this is a horrible hack, but i don't know how to make
+ // each string's input unique. maybe wrap them in their own form?
+ myName() {
+ return 'string';
+ }
+
+ attributeChangedCallback(name, old, v) {
+ console.debug('String#attributeChangedCallback',
+ this, name, old, v);
+ if (old != v) {
+ this.render();
+ }
+ }
+
+ connectedCallback() {
+ console.debug('String#connectedCallback');
+ }
+
+ zot() {
+ console.debug('String#zot', this);
+ const tmpl = this.querySelector('template');
+ console.log(' -- tmpl', tmpl);
+ this.querySelectorAll('input[slot="fret"]').forEach(elt => {
+ console.debug(' -- del', elt, parent);
+ // TODO: this is precisely what i was trying to avoid: the
+ // code has to know exactly what the template looks like.
+ elt.parentNode.parentNode.removeChild(elt.parentNode);
+ })
+ }
+
+ muteClicked(e) {
+ console.debug('String#muteClicked', this, e);
+ if (this.getAttribute('selected') !== 'x') {
+ this.setAttribute('selected', 'x');
+ this.classList.add('muted');
+ } else {
+ this.setAttribute('selected', this.getAttribute('tonic'));
+ this.classList.remove('muted');
+ }
+ }
+
+ fretClicked(e) {
+ console.debug('String#fretClicked', this, e);
+ console.debug(' -- note', e.target.note);
+ this.setAttribute('selected', e.target.note);
+ }
+
+ render() {
+ console.debug('String#render');
+
+ this.querySelectorAll('input[name="muted"]').forEach(elt => {
+ console.debug(' -- input', elt);
+ elt.onchange = this.muteClicked.bind(this);
+ });
+
+ this.zot();
+
+ const tmpl = this.querySelector('template');
+ this.querySelectorAll('slot[name="open"]').forEach(s => {
+ s.note = this.getAttribute('tonic');
+ s.innerText = s.note;
+ s.parentNode.onclick = this.fretClicked.bind(this);
+ })
+
+ const chromIndex = chromaticScale.indexOf(this.getAttribute('tonic'));
+ const frets = Number(this.getAttribute('frets'));
+ for (let i = 1; i <= frets; i++) {
+ const item = tmpl.content.cloneNode(true);
+ item.querySelectorAll('input[slot="fret"]').forEach(s => {
+ s.setAttribute('name', this.myName());
+ s.setAttribute('value', chromaticScale[chromIndex + i]);
+ s.parentNode.onclick = this.fretClicked.bind(this);
+ })
+ tmpl.parentNode.insertBefore(item, tmpl);
+ }
+
+ this.querySelectorAll('slot[name="selected"]').forEach(s => {
+ s.innerText = this.getAttribute('selected');
+ })
+ }
+}