Isolate RNG drafts and fix image editor lifecycle
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
This commit is contained in:
parent
7c0b72f914
commit
b470a34b2a
7 changed files with 297 additions and 50 deletions
|
|
@ -1,7 +1,8 @@
|
|||
// Rôle : édite une image utilisateur en cover rectangulaire de card toolbox.
|
||||
/* eslint-disable react-refresh/only-export-components -- expose aussi les helpers purs de calcul/export du crop. */
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Icon } from "../../components/Icon.jsx";
|
||||
import { useKeyedState } from "../../hooks/useKeyedState.js";
|
||||
import { lockBodyScroll } from "../../utils/bodyScrollLock.js";
|
||||
|
||||
const OUTPUT_WIDTH = 620;
|
||||
|
|
@ -64,46 +65,43 @@ export function ToolboxCoverEditor({ file, existingCover = "", onClose, onSave }
|
|||
const frameRef = useRef(null);
|
||||
const imageBitmapRef = useRef(null);
|
||||
const dragRef = useRef(null);
|
||||
const [source, setSource] = useState("");
|
||||
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
|
||||
const [preview, setPreview] = useKeyedState(file || existingCover, () => ({ source: "", width: 0, height: 0, error: "" }));
|
||||
const { source, error } = preview;
|
||||
const imageSize = preview;
|
||||
const [frame, setFrame] = useState({ width: 0, height: 0 });
|
||||
const [transform, setTransform] = useState({ x: 0, y: 0, scale: 1 });
|
||||
const [baseScale, setBaseScale] = useState(1);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const maxScale = useMemo(() => baseScale * 4, [baseScale]);
|
||||
const baseScale = frame.width && frame.height && imageSize.width && imageSize.height
|
||||
? Math.max(frame.width / imageSize.width, frame.height / imageSize.height) : 1;
|
||||
const transformKey = JSON.stringify([source, frame.width, frame.height, imageSize.width, imageSize.height]);
|
||||
const [transform, setTransform] = useKeyedState(transformKey, () => ({ x: 0, y: 0, scale: baseScale }));
|
||||
const maxScale = baseScale * 4;
|
||||
|
||||
useEffect(() => lockBodyScroll(), []);
|
||||
|
||||
useEffect(() => () => imageBitmapRef.current?.close?.(), []);
|
||||
|
||||
useEffect(() => {
|
||||
const nextSource = createPreviewSource(file, existingCover);
|
||||
setSource(nextSource);
|
||||
setError("");
|
||||
return () => {
|
||||
if (file && nextSource) URL.revokeObjectURL(nextSource);
|
||||
};
|
||||
}, [existingCover, file]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!source) return undefined;
|
||||
if (!nextSource) return undefined;
|
||||
let cancelled = false;
|
||||
decodePreviewImage(source, file)
|
||||
let decodedBitmap;
|
||||
decodePreviewImage(nextSource, file)
|
||||
.then((bitmap) => {
|
||||
if (cancelled) {
|
||||
bitmap.close?.();
|
||||
return;
|
||||
}
|
||||
imageBitmapRef.current?.close?.();
|
||||
decodedBitmap = bitmap;
|
||||
imageBitmapRef.current = bitmap;
|
||||
setImageSize({ width: bitmap.width, height: bitmap.height });
|
||||
setPreview({ source: nextSource, width: bitmap.width, height: bitmap.height, error: "" });
|
||||
})
|
||||
.catch(() => setError("Image illisible. Choisis un autre fichier."));
|
||||
.catch(() => {
|
||||
if (!cancelled) setPreview({ source: "", width: 0, height: 0, error: "Image illisible. Choisis un autre fichier." });
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
decodedBitmap?.close?.();
|
||||
if (imageBitmapRef.current === decodedBitmap) imageBitmapRef.current = null;
|
||||
if (file) URL.revokeObjectURL(nextSource);
|
||||
};
|
||||
}, [file, source]);
|
||||
}, [existingCover, file, setPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
const frameElement = frameRef.current;
|
||||
|
|
@ -118,13 +116,6 @@ export function ToolboxCoverEditor({ file, existingCover = "", onClose, onSave }
|
|||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!frame.width || !frame.height || !imageSize.width || !imageSize.height) return;
|
||||
const nextBaseScale = Math.max(frame.width / imageSize.width, frame.height / imageSize.height);
|
||||
setBaseScale(nextBaseScale);
|
||||
setTransform({ x: 0, y: 0, scale: nextBaseScale });
|
||||
}, [frame.height, frame.width, imageSize.height, imageSize.width]);
|
||||
|
||||
function clampTransform(nextTransform) {
|
||||
const displayWidth = imageSize.width * nextTransform.scale;
|
||||
const displayHeight = imageSize.height * nextTransform.scale;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue