From 4565d082f3686036d2de07fb57b9fda330204ee8 Mon Sep 17 00:00:00 2001
From: siroshimadenagasaki
<161388775+siroshimadenagasaki@users.noreply.github.com>
Date: Tue, 15 Oct 2024 19:05:21 -0300
Subject: [PATCH] ok
---
trash/.DS_Store | Bin 6148 -> 6148 bytes
trash/
.html | 73 +++
trash/fretboard-diagram-creator-main.zip | Bin 0 -> 7884 bytes
trash/fretboard-diagram-creator-main/LICENSE | 21 +
.../fretboard-diagram-creator-main/README.md | 13 +
.../fretboard.css | 207 ++++++
.../fretboard.html | 42 ++
.../fretboard.js | 590 ++++++++++++++++++
8 files changed, 946 insertions(+)
create mode 100644 trash/.html
create mode 100644 trash/fretboard-diagram-creator-main.zip
create mode 100644 trash/fretboard-diagram-creator-main/LICENSE
create mode 100644 trash/fretboard-diagram-creator-main/README.md
create mode 100644 trash/fretboard-diagram-creator-main/fretboard.css
create mode 100644 trash/fretboard-diagram-creator-main/fretboard.html
create mode 100644 trash/fretboard-diagram-creator-main/fretboard.js
diff --git a/trash/.DS_Store b/trash/.DS_Store
index 8422ee56210ca69dbcf49787f69bbaf5920b5f40..5f6c119b05af490d94b56c16e8e94fdcf6749452 100644
GIT binary patch
literal 6148
zcmeHK!A`?440X044ehccCzK!9VSiAjB0iu$K)XVj)Hc%uoO|O4AH{*s;yJd^royQV
z0kS3MrH<`1FI!R*ky|~KOQJauO`(jVQ#79lkF)kwdv24&(7L+qHFQE9Aj{$;22i28@BDGN9+obavEPEV(gY
z4E#j~_CJzu~2NHo}wrt0|NsP3otNLFz7L4FqAOlGUQAwR5t}la4}>uV&;<$<~w_(AwT6l`9|oW`Hq)$
diff --git a/trash/.html b/trash/.html
new file mode 100644
index 0000000..fd094bf
--- /dev/null
+++ b/trash/.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+Guitar Fretboard Trainer
+
+
+
Start
+
Reset
+
+ Score
+
+
+
Play Again
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
?
+
+
+
Choose Note:
+
+ - A
+ - A#
+ - B
+ - C
+ - C#
+ - D
+ - D#
+ - E
+ - F
+ - F#
+ - G
+ - G#
+
+
+
+
+
\ No newline at end of file
diff --git a/trash/fretboard-diagram-creator-main.zip b/trash/fretboard-diagram-creator-main.zip
new file mode 100644
index 0000000000000000000000000000000000000000..39f0c3c2bc39bb9f07bba8040829f328bf2e7e8a
GIT binary patch
literal 7884
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
LF&DJ96y*LdS&CNc
literal 0
HcmV?d00001
diff --git a/trash/fretboard-diagram-creator-main/LICENSE b/trash/fretboard-diagram-creator-main/LICENSE
new file mode 100644
index 0000000..3e03783
--- /dev/null
+++ b/trash/fretboard-diagram-creator-main/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Tobias Kolditz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/trash/fretboard-diagram-creator-main/README.md b/trash/fretboard-diagram-creator-main/README.md
new file mode 100644
index 0000000..85069fd
--- /dev/null
+++ b/trash/fretboard-diagram-creator-main/README.md
@@ -0,0 +1,13 @@
+# Fretboard Diagram Creator
+
+A simple online tool with which you can create fretboard diagrams for the guitar.
+
+Demo: https://verzettelung.com/20/12/22/
+
+## Features
+
+* Freely select notes for scale or chord diagrams
+* Edit note labels (to indicate fingerings, intervals etc.)
+* Color notes
+* Select part of the fretboard
+* Save diagram as SVG
diff --git a/trash/fretboard-diagram-creator-main/fretboard.css b/trash/fretboard-diagram-creator-main/fretboard.css
new file mode 100644
index 0000000..c587581
--- /dev/null
+++ b/trash/fretboard-diagram-creator-main/fretboard.css
@@ -0,0 +1,207 @@
+/* Diagram */
+
+g.note.transparent:hover {
+ opacity: 1;
+}
+
+g.note {
+ fill: white;
+ stroke-width: 1px;
+ stroke: black;
+}
+
+g.note.selected>circle {
+ opacity: 1;
+ stroke-dasharray: 4, 4;
+ stroke-width: 3px;
+ stroke: black;
+}
+
+g.note.hidden {
+ opacity: 0;
+}
+
+g.note.transparent {
+ opacity: 0.3;
+}
+
+g.note.visible {
+ opacity: 1;
+ fill: white;
+}
+
+g.note.blue {
+ fill: steelblue;
+}
+
+g.note.blue>text {
+ stroke: white;
+ fill: white;
+}
+
+g.note.black {
+ fill: black;
+}
+
+g.note.black>text {
+ stroke: white;
+ fill: white;
+}
+
+g.note.red {
+ fill: indianred;
+}
+
+g.note.red>text {
+ stroke: white;
+ fill: white;
+}
+
+g.note.green {
+ fill: teal;
+}
+
+g.note.green>text {
+ stroke: white;
+ fill: white;
+}
+
+path.string {
+ stroke: black;
+ fill: none;
+}
+
+path.frets {
+ stroke: black;
+ stroke-width: 1px;
+ fill: none;
+}
+
+g.note>text {
+ stroke: black;
+ stroke-width: 0px;
+ fill: black;
+ text-anchor: middle;
+ dominant-baseline: middle;
+ font-size: 18px;
+}
+
+#editable-div {
+ text-align: center;
+ font-size: 18px;
+}
+
+#fretboard-diagram-creator {
+ padding-top: 10px;
+ /* border: 1px solid; */
+}
+
+div.menu {
+ text-align: center;
+ /* text-align: center; */
+}
+
+text.marker {
+ text-anchor: middle;
+ font-size: 16px;
+ fill: black;
+}
+
+
+/* Controls */
+
+/* Actions on nodes */
+
+#color-selector {
+ /* background-color: lightgray; */
+ margin: auto;
+ padding-bottom: 10px;
+ width: 280px;
+ display: flex;
+ justify-content: space-between;
+}
+
+button.color {
+ width: 44px;
+}
+
+button {
+ height: 25px;
+}
+
+button.color:hover {
+ transform: scale(1.1);
+}
+
+.color.blue {
+ background-color: steelblue;
+}
+
+.color.black {
+ background-color: black;
+}
+
+.color.green {
+ background-color: teal;
+}
+
+.color.red {
+ background-color: indianred;
+}
+
+.color.white {
+ background-color: white;
+}
+
+/* Global actions */
+
+#global-actions {
+ margin: auto;
+ width: 280px;
+ display: flex;
+ justify-content: space-between;
+}
+
+@supports (width: 100vw) {
+ figure.half-full {
+ max-width: 100vw;
+ width: 1200px;
+ position: relative;
+ left: 50%;
+ right: 50%;
+ margin-left: -600px;
+ margin-right: -600px;
+ }
+ @media only screen and (max-width: 1200px) {
+ figure.half-full {
+ max-width: 100vw;
+ width: 1000px;
+ position: relative;
+ left: 50%;
+ right: 50%;
+ margin-left: -500px;
+ margin-right: -500px;
+ overflow-y: auto;
+ }
+ }
+ @media only screen and (max-width: 1000px) {
+ figure.half-full {
+ max-width: 100vw;
+ width: 100%;
+ left: 0;
+ right: 0;
+ margin: 0;
+ overflow-y: auto;
+ }
+ }
+}
+
+.num-input {
+ font-variant-numeric: tabular-nums;
+ font-feature-settings: "tnum";
+}
+
+.error {
+ font-size: 18px;
+ text-anchor: middle;
+}
\ No newline at end of file
diff --git a/trash/fretboard-diagram-creator-main/fretboard.html b/trash/fretboard-diagram-creator-main/fretboard.html
new file mode 100644
index 0000000..dd79286
--- /dev/null
+++ b/trash/fretboard-diagram-creator-main/fretboard.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ Fretboard Diagram Creator
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trash/fretboard-diagram-creator-main/fretboard.js b/trash/fretboard-diagram-creator-main/fretboard.js
new file mode 100644
index 0000000..d9f1678
--- /dev/null
+++ b/trash/fretboard-diagram-creator-main/fretboard.js
@@ -0,0 +1,590 @@
+function setAttributes(elem, attrs) {
+ for (var idx in attrs) {
+ if ((idx === 'styles' || idx === 'style') && typeof attrs[idx] === 'object') {
+ const styles = [];
+ for (var prop in attrs[idx]) { styles.push(`${prop}: ${attrs[idx][prop]};`); }
+ elem.setAttribute('style', styles.join(' '));
+ } else if (idx === 'html') {
+ elem.innerHTML = attrs[idx];
+ } else {
+ elem.setAttribute(idx, attrs[idx]);
+ }
+ }
+}
+
+function generateClassValue(elem, classes) {
+ var classValues = elem.className.baseVal.split(" ");
+ if ('type' in classes) {
+ classValues[0] = classes.type;
+ }
+ if ('color' in classes) {
+ classValues[1] = classes.color;
+ }
+ if ('visibility' in classes) {
+ classValues[2] = classes.visibility;
+ }
+ return classValues.join(' ');
+}
+
+function createSvgElement(tag, attributes = null) {
+ const elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
+ if (typeof attributes === 'object') {
+ setAttributes(elem, attributes);
+ }
+ return elem;
+}
+
+
+class Fretboard {
+ constructor(opts) {
+ this.svg = opts.svg;
+ this.consts = {
+ offsetX: 40,
+ offsetY: 30,
+ stringIntervals: [24, 19, 15, 10, 5, 0],
+ markers: [3, 5, 7, 9, 12, 15, 17, 19, 21],
+ fretWidth: 70,
+ stringSpacing: 40,
+ minStringSize: 0.2,
+ circleRadius: 18,
+ notes: [['E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'C', 'C#', 'D', 'D#'],
+ ['E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb']],
+ sign: ['♯', '♭'],
+ };
+ this.consts.numStrings = this.consts.stringIntervals.length;
+ this.consts.fretHeight = (this.consts.numStrings - 1) * this.consts.stringSpacing;
+
+ this.state = {
+ selected: null,
+ visibility: 'transparent',
+ startFret: 0,
+ endFret: 12,
+ enharmonic: 0
+ };
+
+ // Set end fret according to viewport width
+ this.state.endFret = Math.min(Math.floor((window.innerWidth - 2 * this.consts.offsetX ) / this.consts.fretWidth), 12);
+ opts.endFret.value = this.state.endFret;
+
+ this.computeDependents();
+
+ this.data = {};
+
+ this.draw();
+ }
+
+ computeDependents() {
+ this.state.numFrets = this.state.endFret - this.state.startFret;
+ this.state.fretboardWidth = this.consts.fretWidth * this.state.numFrets;
+ }
+
+ toggleEnharmonic() {
+ const untoggledEnharmonic = this.state.enharmonic;
+ this.state.enharmonic = (untoggledEnharmonic + 1) % 2;
+ this.erase();
+ this.draw();
+ return this.consts.sign[untoggledEnharmonic];
+ }
+
+ setFretWindow(fretWindow) {
+ const start = 'start' in fretWindow ? fretWindow.start : this.state.startFret;
+ const end = 'end' in fretWindow ? fretWindow.end : this.state.endFret;
+ this.erase();
+ if (start < 0 || start > 22 || end < 1 || end > 22) {
+ this.drawError("Invalid fret value(s)!");
+ return;
+ }
+ if (end <= start) {
+ this.drawError("End fret must not be smaller than start fret!");
+ this.state.startFret = start;
+ this.state.endFret = end;
+ return;
+ }
+ if (end - start > 16) {
+ this.drawError("Maximal number of displayable frets is 16,
e.g., 1st to 16th or 4th to 19th!");
+ this.state.startFret = start;
+ this.state.endFret = end;
+ return;
+ }
+
+ this.state.startFret = start;
+ this.state.endFret = end;
+
+ this.computeDependents();
+ this.draw();
+ }
+
+ drawError(message) {
+ const text = createSvgElement('text', {
+ x: 400,
+ y: 140,
+ class: 'error',
+ });
+ text.innerHTML = message;
+ this.svg.appendChild(text);
+ setAttributes(this.svg, {
+ width: 800,
+ });
+ }
+
+ draw() {
+ this.drawFrets();
+ this.drawMarkers();
+ this.drawStrings();
+ this.drawNotes();
+ this.addEditableDiv();
+
+ // adjust diagram width to number of selected frets
+ setAttributes(this.svg, {
+ width: this.state.fretboardWidth + 2 * this.consts.offsetX,
+ })
+
+ this.svg.addEventListener('click', () => {
+ if (this.state.selected) {
+ this.updateNote(this.state.selected, {
+ visibility: 'visible',
+ });
+ this.state.selected = null;
+ }
+ });
+
+ document.addEventListener('keydown', (event) => {
+ if (!this.state.selected || !event.code) {
+ return;
+ }
+ const selected = this.state.selected;
+ switch (event.code) {
+ case 'Backspace':
+ case 'Delete':
+ this.deleteNote()
+ break;
+ case 'KeyB':
+ this.updateNote(selected, { color: "blue" });
+ break;
+ case 'KeyD':
+ this.updateNote(selected, { color: "black" });
+ break;
+ case 'KeyG':
+ this.updateNote(selected, { color: "green" });
+ break;
+ case "KeyW":
+ this.updateNote(selected, { color: "white" });
+ break;
+ case "KeyR":
+ this.updateNote(selected, { color: "red" });
+ break;
+ }
+ })
+ }
+
+ deleteNote() {
+ // reset text
+ const selected = this.state.selected;
+ const text = selected.lastChild;
+ if (text) {
+ text.innerHTML = text.getAttribute('data-note');
+ }
+ this.updateNote(selected, {
+ color: "white", visibility: this.state.visibility
+ });
+ this.state.selected = null;
+ }
+
+ updateColor(event) {
+ this.updateNote(this.state.selected, {
+ color: event.currentTarget.getAttribute("title")
+ });
+ }
+
+ drawFrets() {
+ var pathSegments = ["M " + this.consts.offsetX + " " + this.consts.offsetY];
+ for (let i = this.state.startFret; i < (this.state.endFret + 1); i++) {
+ let factor = (i - this.state.startFret) % 2 == 0 ? 1 : -1;
+ pathSegments.push("v " + (factor) * this.consts.fretHeight);
+ pathSegments.push("m " + this.consts.fretWidth + " " + 0);
+ }
+ const path = pathSegments.join(" ");
+
+
+ const frets = createSvgElement('path', {
+ 'class': 'frets',
+ 'd': path,
+ });
+ this.svg.appendChild(frets);
+ }
+
+ drawMarkers() {
+ const markers = createSvgElement('g', {
+ class: 'markers'
+ });
+ const filteredMarkers = this.consts.markers
+ .filter(i => i > this.state.startFret && i <= this.state.endFret);
+ for (let i of filteredMarkers) {
+ const marker = createSvgElement('text', {
+ class: 'marker',
+ x: this.consts.offsetX + (i - 1 - this.state.startFret) * this.consts.fretWidth + (this.consts.fretWidth / 2),
+ y: this.consts.offsetY + this.consts.fretHeight + this.consts.stringSpacing,
+ });
+ marker.innerHTML = i;
+ markers.appendChild(marker);
+ }
+ this.svg.appendChild(markers);
+ }
+
+ drawStrings() {
+ this.strings = createSvgElement('g', {
+ 'class': 'strings',
+ })
+ this.svg.appendChild(this.strings);
+ for (let i = 0; i < this.consts.numStrings; i++) {
+ let path = "M " + this.consts.offsetX + " " + (this.consts.offsetY + i * this.consts.stringSpacing) + " h " + this.state.fretboardWidth;
+ const string = createSvgElement('path', {
+ 'class': 'string',
+ 'd': path,
+ 'styles': {
+ 'stroke-width': this.consts.minStringSize * (i + 1),
+ }
+ });
+ this.strings.appendChild(string);
+ }
+ }
+
+ drawNote(noteId, x, y, noteName, isOpen) {
+ const note = createSvgElement('g', {
+ 'id': noteId,
+ 'transform': "translate(" + x + "," + y + ")",
+ 'data-x': x,
+ 'data-y': y,
+ });
+ this.notes.appendChild(note);
+ note.addEventListener("click", (event) => this.noteClickHandler(event));
+ note.addEventListener("dblclick", (event) => this.noteDoubleClickHandler(event));
+
+ const circle = createSvgElement('circle', {
+ 'r': this.consts.circleRadius,
+ });
+ if (isOpen) {
+ setAttributes(circle, {
+ // don't show circle around open notes
+ 'stroke': 'none',
+ })
+ }
+ note.appendChild(circle);
+
+ // compute name of note
+ const text = createSvgElement('text', {
+ 'data-note': noteName,
+ });
+ text.innerHTML = noteName;
+
+ note.appendChild(text);
+
+ const update = (noteId in this.data) ? this.data[noteId] : { type: 'note', color: 'white', visibility: this.state.visibility };
+ this.updateNote(note, update);
+ }
+
+ computeNoteName(fret, string) {
+ const interval = this.consts.stringIntervals[string] + fret + 1;
+ return this.consts.notes[this.state.enharmonic][interval % 12];
+ }
+
+ drawNotes() {
+ this.notes = createSvgElement('g', {
+ 'class': 'notes',
+ })
+ this.svg.appendChild(this.notes);
+
+ // open notes (fret: -1)
+ for (let j = 0; j < this.consts.numStrings; j++) {
+ const noteId = `o-s${j}`;
+ const x = this.consts.offsetX / 2;
+ const y = this.consts.offsetY + this.consts.stringSpacing * j;
+ const noteName = this.computeNoteName(-1, j);
+ this.drawNote(noteId, x, y, noteName, true);
+ }
+ // notes on fretboard
+ for (let i = this.state.startFret; i < this.state.endFret; i++) {
+ for (let j = 0; j < this.consts.numStrings; j++) {
+ const noteId = `f${i}-s${j}`;
+ const x = this.consts.offsetX + (this.consts.fretWidth / 2) + this.consts.fretWidth * (i - this.state.startFret);
+ const y = this.consts.offsetY + this.consts.stringSpacing * j;
+ const noteName = this.computeNoteName(i, j);
+ this.drawNote(noteId, x, y, noteName, false);
+ }
+ }
+ }
+
+ noteClickHandler(event) {
+ event.stopPropagation();
+ const note = event.currentTarget;
+ note.focus();
+ if (this.state.selected) {
+ this.updateNote(this.state.selected, {
+ visibility: 'visible',
+ });
+ }
+ this.updateNote(note, {
+ visibility: 'selected',
+ });
+ this.state.selected = note;
+
+ if (event.ctrlKey) {
+ this.editSelectedLabel();
+ }
+ }
+
+ noteDoubleClickHandler(event) {
+ event.stopPropagation();
+ const note = event.currentTarget;
+ if (this.state.selected) {
+ this.updateNote(this.state.selected, {
+ visibility: 'visible',
+ });
+ }
+ this.updateNote(note, {
+ visibility: 'selected',
+ });
+ this.state.selected = note;
+ this.editSelectedLabel();
+ }
+
+ editSelectedLabel() {
+ const selected = this.state.selected;
+ const x = selected.getAttribute('data-x');
+ const y = selected.getAttribute('data-y');
+ setAttributes(this.editableText, {
+ x: x - this.consts.circleRadius,
+ y: y - this.consts.circleRadius + 4,
+ height: 2 * this.consts.circleRadius,
+ width: 2 * this.consts.circleRadius,
+ class: 'visible',
+ styles: {
+ display: 'block',
+ }
+ });
+
+ const selectedText = this.state.selected.lastChild;
+ setAttributes(selectedText, {
+ styles: {
+ display: 'none',
+ }
+ });
+
+ this.editableText.children[0].innerHTML = selectedText.innerHTML;
+ this.editableText.children[0].focus();
+ // select all text in editable div
+ document.execCommand('selectAll', false, null);
+ }
+
+ addEditableDiv() {
+ this.editableText = createSvgElement('foreignObject', {
+ class: 'hidden',
+ });
+ this.editableText.addEventListener('click', (event) => {
+ event.stopPropagation();
+ });
+ const div = document.createElement('div');
+ div.setAttribute('contentEditable', 'true');
+ div.setAttribute('id', 'editable-div')
+ div.addEventListener('keydown', (event) => {
+ event.stopPropagation();
+ if (event.code === 'Enter') {
+ event.target.blur();
+ }
+ });
+ div.addEventListener('blur', (event) => {
+ if (!this.state.selected) {
+ return;
+ }
+ const selectedText = this.state.selected.lastChild;
+
+ var newText = this.editableText.children[0].innerText;
+ // don't allow empty labels
+ if (newText.trim()) {
+ this.updateNote(this.state.selected, {
+ noteText: newText,
+ });
+ }
+
+ this.editableText.children[0].innerHTML = '';
+ setAttributes(selectedText, {
+ styles: {
+ display: 'block',
+ }
+ });
+ setAttributes(this.editableText, {
+ styles: {
+ display: 'none',
+ }
+ });
+ })
+ this.editableText.appendChild(div);
+ this.svg.appendChild(this.editableText);
+ }
+
+ updateNote(elem, update) {
+ if (!(elem.id in this.data)) {
+ this.data[elem.id] = {};
+ }
+ const classValue = generateClassValue(elem, update);
+ elem.setAttribute('class', classValue);
+
+ if ('noteText' in update) {
+ elem.lastChild.innerHTML = update.noteText;
+ }
+
+ const noteData = this.data[elem.id];
+ for (let [key, value] of Object.entries(update)) {
+ noteData[key] = value;
+ }
+ }
+
+ toggleVisibility() {
+ this.state.visibility = this.state.visibility === 'hidden' ? 'transparent' : 'hidden';
+ for (let note of this.notes.children) {
+ if (note.className.baseVal.endsWith('visible') || note.className.baseVal.endsWith('selected')) {
+ continue;
+ }
+ this.updateNote(note, {
+ visibility: this.state.visibility,
+ })
+ }
+
+ for (let [_key, value] of Object.entries(this.data)) {
+ if (value['visibility'] === 'visible' || value['visibility'] === 'selected') {
+ continue;
+ }
+ value['visibility'] = this.state.visibility;
+ }
+ }
+
+ clearSelection() {
+ if (this.state.selected) {
+ this.updateNote(this.state.selected, {
+ visibility: 'visible',
+ });
+ this.state.selected = null;
+ }
+ }
+
+ erase() {
+ this.clearSelection();
+ this.svg.innerHTML = "";
+ }
+
+ reset() {
+ this.data = {};
+ for (let note of this.notes.children) {
+ // reset text
+ const text = note.lastChild;
+ if (text) {
+ text.innerHTML = text.getAttribute('data-note');
+ }
+ this.updateNote(note,
+ { type: "note", color: "white", visibility: this.state.visibility });
+ this.state.selected = null;
+ }
+ }
+}
+
+/* Main */
+
+/* Initialize diagram */
+
+const svg = document.getElementById('fretboard');
+const endFret = document.getElementById('end-fret');
+
+const fretboard = new Fretboard({
+ svg: svg,
+ endFret: endFret
+})
+
+/* Button for toggeling unselected notes */
+
+const togglebutton = document.getElementById('visibility');
+togglebutton.addEventListener('click', (event) => {
+ fretboard.toggleVisibility();
+});
+
+/* Save SVG button */
+
+var svgButton = document.getElementById('save-svg');
+const svgLink = document.getElementById('svg-link');
+
+svgButton.addEventListener('click', () => {
+ fretboard.clearSelection();
+ const svgCopy = inlineCSS(svg);
+ var svgData = svgCopy.outerHTML;
+ var svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
+ var svgUrl = URL.createObjectURL(svgBlob);
+ svgLink.href = svgUrl;
+ svgLink.click();
+});
+
+const PROPERTIES = ["fill", "stroke", "stroke-width", "text-anchor", "dominant-baseline"]
+
+function inlineCSS(svg) {
+ const svgElements = document.querySelectorAll("#fretboard *");
+ const clonedSVG = svg.cloneNode(deep = true);
+ const clonedElements = clonedSVG.querySelectorAll("*");
+ for (let i = 0; i < svgElements.length; i++) {
+ const computedStyle = getComputedStyle(svgElements[i]);
+ // remove invisible elements to reduce file size
+ const opacity = computedStyle.getPropertyValue('opacity');
+ if (opacity === '0') {
+ clonedElements[i].remove();
+ continue;
+ }
+ const styles = { opacity: opacity }
+ for (let attr of PROPERTIES) {
+ let value = computedStyle.getPropertyValue(attr);
+ if (value) {
+ styles[attr] = value;
+ }
+ }
+ setAttributes(clonedElements[i], {
+ 'styles': styles,
+ });
+ }
+ return clonedSVG;
+}
+
+/* Reset button */
+
+const resetButton = document.getElementById('reset');
+resetButton.addEventListener('click', (event) => {
+ const doReset = window.confirm("Do you really want to reset your diagram?");
+ if (doReset) {
+ fretboard.reset();
+ }
+});
+
+/* Fret window */
+
+const startFret = document.getElementById('start-fret');
+startFret.addEventListener('input', (event) => {
+ fretboard.setFretWindow({ start: event.target.value - 1 });
+});
+
+endFret.addEventListener('input', (event) => {
+ fretboard.setFretWindow({ end: parseInt(event.target.value) });
+});
+
+/* Color selector */
+
+const colorButtons = document.querySelectorAll("button.color");
+for (let button of colorButtons) {
+ button.addEventListener('click', (event) => {
+ fretboard.updateColor(event);
+ });
+}
+
+const deleteNoteButton = document.getElementById("delete-note");
+deleteNoteButton.addEventListener('click', () => fretboard.deleteNote());
+
+
+const enharmonicToggle = document.getElementById("enharmonic");
+enharmonicToggle.addEventListener('click', () => {
+ const sign = fretboard.toggleEnharmonic();
+ enharmonicToggle.innerHTML = sign;
+});
\ No newline at end of file