diff --git a/src/features/animation/__tests__/sequences-test.ts b/src/features/animation/__tests__/sequences-test.ts index 863ba33..684d5be 100644 --- a/src/features/animation/__tests__/sequences-test.ts +++ b/src/features/animation/__tests__/sequences-test.ts @@ -3,6 +3,7 @@ import { createInitialSequences, duplicateSequence, getSequenceSpring, + NEUTRAL_EXPRESSION_ID, normalizeSequencesForExpressions, parseSequences, remapSequencesAfterExpressionDelete, @@ -88,6 +89,21 @@ describe('editable avatar sequences', () => { expect(normalized.steps[0].expressionId).toBe(initialExpressions[0].id) }) + it('preserves the system neutral target during normalization', () => { + const sequence = createInitialSequences()[0] + const [normalized] = normalizeSequencesForExpressions( + [ + { + ...sequence, + steps: [{ ...sequence.steps[0], expressionId: NEUTRAL_EXPRESSION_ID }], + }, + ], + [] + ) + + expect(normalized.steps[0].expressionId).toBe(NEUTRAL_EXPRESSION_ID) + }) + it('maps transition styles and durations to distinct spring dynamics', () => { const smooth = getSequenceSpring('smooth', 900, 7) const snappy = getSequenceSpring('snappy', 250, 7) diff --git a/src/features/animation/components/SequenceWorkspace.tsx b/src/features/animation/components/SequenceWorkspace.tsx index 32c8cc1..6bf0f84 100644 --- a/src/features/animation/components/SequenceWorkspace.tsx +++ b/src/features/animation/components/SequenceWorkspace.tsx @@ -25,7 +25,8 @@ import { ControlSection, InspectorCard, PanelTitle } from '@/app/components/comm import { NumericField } from '@/app/components/controls' import { createSequenceStep, - findExpressionIndex, + NEUTRAL_EXPRESSION_ID, + resolveSequenceExpression, type AvatarSequence, type SequenceStep, } from '@/features/animation/sequences' @@ -37,6 +38,7 @@ import { import { type BodyNode } from '@/features/avatar/body' import { ExpressionPreview } from '@/features/avatar/components/ExpressionWorkspace' import { type Expression } from '@/features/avatar/geometry' +import { defaultExpression } from '@/features/avatar/presets' import { type SurfaceConfig } from '@/features/avatar/surfaces' export function SequenceWorkspace({ editing, @@ -268,9 +270,8 @@ export function SequenceWorkspace({ {editing.draft.steps.length ? (
{editing.draft.steps.map((step, position) => { - const expressionIndex = findExpressionIndex(expressions, step.expressionId) - const preset = expressions[expressionIndex] - if (!preset) return null + const resolved = resolveSequenceExpression(expressions, step.expressionId) + if (!resolved.expression) return null const card = ( ) @@ -318,16 +327,20 @@ export function SequenceWorkspace({ draggedStepId.current = null }} > - - - - onEditExpression(expressionIndex, preset)} - > - {t('Modifier')} - - - + {resolved.neutral ? ( + card + ) : ( + + + + onEditExpression(resolved.index, resolved.expression)} + > + {t('Modifier')} + + + + )} ) })} @@ -408,6 +421,28 @@ export function SequenceWorkspace({ subtitle="Sélectionne un preset pour l’ajouter à la fin de la timeline." />
+ {expressions.map((preset, index) => ( -
@@ -1225,7 +1215,8 @@ export function StudioInspector({ controller }: { controller: StudioController } {group.sequences.map(sequence => { const firstStep = sequence.steps[0] const firstExpression = firstStep - ? expressionById.get(firstStep.expressionId) + ? resolveSequenceExpression(expressions, firstStep.expressionId) + .expression : undefined const card = (