mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-11-03 22:03:54 +01:00
Move Setup panel above notation; host note filter inside it; fix key signature semantics
- Phrase mode: Setup panel now sits between the title row and the sheet music (was below the transport). It hosts the material options plus the shared TargetNoteControls (key/strings/fret window). - TargetNoteControls: in Phrase Mode the Key section binds to the generator key (tonic+mode always applied, no enable toggle) and the display-signature sync checkbox is hidden — phrase notation takes its signature from the score's key, so a display-signature select there would be a no-op. - Footer filter button hidden in Phrase Mode (lives in Setup now); still present in single-note modes. - Remove leftover '&& null' placeholder blocks.
This commit is contained in:
@@ -1040,9 +1040,9 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.phrase-setup {
|
.phrase-setup {
|
||||||
margin-top: 16px;
|
margin-bottom: 16px;
|
||||||
border-top: 1px solid color-mix(in srgb, var(--color-text-main) 12%, transparent);
|
border-bottom: 1px solid color-mix(in srgb, var(--color-text-main) 12%, transparent);
|
||||||
padding-top: 12px;
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phrase-setup summary {
|
.phrase-setup summary {
|
||||||
|
|||||||
@@ -30,17 +30,23 @@ export const Controls: React.FC<ControlsProps> = ({ currentPitch }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const inPhraseMode = settings.gameMode === 'phrase';
|
||||||
|
|
||||||
const pf = getPracticeFilter(settings);
|
const pf = getPracticeFilter(settings);
|
||||||
const activeFilterCount =
|
const activeFilterCount =
|
||||||
(pf.keyEnabled ? 1 : 0) +
|
(pf.keyEnabled ? 1 : 0) +
|
||||||
(pf.strings.length > 0 ? 1 : 0) +
|
(pf.strings.length > 0 ? 1 : 0) +
|
||||||
(pf.fretWindowEnabled ? 1 : 0);
|
(pf.fretWindowEnabled ? 1 : 0);
|
||||||
|
|
||||||
const inPhraseMode = settings.gameMode === 'phrase';
|
// Phrase Mode hosts the filter inside its Setup panel (above the
|
||||||
|
// notation) — no footer filter button there.
|
||||||
|
const showFilterButton = !inPhraseMode;
|
||||||
|
const shouldShowFilters = showFilterButton && filtersOpen;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="controls-container">
|
<div className="controls-container">
|
||||||
{/* Note filter: collapsed behind a button — visible only when needed */}
|
{/* Note filter: collapsed behind a button — visible only when needed */}
|
||||||
|
{showFilterButton && (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '8px' }}>
|
||||||
<button
|
<button
|
||||||
className={`hint-button ${filtersOpen ? 'active' : ''}`}
|
className={`hint-button ${filtersOpen ? 'active' : ''}`}
|
||||||
@@ -59,7 +65,8 @@ export const Controls: React.FC<ControlsProps> = ({ currentPitch }) => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{filtersOpen && <TargetNoteControls />}
|
)}
|
||||||
|
{shouldShowFilters && <TargetNoteControls />}
|
||||||
|
|
||||||
{/* Bottom Section: Tools Grid (2 Columns now) */}
|
{/* Bottom Section: Tools Grid (2 Columns now) */}
|
||||||
<div className="tools-grid" style={inPhraseMode ? { gridTemplateColumns: '1fr' } : undefined}>
|
<div className="tools-grid" style={inPhraseMode ? { gridTemplateColumns: '1fr' } : undefined}>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { getNoteDetails } from '../music/NoteUtils';
|
|||||||
import { EXERCISES, loadExercise } from '../exercises/library';
|
import { EXERCISES, loadExercise } from '../exercises/library';
|
||||||
import { parseAbc } from '../exercises/abcParser';
|
import { parseAbc } from '../exercises/abcParser';
|
||||||
import { PhraseSheetMusic } from './PhraseSheetMusic';
|
import { PhraseSheetMusic } from './PhraseSheetMusic';
|
||||||
|
import { TargetNoteControls } from './TargetNoteControls';
|
||||||
import { Fretboard } from './Fretboard';
|
import { Fretboard } from './Fretboard';
|
||||||
import { PianoKeys } from './PianoKeys';
|
import { PianoKeys } from './PianoKeys';
|
||||||
import { TUNINGS, getFretboardPositions } from '../music/Tunings';
|
import { TUNINGS, getFretboardPositions } from '../music/Tunings';
|
||||||
@@ -435,118 +436,8 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sheet-music-container">
|
{/* Setup panel — above the notation, holds material options
|
||||||
{scoreOk ? (
|
plus the shared note filter (key/strings/fret window) */}
|
||||||
<PhraseSheetMusic
|
|
||||||
score={trainer.activeScore}
|
|
||||||
currentIdx={trainer.currentIdx}
|
|
||||||
statuses={trainer.statuses}
|
|
||||||
clef={activeClef}
|
|
||||||
transpose={activeTranspose}
|
|
||||||
width={staffWidth}
|
|
||||||
theme={settings.theme}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="phrase-error">{materialError}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{trainer.error && <div className="error-message">{trainer.error}</div>}
|
|
||||||
{importError && <div className="error-message">{importError}</div>}
|
|
||||||
{rangeWarning && <div className="phrase-warning">{rangeWarning}</div>}
|
|
||||||
|
|
||||||
<div className="feedback-area">
|
|
||||||
{trainer.phase === 'countIn' && (
|
|
||||||
<span
|
|
||||||
className="phrase-countin"
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
minWidth: '20px',
|
|
||||||
padding: '1px 7px',
|
|
||||||
marginRight: '8px',
|
|
||||||
borderRadius: '10px',
|
|
||||||
fontSize: '11px',
|
|
||||||
opacity: 0.75,
|
|
||||||
background: 'color-mix(in srgb, var(--color-text-main) 10%, transparent)',
|
|
||||||
}}
|
|
||||||
aria-label={`Count-in: ${trainer.countInLeft} beats left`}
|
|
||||||
>{trainer.countInLeft}</span>
|
|
||||||
)}
|
|
||||||
<div className="instruction-text phrase-status">{statusText}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Transport — reuses the single-note button styles/behavior */}
|
|
||||||
<div className="action-row phrase-transport">
|
|
||||||
<button className="skip-button" onClick={startAction} title="Start / Pause (Space)">
|
|
||||||
{trainer.phase === 'playing' || trainer.phase === 'countIn' ? <Pause size={18} /> : <Play size={18} />}
|
|
||||||
{startLabel}
|
|
||||||
</button>
|
|
||||||
{!settings.zenMode && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
className="hint-button"
|
|
||||||
onClick={trainer.previewToggle}
|
|
||||||
title="Preview the phrase (P)"
|
|
||||||
>
|
|
||||||
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
|
|
||||||
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
|
|
||||||
</button>
|
|
||||||
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
|
|
||||||
<RotateCcw size={18} />
|
|
||||||
Retry
|
|
||||||
</button>
|
|
||||||
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && (
|
|
||||||
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
|
|
||||||
<SkipForward size={18} />
|
|
||||||
{phrase.material === 'melody' ? 'New' : 'Next'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{phrase.pace === 'step' && (
|
|
||||||
<button className="hint-button" onClick={trainer.skip} title="Skip this note (S)">
|
|
||||||
<SkipForward size={18} />
|
|
||||||
Skip
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* Same virtual-instrument / hint toggles as single-note mode */}
|
|
||||||
{trainer.phase === 'done' && settings.zenMode && (
|
|
||||||
<>
|
|
||||||
<button className="hint-button" onClick={trainer.retry} title="Retry (R)">
|
|
||||||
<RotateCcw size={18} />
|
|
||||||
Retry
|
|
||||||
</button>
|
|
||||||
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && (
|
|
||||||
<button className="hint-button" onClick={nextAction} title="New / Next (N)">
|
|
||||||
<SkipForward size={18} />
|
|
||||||
{phrase.material === 'melody' ? 'New' : 'Next'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!settings.zenMode && currentInstrumentDef.showTuning && (
|
|
||||||
<button
|
|
||||||
className={`hint-button ${settings.showFretboard ? 'active' : ''}`}
|
|
||||||
onClick={() => setSettings(s => ({ ...s, showFretboard: !s.showFretboard }))}
|
|
||||||
title={`Toggle Virtual ${currentInstrumentDef.displayName} (Keyboard Shortcut: V)`}
|
|
||||||
>
|
|
||||||
<Guitar size={18} />
|
|
||||||
{currentInstrumentDef.id === 'piano' ? 'Piano' : 'Guitar'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{!settings.zenMode && (
|
|
||||||
<button
|
|
||||||
className={`hint-button ${settings.showHint ? 'active' : ''}`}
|
|
||||||
onClick={() => setSettings(s => ({ ...s, showHint: !s.showHint }))}
|
|
||||||
title="Keyboard Shortcut: H"
|
|
||||||
>
|
|
||||||
<HelpCircle size={18} />
|
|
||||||
{settings.showHint ? "Hide Hint" : "Show Hint"}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Setup panel */}
|
|
||||||
{!settings.zenMode && (
|
{!settings.zenMode && (
|
||||||
<details className="phrase-setup">
|
<details className="phrase-setup">
|
||||||
<summary><ChevronDown size={16} /> Setup</summary>
|
<summary><ChevronDown size={16} /> Setup</summary>
|
||||||
@@ -562,8 +453,6 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{(phrase.material === 'melody' || phrase.material === 'scale' || phrase.material === 'arpeggio') && null}
|
|
||||||
|
|
||||||
{phrase.material === 'melody' && (
|
{phrase.material === 'melody' && (
|
||||||
<>
|
<>
|
||||||
<label>Bars
|
<label>Bars
|
||||||
@@ -778,8 +667,6 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{fretted && null}
|
|
||||||
|
|
||||||
{/* Pace lives in the metronome widget now:
|
{/* Pace lives in the metronome widget now:
|
||||||
'Sync to exercise' checked = tempo pace,
|
'Sync to exercise' checked = tempo pace,
|
||||||
unchecked = at-your-pace (step). */}
|
unchecked = at-your-pace (step). */}
|
||||||
@@ -815,9 +702,122 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
Matching checks pitch, not fingering — several fretboard positions produce the same pitch.
|
Matching checks pitch, not fingering — several fretboard positions produce the same pitch.
|
||||||
For repeated identical notes, briefly release between notes so each attack is detected.
|
For repeated identical notes, briefly release between notes so each attack is detected.
|
||||||
</p>
|
</p>
|
||||||
|
<TargetNoteControls />
|
||||||
</details>
|
</details>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="sheet-music-container">
|
||||||
|
{scoreOk ? (
|
||||||
|
<PhraseSheetMusic
|
||||||
|
score={trainer.activeScore}
|
||||||
|
currentIdx={trainer.currentIdx}
|
||||||
|
statuses={trainer.statuses}
|
||||||
|
clef={activeClef}
|
||||||
|
transpose={activeTranspose}
|
||||||
|
width={staffWidth}
|
||||||
|
theme={settings.theme}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="phrase-error">{materialError}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{trainer.error && <div className="error-message">{trainer.error}</div>}
|
||||||
|
{importError && <div className="error-message">{importError}</div>}
|
||||||
|
{rangeWarning && <div className="phrase-warning">{rangeWarning}</div>}
|
||||||
|
|
||||||
|
<div className="feedback-area">
|
||||||
|
{trainer.phase === 'countIn' && (
|
||||||
|
<span
|
||||||
|
className="phrase-countin"
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
minWidth: '20px',
|
||||||
|
padding: '1px 7px',
|
||||||
|
marginRight: '8px',
|
||||||
|
borderRadius: '10px',
|
||||||
|
fontSize: '11px',
|
||||||
|
opacity: 0.75,
|
||||||
|
background: 'color-mix(in srgb, var(--color-text-main) 10%, transparent)',
|
||||||
|
}}
|
||||||
|
aria-label={`Count-in: ${trainer.countInLeft} beats left`}
|
||||||
|
>{trainer.countInLeft}</span>
|
||||||
|
)}
|
||||||
|
<div className="instruction-text phrase-status">{statusText}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Transport — reuses the single-note button styles/behavior */}
|
||||||
|
<div className="action-row phrase-transport">
|
||||||
|
<button className="skip-button" onClick={startAction} title="Start / Pause (Space)">
|
||||||
|
{trainer.phase === 'playing' || trainer.phase === 'countIn' ? <Pause size={18} /> : <Play size={18} />}
|
||||||
|
{startLabel}
|
||||||
|
</button>
|
||||||
|
{!settings.zenMode && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="hint-button"
|
||||||
|
onClick={trainer.previewToggle}
|
||||||
|
title="Preview the phrase (P)"
|
||||||
|
>
|
||||||
|
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
|
||||||
|
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
|
||||||
|
</button>
|
||||||
|
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
|
||||||
|
<RotateCcw size={18} />
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && (
|
||||||
|
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
|
||||||
|
<SkipForward size={18} />
|
||||||
|
{phrase.material === 'melody' ? 'New' : 'Next'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{phrase.pace === 'step' && (
|
||||||
|
<button className="hint-button" onClick={trainer.skip} title="Skip this note (S)">
|
||||||
|
<SkipForward size={18} />
|
||||||
|
Skip
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* Same virtual-instrument / hint toggles as single-note mode */}
|
||||||
|
{trainer.phase === 'done' && settings.zenMode && (
|
||||||
|
<>
|
||||||
|
<button className="hint-button" onClick={trainer.retry} title="Retry (R)">
|
||||||
|
<RotateCcw size={18} />
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && (
|
||||||
|
<button className="hint-button" onClick={nextAction} title="New / Next (N)">
|
||||||
|
<SkipForward size={18} />
|
||||||
|
{phrase.material === 'melody' ? 'New' : 'Next'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!settings.zenMode && currentInstrumentDef.showTuning && (
|
||||||
|
<button
|
||||||
|
className={`hint-button ${settings.showFretboard ? 'active' : ''}`}
|
||||||
|
onClick={() => setSettings(s => ({ ...s, showFretboard: !s.showFretboard }))}
|
||||||
|
title={`Toggle Virtual ${currentInstrumentDef.displayName} (Keyboard Shortcut: V)`}
|
||||||
|
>
|
||||||
|
<Guitar size={18} />
|
||||||
|
{currentInstrumentDef.id === 'piano' ? 'Piano' : 'Guitar'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!settings.zenMode && (
|
||||||
|
<button
|
||||||
|
className={`hint-button ${settings.showHint ? 'active' : ''}`}
|
||||||
|
onClick={() => setSettings(s => ({ ...s, showHint: !s.showHint }))}
|
||||||
|
title="Keyboard Shortcut: H"
|
||||||
|
>
|
||||||
|
<HelpCircle size={18} />
|
||||||
|
{settings.showHint ? "Hide Hint" : "Show Hint"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Hint instruments */}
|
{/* Hint instruments */}
|
||||||
{(settings.showHint || settings.showFretboard) && currentInstrumentDef.showTuning && (
|
{(settings.showHint || settings.showFretboard) && currentInstrumentDef.showTuning && (
|
||||||
<div className="hint-card">
|
<div className="hint-card">
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
* - Strings: pick which strings contribute target notes (fretted instruments).
|
* - Strings: pick which strings contribute target notes (fretted instruments).
|
||||||
* - Fret window: restrict target notes to a fret range (fretted instruments).
|
* - Fret window: restrict target notes to a fret range (fretted instruments).
|
||||||
*
|
*
|
||||||
* In Phrase Mode the same controls bind to the shared practice filter
|
* In Phrase Mode the Key section binds to the generator key (tonic+mode are
|
||||||
* (mirrored into phrase settings), so state stays consistent across modes.
|
* always applied; no toggle), and the display-signature sync checkbox is
|
||||||
|
* hidden (phrase notation takes its signature from the score's key).
|
||||||
|
* Rendered inside the per-mode Setup panel above the notation.
|
||||||
*/
|
*/
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Key, Guitar, Music, SlidersHorizontal } from 'lucide-react';
|
import { Key, Guitar, Music, SlidersHorizontal } from 'lucide-react';
|
||||||
@@ -46,6 +48,8 @@ const MINOR_TONIC_TO_SIGNATURE: Record<string, string> = {
|
|||||||
export const TargetNoteControls: React.FC = () => {
|
export const TargetNoteControls: React.FC = () => {
|
||||||
const { settings, updateSettings } = useSettings();
|
const { settings, updateSettings } = useSettings();
|
||||||
const pf = getPracticeFilter(settings);
|
const pf = getPracticeFilter(settings);
|
||||||
|
// Phrase Mode: key tonic/mode are the generator key — always in effect.
|
||||||
|
const inPhraseMode = settings.gameMode === 'phrase';
|
||||||
|
|
||||||
const setFilter = (updates: Partial<PracticeFilter>) => {
|
const setFilter = (updates: Partial<PracticeFilter>) => {
|
||||||
updateSettings(s => {
|
updateSettings(s => {
|
||||||
@@ -81,7 +85,8 @@ export const TargetNoteControls: React.FC = () => {
|
|||||||
|
|
||||||
const allSelected = pf.strings.length === 0;
|
const allSelected = pf.strings.length === 0;
|
||||||
|
|
||||||
const anyActive = pf.keyEnabled || pf.fretWindowEnabled || pf.strings.length > 0;
|
const anyActive =
|
||||||
|
(!inPhraseMode && pf.keyEnabled) || pf.fretWindowEnabled || pf.strings.length > 0;
|
||||||
return (
|
return (
|
||||||
<div className="practice-filter-grid" style={{ display: 'flex', flexDirection: 'column', gap: '8px', border: '1px solid rgba(128,128,128,0.2)', padding: '12px', borderRadius: '8px', gridColumn: '1 / -1' }}>
|
<div className="practice-filter-grid" style={{ display: 'flex', flexDirection: 'column', gap: '8px', border: '1px solid rgba(128,128,128,0.2)', padding: '12px', borderRadius: '8px', gridColumn: '1 / -1' }}>
|
||||||
{/* Section header: unified with Tuner/Metronome style */}
|
{/* Section header: unified with Tuner/Metronome style */}
|
||||||
@@ -101,35 +106,6 @@ export const TargetNoteControls: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Display key signature (sheet notation) */}
|
|
||||||
<div className="control-group">
|
|
||||||
<label className="control-label">
|
|
||||||
<span>Key signature (display)</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={settings.keySignature}
|
|
||||||
onChange={e => updateSettings(s => ({ ...s, keySignature: e.target.value }))}
|
|
||||||
className="control-select"
|
|
||||||
aria-label="Display key signature"
|
|
||||||
>
|
|
||||||
<optgroup label="Major Keys">
|
|
||||||
<option value="C">C Major</option>
|
|
||||||
<option value="G">G Major</option>
|
|
||||||
<option value="D">D Major</option>
|
|
||||||
<option value="A">A Major</option>
|
|
||||||
<option value="E">E Major</option>
|
|
||||||
<option value="F">F Major</option>
|
|
||||||
<option value="Bb">Bb Major</option>
|
|
||||||
<option value="Eb">Eb Major</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup label="Minor Keys">
|
|
||||||
<option value="Am">A Minor</option>
|
|
||||||
<option value="Em">E Minor</option>
|
|
||||||
<option value="Dm">D Minor</option>
|
|
||||||
</optgroup>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Key filter */}
|
{/* Key filter */}
|
||||||
<div className="control-group">
|
<div className="control-group">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
@@ -137,6 +113,7 @@ export const TargetNoteControls: React.FC = () => {
|
|||||||
<Key size={18} />
|
<Key size={18} />
|
||||||
<span>Key</span>
|
<span>Key</span>
|
||||||
</label>
|
</label>
|
||||||
|
{!inPhraseMode && (
|
||||||
<button
|
<button
|
||||||
className={`switch-button ${pf.keyEnabled ? 'active' : ''}`}
|
className={`switch-button ${pf.keyEnabled ? 'active' : ''}`}
|
||||||
onClick={() => setFilter({ keyEnabled: !pf.keyEnabled })}
|
onClick={() => setFilter({ keyEnabled: !pf.keyEnabled })}
|
||||||
@@ -144,8 +121,9 @@ export const TargetNoteControls: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<div className="switch-thumb" />
|
<div className="switch-thumb" />
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{pf.keyEnabled && (
|
{(pf.keyEnabled || inPhraseMode) && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
||||||
<select
|
<select
|
||||||
@@ -169,6 +147,7 @@ export const TargetNoteControls: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
{!inPhraseMode && (
|
||||||
<label style={{ fontSize: '11px', display: 'flex', alignItems: 'center', gap: '6px', marginTop: '6px', opacity: 0.85 }}>
|
<label style={{ fontSize: '11px', display: 'flex', alignItems: 'center', gap: '6px', marginTop: '6px', opacity: 0.85 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -186,6 +165,7 @@ export const TargetNoteControls: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
Sync display key signature with target key
|
Sync display key signature with target key
|
||||||
</label>
|
</label>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user