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:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { computeTimeSec } from '../../main/video/videoPlaybackStore';
|
||||
import type { SessionState } from '../../shared/ipc/contracts';
|
||||
@@ -32,9 +32,39 @@ export function PresentationView({
|
||||
);
|
||||
const scene =
|
||||
session?.project && session.currentSceneId ? session.project.scenes[session.currentSceneId] : undefined;
|
||||
const previewUrl = useAssetUrl(scene?.previewAssetId ?? null);
|
||||
const originalUrl = useAssetUrl(scene?.previewAssetId ?? null);
|
||||
const thumbUrl = useAssetUrl(scene?.previewThumbAssetId ?? null);
|
||||
const [shownImageUrl, setShownImageUrl] = useState<string | null>(null);
|
||||
const rot = scene?.previewRotationDeg ?? 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (!scene) {
|
||||
setShownImageUrl(null);
|
||||
return;
|
||||
}
|
||||
if (scene.previewAssetType !== 'image') {
|
||||
setShownImageUrl(null);
|
||||
return;
|
||||
}
|
||||
// Show thumbnail instantly (if exists) to avoid stutter on slide switch, then swap to original when loaded.
|
||||
setShownImageUrl(thumbUrl ?? originalUrl);
|
||||
if (!thumbUrl || !originalUrl || thumbUrl === originalUrl) return;
|
||||
let cancelled = false;
|
||||
const img = new Image();
|
||||
img.decoding = 'async';
|
||||
img.onload = () => {
|
||||
if (cancelled) return;
|
||||
setShownImageUrl(originalUrl);
|
||||
};
|
||||
img.onerror = () => {
|
||||
// keep thumbnail
|
||||
};
|
||||
img.src = originalUrl;
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [originalUrl, scene, thumbUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = videoElRef.current;
|
||||
if (!el) return;
|
||||
@@ -57,7 +87,7 @@ export function PresentationView({
|
||||
}, [
|
||||
scene?.previewAssetId,
|
||||
scene?.previewAssetType,
|
||||
previewUrl,
|
||||
originalUrl,
|
||||
vp?.revision,
|
||||
vp?.targetAssetId,
|
||||
vp?.playing,
|
||||
@@ -66,20 +96,20 @@ export function PresentationView({
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
{previewUrl && scene?.previewAssetType === 'image' ? (
|
||||
{shownImageUrl && scene?.previewAssetType === 'image' ? (
|
||||
<div className={styles.fill}>
|
||||
<RotatedImage
|
||||
url={previewUrl}
|
||||
url={shownImageUrl}
|
||||
rotationDeg={rot}
|
||||
mode="contain"
|
||||
onContentRectChange={setContentRect}
|
||||
/>
|
||||
</div>
|
||||
) : previewUrl && scene?.previewAssetType === 'video' ? (
|
||||
) : originalUrl && scene?.previewAssetType === 'video' ? (
|
||||
<video
|
||||
ref={videoElRef}
|
||||
className={styles.video}
|
||||
src={previewUrl}
|
||||
src={originalUrl}
|
||||
muted
|
||||
playsInline
|
||||
loop={false}
|
||||
|
||||
Reference in New Issue
Block a user