Browse Source

add and remove chords

mrkvon 1 year ago
parent
commit
cb1c595dbd
3 changed files with 100 additions and 11 deletions
  1. 38
    9
      src/App.tsx
  2. 39
    0
      src/ChordForm.tsx
  3. 23
    2
      src/chords.tsx

+ 38
- 9
src/App.tsx View File

@@ -1,27 +1,56 @@
1 1
 import React, { useState, ReactElement } from 'react';
2 2
 import FingerBoard from './FingerBoard';
3
+import ChordForm from './ChordForm';
3 4
 import { chord2fingering } from './chords';
4 5
 
6
+type ChordSelected = {
7
+  chord: string;
8
+  selected: boolean;
9
+};
10
+
5 11
 function App(): ReactElement {
6
-  const [chords] = useState(['dmin7', 'g7', 'cmaj7']);
7
-  const [selectedChord, setSelectedChord] = useState(0);
12
+  const [chords, setChords] = useState([] as ChordSelected[]);
13
+
14
+  const selectedChord = chords.find(({ selected }) => selected);
15
+  const fingers = selectedChord ? chord2fingering(selectedChord.chord) : [];
16
+
17
+  const handleRemoveChord = (i: number) => {
18
+    setChords(chords => [...chords.slice(0, i), ...chords.slice(i + 1)]);
19
+  };
20
+
21
+  const handleAddChord = (chord: string) => {
22
+    setChords(chords => [
23
+      ...chords.map(({ chord }) => ({ chord, selected: false })),
24
+      { chord, selected: true },
25
+    ]);
26
+  };
8 27
 
9
-  const fingers = chord2fingering(chords[selectedChord]);
28
+  const handleSelectChord = (i: number) => {
29
+    setChords(chords.map((chord, j) => ({ ...chord, selected: i === j })));
30
+  };
10 31
 
11 32
   return (
12 33
     <div style={{ display: 'flex' }}>
13 34
       <div>
14 35
         <div style={{ textAlign: 'center', fontSize: '2em' }}>
15
-          {chords[selectedChord]}
36
+          {chords.find(({ selected }) => selected)?.chord ?? null}
16 37
         </div>
17 38
         <FingerBoard fingers={fingers} />
18 39
       </div>
19 40
       <div>
20
-        {chords.map((chord, i) => (
21
-          <button key={i} onMouseOver={() => setSelectedChord(i)}>
22
-            {chord}
23
-          </button>
24
-        ))}
41
+        <ChordForm onSubmit={handleAddChord} />
42
+        <ul>
43
+          {chords.map(({ chord }, i) => (
44
+            <li key={i}>
45
+              <button onMouseOver={() => handleSelectChord(i)}>{chord}</button>
46
+              <button onClick={() => handleRemoveChord(i)}>x</button>
47
+              {/*i > 0 && <button onClick={() => handleUp(i)}>up</button>}
48
+              {i < chords.length - 1 && (
49
+                <button onClick={() => handleDown(i)}>down</button>
50
+              )*/}
51
+            </li>
52
+          ))}
53
+        </ul>
25 54
       </div>
26 55
     </div>
27 56
   );

+ 39
- 0
src/ChordForm.tsx View File

@@ -0,0 +1,39 @@
1
+import React, { useState, ReactElement, FormEvent, ChangeEvent } from 'react';
2
+import { chord2fingering } from './chords';
3
+
4
+const ChordForm = ({
5
+  onSubmit,
6
+}: {
7
+  onSubmit: (chord: string) => void;
8
+}): ReactElement => {
9
+  const [chord, setChord] = useState('');
10
+  const [error, setError] = useState('');
11
+
12
+  const handleSubmit = (e: FormEvent) => {
13
+    e.preventDefault();
14
+    setChord('');
15
+    onSubmit(chord);
16
+  };
17
+
18
+  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
19
+    const value = e.target.value;
20
+
21
+    setChord(value);
22
+    try {
23
+      chord2fingering(value);
24
+      setError('');
25
+    } catch (e) {
26
+      setError(e.message);
27
+    }
28
+  };
29
+
30
+  return (
31
+    <form onSubmit={handleSubmit}>
32
+      {chord && error && <div>{error}</div>}
33
+      <input value={chord} onChange={handleChange} />
34
+      <input type="submit" value="submit" disabled={!chord || !!error} />
35
+    </form>
36
+  );
37
+};
38
+
39
+export default ChordForm;

+ 23
- 2
src/chords.tsx View File

@@ -45,13 +45,30 @@ const chord2numbers = (chord: string): NumberChord => {
45 45
   const separator = ['#', 'b'].includes(chord[1]) ? 2 : 1;
46 46
   const note = chord.slice(0, separator);
47 47
   const chordType = chord.slice(separator);
48
+
49
+  // eslint-disable-next-line no-console
50
+  console.log(chordType, chordType in chords);
51
+
52
+  if (!(chordType in chords)) {
53
+    throw new Error("chord type doesn't exist");
54
+  }
55
+
48 56
   return {
49 57
     base: note2number(note),
50 58
     tones: chords[chordType],
51 59
   };
52 60
 };
53 61
 
54
-function note2number(note: string) {
62
+export const isChord = (chord: string): boolean => {
63
+  try {
64
+    chord2numbers(chord);
65
+    return true;
66
+  } catch {
67
+    return false;
68
+  }
69
+};
70
+
71
+function note2number(note: string): number {
55 72
   const notes: { [name: string]: number } = {
56 73
     c: 0,
57 74
     d: 2,
@@ -63,6 +80,9 @@ function note2number(note: string) {
63 80
     h: 11,
64 81
   };
65 82
 
83
+  if (!Object.keys(notes).includes(note[0]))
84
+    throw new Error('invalid note name (c, d, e, f, g, a, b, h)');
85
+
66 86
   if (note.length === 1) return notes[note];
67 87
 
68 88
   const accidental2number = (accidental: string): 1 | 11 => {
@@ -73,5 +93,6 @@ function note2number(note: string) {
73 93
 
74 94
   if (note.length === 2)
75 95
     return notes[note[0]] + (accidental2number(note[1]) % 12);
76
-  throw new Error('invalid note name (c, d, e, f, g, a, b, h)');
96
+
97
+  throw new Error('invalid note name');
77 98
 }