feat(project): optimize image imports and converter
- Optimize imported scene preview images (smart WebP/JPEG/PNG, preserve alpha, keep pixel size) - Update converter to re-encode existing image assets with same algorithm - Improve import/export progress overlay and reduce presentation slide stutter Made-with: Cursor
This commit is contained in:
@@ -35,6 +35,7 @@ export type SceneGraphSceneAudioSummary = {
|
||||
export type SceneGraphSceneCard = {
|
||||
title: string;
|
||||
previewAssetId: AssetId | null;
|
||||
previewThumbAssetId: AssetId | null;
|
||||
previewAssetType: 'image' | 'video' | null;
|
||||
previewVideoAutostart: boolean;
|
||||
previewRotationDeg: 0 | 90 | 180 | 270;
|
||||
@@ -69,6 +70,7 @@ type SceneCardData = {
|
||||
title: string;
|
||||
active: boolean;
|
||||
previewAssetId: AssetId | null;
|
||||
previewThumbAssetId: AssetId | null;
|
||||
previewAssetType: 'image' | 'video' | null;
|
||||
previewVideoAutostart: boolean;
|
||||
previewRotationDeg: 0 | 90 | 180 | 270;
|
||||
@@ -129,7 +131,8 @@ function IconVideoPreviewAutostart() {
|
||||
}
|
||||
|
||||
function SceneCardNode({ data }: NodeProps<SceneCardData>) {
|
||||
const url = useAssetUrl(data.previewAssetId);
|
||||
const thumbUrl = useAssetUrl(data.previewThumbAssetId);
|
||||
const previewUrl = useAssetUrl(data.previewAssetId);
|
||||
const cardClass = [styles.card, data.active ? styles.cardActive : ''].filter(Boolean).join(' ');
|
||||
const showCornerVideo = data.previewIsVideo;
|
||||
const showCornerAudio = data.hasSceneAudio;
|
||||
@@ -139,11 +142,11 @@ function SceneCardNode({ data }: NodeProps<SceneCardData>) {
|
||||
<div className={cardClass}>
|
||||
<div className={styles.previewShell}>
|
||||
{data.isStartScene ? <div className={styles.badgeStart}>НАЧАЛО</div> : null}
|
||||
{url && data.previewAssetType === 'image' ? (
|
||||
{thumbUrl ? (
|
||||
<div className={styles.previewFill}>
|
||||
{data.previewRotationDeg === 0 ? (
|
||||
<img
|
||||
src={url}
|
||||
src={thumbUrl}
|
||||
alt=""
|
||||
className={styles.imageCover}
|
||||
draggable={false}
|
||||
@@ -152,7 +155,7 @@ function SceneCardNode({ data }: NodeProps<SceneCardData>) {
|
||||
/>
|
||||
) : (
|
||||
<RotatedImage
|
||||
url={url}
|
||||
url={thumbUrl}
|
||||
rotationDeg={data.previewRotationDeg}
|
||||
mode="cover"
|
||||
loading="lazy"
|
||||
@@ -161,9 +164,31 @@ function SceneCardNode({ data }: NodeProps<SceneCardData>) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : url && data.previewAssetType === 'video' ? (
|
||||
) : previewUrl && data.previewAssetType === 'image' ? (
|
||||
<div className={styles.previewFill}>
|
||||
{data.previewRotationDeg === 0 ? (
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt=""
|
||||
className={styles.imageCover}
|
||||
draggable={false}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
) : (
|
||||
<RotatedImage
|
||||
url={previewUrl}
|
||||
rotationDeg={data.previewRotationDeg}
|
||||
mode="cover"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : previewUrl && data.previewAssetType === 'video' ? (
|
||||
<video
|
||||
src={url}
|
||||
src={previewUrl}
|
||||
muted
|
||||
playsInline
|
||||
preload="metadata"
|
||||
@@ -322,6 +347,7 @@ function SceneGraphCanvas({
|
||||
title: c?.title ?? '',
|
||||
active,
|
||||
previewAssetId: c?.previewAssetId ?? null,
|
||||
previewThumbAssetId: c?.previewThumbAssetId ?? null,
|
||||
previewAssetType: c?.previewAssetType ?? null,
|
||||
previewVideoAutostart: c?.previewVideoAutostart ?? false,
|
||||
previewRotationDeg: c?.previewRotationDeg ?? 0,
|
||||
|
||||
@@ -42,6 +42,7 @@ void test('buildNextSceneCardById: does not change refs when irrelevant fields c
|
||||
connections: [],
|
||||
layout: { x: 0, y: 0 },
|
||||
previewAssetId: null,
|
||||
previewThumbAssetId: null,
|
||||
previewAssetType: null,
|
||||
previewVideoAutostart: false,
|
||||
previewRotationDeg: 0,
|
||||
@@ -79,6 +80,7 @@ void test('buildNextSceneCardById: changes card when title changes', () => {
|
||||
connections: [],
|
||||
layout: { x: 0, y: 0 },
|
||||
previewAssetId: null,
|
||||
previewThumbAssetId: null,
|
||||
previewAssetType: null,
|
||||
previewVideoAutostart: false,
|
||||
previewRotationDeg: 0,
|
||||
|
||||
@@ -38,6 +38,7 @@ export function buildNextSceneCardById(
|
||||
if (
|
||||
prevCard?.title === s.title &&
|
||||
prevCard.previewAssetId === s.previewAssetId &&
|
||||
prevCard.previewThumbAssetId === s.previewThumbAssetId &&
|
||||
prevCard.previewAssetType === s.previewAssetType &&
|
||||
prevCard.previewVideoAutostart === s.previewVideoAutostart &&
|
||||
prevCard.previewRotationDeg === s.previewRotationDeg &&
|
||||
@@ -49,6 +50,7 @@ export function buildNextSceneCardById(
|
||||
nextMap[id] = {
|
||||
title: s.title,
|
||||
previewAssetId: s.previewAssetId,
|
||||
previewThumbAssetId: s.previewThumbAssetId,
|
||||
previewAssetType: s.previewAssetType,
|
||||
previewVideoAutostart: s.previewVideoAutostart,
|
||||
previewRotationDeg: s.previewRotationDeg,
|
||||
|
||||
Reference in New Issue
Block a user