feat(scene): rotate video previews like images

Reuse previewRotationDeg for video scenes across editor, control,
presentation and overlays via ContainedVideo layout parity.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-08-11 12:17:57 +08:00
parent 1d94c95cc8
commit 7362a36fe5
10 changed files with 220 additions and 70 deletions
+45 -21
View File
@@ -29,6 +29,7 @@ import type {
SceneId,
} from '../../shared/types';
import { AppLogo } from '../shared/branding/AppLogo';
import { ContainedVideo } from '../shared/ContainedVideo';
import { getDndApi } from '../shared/dndApi';
import { RotatedImage } from '../shared/RotatedImage';
import { Button, Input } from '../shared/ui/controls';
@@ -2696,14 +2697,16 @@ function SceneInspector({
</div>
) : previewUrl && previewAssetType === 'video' ? (
<div className={styles.previewFill}>
<video
src={previewUrl}
<ContainedVideo
url={previewUrl}
rotationDeg={previewRotationDeg}
mode="cover"
muted
playsInline
autoPlay={previewVideoAutostart}
loop={previewVideoLoop}
preload="metadata"
className={styles.videoCover}
style={{ width: '100%', height: '100%' }}
/>
</div>
) : (
@@ -2744,7 +2747,7 @@ function SceneInspector({
</label>
</div>
) : null}
{previewAssetId && previewAssetType === 'image' ? (
{previewAssetId && (previewAssetType === 'image' || previewAssetType === 'video') ? (
<>
<div className={styles.spacer6} />
<Button
@@ -2989,23 +2992,44 @@ function SceneListCard({
</div>
) : previewUrl && scene.previewAssetType === 'video' ? (
<div className={styles.sceneThumbInner}>
<video
src={previewUrl}
muted
playsInline
preload="metadata"
draggable={false}
className={styles.sceneThumbVideo}
onLoadedData={(e) => {
const v = e.currentTarget;
try {
v.currentTime = 0;
v.pause();
} catch {
// ignore
}
}}
/>
{scene.previewRotationDeg === 0 ? (
<video
src={previewUrl}
muted
playsInline
preload="metadata"
draggable={false}
className={styles.sceneThumbVideo}
onLoadedData={(e) => {
const v = e.currentTarget;
try {
v.currentTime = 0;
v.pause();
} catch {
// ignore
}
}}
/>
) : (
<ContainedVideo
url={previewUrl}
rotationDeg={scene.previewRotationDeg}
mode="cover"
muted
playsInline
preload="metadata"
style={{ width: '100%', height: '100%' }}
onLoadedData={(e) => {
const v = e.currentTarget;
try {
v.currentTime = 0;
v.pause();
} catch {
// ignore
}
}}
/>
)}
</div>
) : (
<div className={styles.sceneThumbEmptyInner} aria-hidden />