Resolve upstream runtime integration conflicts
This commit is contained in:
parent
698d21feb1
commit
4d17393cb1
@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user