Resolve upstream runtime integration conflicts

This commit is contained in:
Eric Cappannelli 2026-08-14 21:17:50 -04:00 committed by smontlouis
parent 698d21feb1
commit 4d17393cb1

View File

@ -16,8 +16,8 @@ import {
TriangleAlert, TriangleAlert,
Upload, Upload,
} from 'lucide-react' } from 'lucide-react'
import { AnimatePresence, motion } from 'motion/react' import { AnimatePresence, animate, motion, useMotionValue, useTransform } from 'motion/react'
import { type CSSProperties, useRef, useState } from 'react' import { type CSSProperties, useLayoutEffect, useRef, useState } from 'react'
import { import {
Avatar as RuntimeAvatar, Avatar as RuntimeAvatar,
@ -74,7 +74,7 @@ import {
import { defaultExpression } from '@/features/avatar/presets' import { defaultExpression } from '@/features/avatar/presets'
import { surfaceLabels, surfacePresets } from '@/features/avatar/surfaces' import { surfaceLabels, surfacePresets } from '@/features/avatar/surfaces'
import { type SnapshotBackground } from '@/features/export/snapshotExporter' import { type SnapshotBackground } from '@/features/export/snapshotExporter'
import { AvatarDrawer } from '@/features/studio/components/AvatarDrawer' import { AvatarPage } from '@/features/studio/components/AvatarDrawer'
import { StudioIdentity } from '@/features/studio/components/StudioIdentity' import { StudioIdentity } from '@/features/studio/components/StudioIdentity'
import type { StudioController } from '@/features/studio/useStudioController' import type { StudioController } from '@/features/studio/useStudioController'
@ -116,7 +116,6 @@ const highlightedCode = (source: string) =>
}) })
export function StudioInspector({ controller }: { controller: StudioController }) { export function StudioInspector({ controller }: { controller: StudioController }) {
const [avatarDrawerOpen, setAvatarDrawerOpen] = useState(false)
const [runtimeExampleOpen, setRuntimeExampleOpen] = useState(false) const [runtimeExampleOpen, setRuntimeExampleOpen] = useState(false)
const runtimeExampleRef = useRef<RuntimeAvatarController>(null) const runtimeExampleRef = useRef<RuntimeAvatarController>(null)
const { const {
@ -328,7 +327,7 @@ export function StudioInspector({ controller }: { controller: StudioController }
}) })
} }
return ( return (
<Drawer open={avatarDrawerOpen} onOpenChange={setAvatarDrawerOpen}> <Drawer>
<main <main
className={`inspector ${editing ? 'expression-workspace-active' : sequenceEditing ? 'sequence-workspace-active' : bodyEditing ? 'body-workspace' : 'studio-workspace'}${activeSequence && !editorPageOpen ? ' state-player-active' : ''}`} className={`inspector ${editing ? 'expression-workspace-active' : sequenceEditing ? 'sequence-workspace-active' : bodyEditing ? 'body-workspace' : 'studio-workspace'}${activeSequence && !editorPageOpen ? ' state-player-active' : ''}`}
> >
@ -448,18 +447,33 @@ export function StudioInspector({ controller }: { controller: StudioController }
> >
{!editorPageOpen && ( {!editorPageOpen && (
<header className="mode-page-header"> <header className="mode-page-header">
<p className="eyebrow">{activeAvatar.name}</p> <div>
<h1> <p className="eyebrow">{activeAvatar.name}</p>
{t( <h1>
mode === 'manual' {t(
? 'Pose' mode === 'manual'
: mode === 'expressions' ? 'Pose'
? 'Expressions' : mode === 'avatars'
: mode === 'states' ? 'Avatars'
? 'Animations' : mode === 'expressions'
: 'Exporter' ? 'Expressions'
)} : mode === 'states'
</h1> ? 'Animations'
: 'Exporter'
)}
</h1>
</div>
{mode === 'manual' && (
<Button
variant="outline"
size="icon"
type="button"
aria-label={t('Réinitialiser')}
onClick={() => transitionToExpression({ ...defaultExpression })}
>
<RotateCcw />
</Button>
)}
</header> </header>
)} )}
@ -1394,20 +1408,13 @@ export function StudioInspector({ controller }: { controller: StudioController }
aria-label={t('Afficher le maillage')} aria-label={t('Afficher le maillage')}
/> />
</div> </div>
<Button
className="reset"
variant="outline"
type="button"
onClick={() => transitionToExpression({ ...defaultExpression })}
>
{t('Réinitialiser')}
</Button>
</InspectorCard> </InspectorCard>
</ControlSection> </ControlSection>
</> </>
)} )}
</div> </div>
)} )}
{!editorPageOpen && mode === 'avatars' && <AvatarPage controller={controller} />}
{!sequenceEditing && !editing && bodyEditing && ( {!sequenceEditing && !editing && bodyEditing && (
<footer className="workspace-footer"> <footer className="workspace-footer">
@ -2143,79 +2150,44 @@ export function StudioInspector({ controller }: { controller: StudioController }
{activeSequence && !editorPageOpen && ( {activeSequence && !editorPageOpen && (
<motion.footer <motion.footer
className={`state-playback-footer${statePlayerExpanded ? ' is-expanded' : ''}`} className={`state-playback-footer${statePlayerExpanded ? ' is-expanded' : ''}`}
initial={{ opacity: 0, y: 18 }} style={{ y: playbackFooterY }}
animate={{ opacity: 1, y: 0 }}
> >
<Button <div className="state-playback-drag-handle-slot">
className="state-playback-expand" <motion.div style={{ y: playbackHandleCounterY }}>
variant="outline" <motion.button
size="icon-sm" className="state-playback-drag-handle"
type="button" style={{ y: playbackHandleY }}
aria-expanded={statePlayerExpanded} type="button"
aria-label={t( drag="y"
statePlayerExpanded dragMomentum={false}
? 'Masquer les détails de l’animation' aria-expanded={statePlayerExpanded}
: 'Afficher les détails de l’animation' aria-label={t(
)} statePlayerExpanded
onClick={() => setStatePlayerExpanded(expanded => !expanded)} ? 'Masquer les détails de l’animation'
> : 'Afficher les détails de l’animation'
{statePlayerExpanded ? <ChevronDown /> : <ChevronUp />} )}
</Button> onTap={() => snapPlaybackFooter(!statePlayerExpanded)}
{statePlayerExpanded && ( onDragStart={() => {
<motion.div playbackFooterDragOriginY.current = playbackFooterY.get()
className="state-playback-details" }}
initial={{ opacity: 0, y: 8 }} onDrag={(_, info) => {
animate={{ opacity: 1, y: 0 }} playbackFooterY.set(
> Math.min(
<div className="state-playback-details-header"> playbackFooterCollapsedOffset,
<div> Math.max(0, playbackFooterDragOriginY.current + info.offset.y)
<p className="eyebrow">{t('Détails de l’animation')}</p> )
<h2>{activeSequenceLabel}</h2> )
<p> }}
{activeSequence.builtIn onDragEnd={(_, info) => {
? t(activeSequence.description) playbackHandleY.set(0)
: activeSequence.description} const projectedY = playbackFooterY.get() + info.velocity.y * 0.16
</p> snapPlaybackFooter(projectedY < playbackFooterCollapsedOffset / 2)
</div> }}
<Badge variant="secondary"> >
{t('Mode de lecture')} · {t(activeSequence.playbackMode)} <span />
</Badge> </motion.button>
</div>
<div className="state-playback-detail-grid">
<div>
<span>{t('Expressions')}</span>
<strong>{activeSequence.steps.length}</strong>
<small>
{activeSequence.steps
.map(step => formatSeconds(step.holdMs, language))
.join(' · ')}
</small>
</div>
<div>
<span>{t('Premier clignement')}</span>
<strong>
{activeSequence.blink.enabled
? formatSeconds(activeSequence.blink.initialDelayMs, language)
: t('Désactivé')}
</strong>
<small>{t('après le lancement')}</small>
</div>
<div>
<span>{t('Intervalle du clignement')}</span>
<strong>
{formatSeconds(activeSequence.blink.minIntervalMs, language)}–
{formatSeconds(activeSequence.blink.maxIntervalMs, language)}
</strong>
<small>{t('tirage aléatoire')}</small>
</div>
<div>
<span>{t('Durée du clignement')}</span>
<strong>{activeSequence.blink.durationMs} ms</strong>
<small>{t('fermeture et ouverture')}</small>
</div>
</div>
</motion.div> </motion.div>
)} </div>
<div className="state-playback-bar"> <div className="state-playback-bar">
<div className="state-playback-timeline"> <div className="state-playback-timeline">
{activeSequence.steps.map((step, position) => { {activeSequence.steps.map((step, position) => {
@ -2266,6 +2238,62 @@ export function StudioInspector({ controller }: { controller: StudioController }
/> />
</div> </div>
</div> </div>
<motion.div
ref={playbackDetailsRef}
className="state-playback-details-shell"
style={{ opacity: playbackDetailsOpacity }}
aria-hidden={!statePlayerExpanded}
>
<div className="state-playback-details">
<div className="state-playback-details-header">
<div>
<p className="eyebrow">{t('Détails de l’animation')}</p>
<h2>{activeSequenceLabel}</h2>
<p>
{activeSequence.builtIn
? t(activeSequence.description)
: activeSequence.description}
</p>
</div>
<Badge variant="secondary">
{t('Mode de lecture')} · {t(activeSequence.playbackMode)}
</Badge>
</div>
<div className="state-playback-detail-grid">
<div>
<span>{t('Expressions')}</span>
<strong>{activeSequence.steps.length}</strong>
<small>
{activeSequence.steps
.map(step => formatSeconds(step.holdMs, language))
.join(' · ')}
</small>
</div>
<div>
<span>{t('Premier clignement')}</span>
<strong>
{activeSequence.blink.enabled
? formatSeconds(activeSequence.blink.initialDelayMs, language)
: t('Désactivé')}
</strong>
<small>{t('après le lancement')}</small>
</div>
<div>
<span>{t('Intervalle du clignement')}</span>
<strong>
{formatSeconds(activeSequence.blink.minIntervalMs, language)}–
{formatSeconds(activeSequence.blink.maxIntervalMs, language)}
</strong>
<small>{t('tirage aléatoire')}</small>
</div>
<div>
<span>{t('Durée du clignement')}</span>
<strong>{activeSequence.blink.durationMs} ms</strong>
<small>{t('fermeture et ouverture')}</small>
</div>
</div>
</div>
</motion.div>
</motion.footer> </motion.footer>
)} )}
{!editorPageOpen && ( {!editorPageOpen && (
@ -2274,9 +2302,9 @@ export function StudioInspector({ controller }: { controller: StudioController }
className="mobile-mode-tab mobile-avatar-tab" className="mobile-mode-tab mobile-avatar-tab"
variant="ghost" variant="ghost"
type="button" type="button"
aria-pressed={avatarDrawerOpen} aria-pressed={mode === 'avatars'}
aria-label={t('Choisir un avatar')} aria-label={t('Choisir un avatar')}
onClick={() => setAvatarDrawerOpen(true)} onClick={() => setMode('avatars')}
> >
<ExpressionPreview <ExpressionPreview
expression={expressions[0] ?? defaultExpression} expression={expressions[0] ?? defaultExpression}
@ -2311,7 +2339,6 @@ export function StudioInspector({ controller }: { controller: StudioController }
))} ))}
</nav> </nav>
)} )}
<AvatarDrawer controller={controller} onOpenChange={setAvatarDrawerOpen} />
</main> </main>
</Drawer> </Drawer>
) )