Skip to content

Commit c5d912c

Browse files
authored
Load existing notation when editing cards (#248)
1 parent 4ae0ba0 commit c5d912c

6 files changed

Lines changed: 79 additions & 8 deletions

File tree

apps/react/src/components/notation/NotationPreviewList.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,27 @@ export const NotationPreviewList: React.FC<NotationPreviewListProps> = ({
3636
keySig,
3737
}) => {
3838
const base = previews.find((p) => p.key === keySig);
39+
const baseStackLength = base?.voices?.[0]?.stack?.length ?? 0;
3940
const others = previews.filter((p) => p.key !== keySig);
4041
const showText = !!previewTextCard && cardType === 'Text Prompt';
4142
const prompt = textPrompt ?? '';
4243
return (
43-
<div className="flex flex-col items-center gap-5">
44+
<div
45+
className="flex flex-col items-center gap-5"
46+
data-base-key={base?.key ?? ''}
47+
data-base-stack-length={baseStackLength}
48+
>
4449
<PreviewCard
4550
notation={<ScoreEditor />}
46-
total={base?.voices[0].stack.length ?? 0}
51+
total={baseStackLength}
4752
showText={showText}
4853
text={prompt}
4954
/>
5055
{others.map((p, i) => (
5156
<PreviewCard
5257
key={i}
5358
notation={<MusicNotation data={p} />}
54-
total={p.voices[0].stack.length}
59+
total={p.voices?.[0]?.stack?.length ?? 0}
5560
showText={showText}
5661
text={prompt}
5762
/>

apps/react/src/components/notation/ScoreEditor.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import React, {
1010
import { shallowEqual } from 'react-redux';
1111
import { MusicNotation } from '../MusicNotation';
1212
import { StepTimeController } from 'MemoryFlashCore/src/lib/stepTimeController';
13-
import { scoreToQuestion } from 'MemoryFlashCore/src/lib/scoreBuilder';
13+
import { questionToScore, scoreToQuestion } from 'MemoryFlashCore/src/lib/scoreBuilder';
1414
import { Duration, BaseDuration } from 'MemoryFlashCore/src/lib/measure';
1515
import { StaffEnum } from 'MemoryFlashCore/src/types/Cards';
1616
import { SheetNote, MultiSheetQuestion } from 'MemoryFlashCore/src/types/MultiSheetCard';
@@ -66,8 +66,20 @@ const isFull = (score: Score) => {
6666
});
6767
};
6868

69-
function useStepCtrl(keySig: string, resetSignal: number, notify: ScoreChangeHandler) {
70-
const ctrlRef = useRef(new StepTimeController());
69+
function useStepCtrl(
70+
keySig: string,
71+
resetSignal: number,
72+
notify: ScoreChangeHandler,
73+
initialQuestion?: MultiSheetQuestion,
74+
) {
75+
const controllerFactory = useMemo(
76+
() => () =>
77+
new StepTimeController(
78+
initialQuestion ? questionToScore(initialQuestion) : new Score(),
79+
),
80+
[initialQuestion],
81+
);
82+
const ctrlRef = useRef(controllerFactory());
7183
const [dur, setDurState] = useState<BaseDuration>('q');
7284
const [dotted, setDotted] = useState(false);
7385
const [extraDurations, setExtraDurations] = useState<Duration[]>([]);
@@ -80,7 +92,7 @@ function useStepCtrl(keySig: string, resetSignal: number, notify: ScoreChangeHan
8092
const midi = useAppSelector((s) => s.midi.notes.map((n) => n.number), shallowEqual);
8193
const prev = useRef<number[]>([]);
8294
const maxChord = useRef<number[]>([]);
83-
const [question, setQuestion] = useState(() => scoreToQuestion(new Score(), keySig));
95+
const [question, setQuestion] = useState(() => scoreToQuestion(ctrlRef.current.score, keySig));
8496

8597
const emit = useCallback(() => {
8698
const ctrl = ctrlRef.current;
@@ -144,6 +156,15 @@ function useStepCtrl(keySig: string, resetSignal: number, notify: ScoreChangeHan
144156
emitRef.current();
145157
}, [resetSignal]);
146158

159+
useEffect(() => {
160+
ctrlRef.current = controllerFactory();
161+
applyDurRef.current();
162+
ctrlRef.current.setStaff(staffRef.current);
163+
maxChord.current = [];
164+
prev.current = [];
165+
emitRef.current();
166+
}, [controllerFactory]);
167+
147168
useEffect(() => {
148169
applyDur();
149170
emit();
@@ -199,16 +220,18 @@ interface ProviderProps {
199220
keySig: string;
200221
resetSignal: number;
201222
onChange: ScoreChangeHandler;
223+
initialQuestion?: MultiSheetQuestion;
202224
children: React.ReactNode;
203225
}
204226

205227
export const ScoreEditorProvider: React.FC<ProviderProps> = ({
206228
keySig,
207229
resetSignal,
208230
onChange,
231+
initialQuestion,
209232
children,
210233
}) => {
211-
const value = useStepCtrl(keySig, resetSignal, onChange);
234+
const value = useStepCtrl(keySig, resetSignal, onChange, initialQuestion);
212235
return <ScoreEditorContext.Provider value={value}>{children}</ScoreEditorContext.Provider>;
213236
};
214237

apps/react/src/screens/NotationInputScreen.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const NotationInputScreen = () => {
3333
const { deckId } = useDeckIdPath();
3434
const { cardId } = useParams();
3535
const card = useAppSelector((state) => (cardId ? state.cards.entities[cardId] : undefined));
36+
const initialQuestion = card?.type === CardTypeEnum.MultiSheet ? card.question : undefined;
3637
const { isLoading: isUpdating, error: updateError } = useNetworkState('updateCard');
3738
const { isLoading: isAdding, error: addError } = useNetworkState('addCardsToDeck');
3839
useEffect(() => {
@@ -48,6 +49,8 @@ export const NotationInputScreen = () => {
4849
// If editing a Text Prompt card, default to previewing the text prompt
4950
preview: !!text,
5051
}));
52+
setQuestion(card.question);
53+
setComplete(true);
5154
}
5255
}, [card]);
5356
const previewsAll = questionsForAllMajorKeys(question, settings.lowest, settings.highest);
@@ -96,6 +99,7 @@ export const NotationInputScreen = () => {
9699
keySig={settings.keySig}
97100
resetSignal={resetCount}
98101
onChange={handleScoreChange}
102+
initialQuestion={initialQuestion}
99103
>
100104
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
101105
<div>
-181 Bytes
Loading
5.13 KB
Loading
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { test, expect } from './helpers';
2+
3+
// ensure edit mode loads the existing question into previews
4+
5+
test('NotationInputScreen preloads existing card notation', async ({ page }) => {
6+
await page.goto('/tests/notation-input-edit-test.html');
7+
await page.locator('#root').waitFor();
8+
9+
const cardPayload = {
10+
_id: 'card1',
11+
deckId: 'deck1',
12+
type: 'MultiSheet',
13+
question: {
14+
key: 'C',
15+
voices: [
16+
{
17+
staff: 'Treble',
18+
stack: [
19+
{ notes: [{ name: 'C', octave: 4 }], duration: 'q' },
20+
{ notes: [{ name: 'D', octave: 4 }], duration: 'q' },
21+
{ notes: [{ name: 'E', octave: 4 }], duration: 'q' },
22+
{ notes: [{ name: 'F', octave: 4 }], duration: 'q' },
23+
],
24+
},
25+
],
26+
presentationModes: [{ id: 'Sheet Music' }],
27+
},
28+
answer: { type: 'ExactMulti' },
29+
};
30+
31+
await page.evaluate((c) => {
32+
(window as any).store.dispatch({ type: 'cards/upsert', payload: [c] });
33+
}, cardPayload);
34+
35+
await expect(page.locator('[data-base-stack-length]')).toHaveAttribute(
36+
'data-base-stack-length',
37+
'4',
38+
);
39+
});

0 commit comments

Comments
 (0)