Codex/ecommerce hot video responsive #7
@@ -998,6 +998,22 @@ function getImageFileFormat(file: File) {
|
||||
return file.name.split(".").pop()?.toUpperCase() ?? "";
|
||||
}
|
||||
|
||||
function getRemoteImageFormat(mimeType: string, imageUrl: string) {
|
||||
const mimeFormat = mimeType.split("/")[1]?.replace("jpeg", "jpg").toUpperCase();
|
||||
if (mimeFormat) return mimeFormat;
|
||||
return imageUrl.split("?")[0].split(".").pop()?.toUpperCase() ?? "IMAGE";
|
||||
}
|
||||
|
||||
function getRemoteImageName(imageUrl: string, fallback: string) {
|
||||
try {
|
||||
const parsed = new URL(imageUrl);
|
||||
const filename = decodeURIComponent(parsed.pathname.split("/").filter(Boolean).pop() || "");
|
||||
return filename || fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function readImageDimensions(src: string): Promise<{ width: number; height: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
@@ -1219,7 +1235,9 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
const cloneReferenceInputRef = useRef<HTMLInputElement>(null);
|
||||
const smartCutoutInputRef = useRef<HTMLInputElement>(null);
|
||||
const imageWorkbenchInputRef = useRef<HTMLInputElement>(null);
|
||||
const imageWorkbenchUrlInputRef = useRef<HTMLInputElement>(null);
|
||||
const watermarkInputRef = useRef<HTMLInputElement>(null);
|
||||
const watermarkUrlInputRef = useRef<HTMLInputElement>(null);
|
||||
const watermarkProcessTimeoutRef = useRef<number | null>(null);
|
||||
const smartCutoutTransitionTimeoutRef = useRef<number | null>(null);
|
||||
const smartCutoutPendingUrlsRef = useRef<string[]>([]);
|
||||
@@ -1793,6 +1811,49 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
setIsCloneSettingsCollapsed(false);
|
||||
};
|
||||
|
||||
const loadRemoteImageFromInput = async (input: HTMLInputElement | null, fallbackName: string) => {
|
||||
const rawValue = input?.value.trim() ?? "";
|
||||
if (!rawValue) {
|
||||
toast.info("请先粘贴图片 URL");
|
||||
return null;
|
||||
}
|
||||
|
||||
let imageUrl: string;
|
||||
try {
|
||||
const parsed = new URL(rawValue, window.location.href);
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||
throw new Error("仅支持 http 或 https 图片链接");
|
||||
}
|
||||
imageUrl = parsed.toString();
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "图片 URL 不正确");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(imageUrl);
|
||||
if (!response.ok) throw new Error(`图片读取失败(${response.status})`);
|
||||
const blob = await response.blob();
|
||||
if (!blob.type.startsWith("image/")) throw new Error("链接内容不是图片");
|
||||
const src = URL.createObjectURL(blob);
|
||||
try {
|
||||
await readImageDimensions(src);
|
||||
} catch {
|
||||
URL.revokeObjectURL(src);
|
||||
throw new Error("图片无法预览,请换一个链接");
|
||||
}
|
||||
if (input) input.value = "";
|
||||
return {
|
||||
src,
|
||||
name: getRemoteImageName(imageUrl, fallbackName),
|
||||
format: getRemoteImageFormat(blob.type, imageUrl),
|
||||
};
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "图片导入失败");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const closeWatermarkRemovalPage = () => {
|
||||
if (watermarkProcessTimeoutRef.current !== null) {
|
||||
window.clearTimeout(watermarkProcessTimeoutRef.current);
|
||||
@@ -1846,6 +1907,18 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
if (file) addWatermarkImage(file);
|
||||
};
|
||||
|
||||
const handleWatermarkUrlImport = async () => {
|
||||
const nextImage = await loadRemoteImageFromInput(watermarkUrlInputRef.current, "watermark-source");
|
||||
if (!nextImage) return;
|
||||
setWatermarkImage((current) => {
|
||||
if (current?.src) URL.revokeObjectURL(current.src);
|
||||
return nextImage;
|
||||
});
|
||||
setWatermarkStatus("idle");
|
||||
setActiveQuickTool("watermark");
|
||||
toast.success("图片已导入");
|
||||
};
|
||||
|
||||
const handleWatermarkGenerate = () => {
|
||||
if (!watermarkImage || watermarkStatus === "processing") return;
|
||||
if (watermarkProcessTimeoutRef.current !== null) window.clearTimeout(watermarkProcessTimeoutRef.current);
|
||||
@@ -1943,6 +2016,22 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
if (file) addImageWorkbenchImage(file);
|
||||
};
|
||||
|
||||
const handleImageWorkbenchUrlImport = async () => {
|
||||
const nextImage = await loadRemoteImageFromInput(imageWorkbenchUrlInputRef.current, "image-workbench-source");
|
||||
if (!nextImage) return;
|
||||
setImageWorkbenchImage((current) => {
|
||||
if (current?.src) URL.revokeObjectURL(current.src);
|
||||
return nextImage;
|
||||
});
|
||||
setImageWorkbenchStatus("idle");
|
||||
setImageWorkbenchMaskStrokes([]);
|
||||
setImageWorkbenchBrushCursor(null);
|
||||
clearImageWorkbenchMaskCanvas();
|
||||
imageWorkbenchActiveStrokeIdRef.current = null;
|
||||
setActiveQuickTool("image-edit");
|
||||
toast.success("图片已导入");
|
||||
};
|
||||
|
||||
const handleImageWorkbenchGenerate = () => {
|
||||
if (!imageWorkbenchImage) {
|
||||
toast.info("请先上传图片");
|
||||
@@ -5021,8 +5110,15 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
)}
|
||||
</div>
|
||||
<div className="ecom-image-workbench-url-row">
|
||||
<input placeholder="粘贴图片 URL" aria-label="粘贴图片 URL" />
|
||||
<button type="button" onClick={() => toast.info("请先使用本地上传")}>添加</button>
|
||||
<input
|
||||
ref={imageWorkbenchUrlInputRef}
|
||||
placeholder="粘贴图片 URL"
|
||||
aria-label="粘贴图片 URL"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void handleImageWorkbenchUrlImport();
|
||||
}}
|
||||
/>
|
||||
<button type="button" onClick={() => void handleImageWorkbenchUrlImport()}>添加</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -5225,8 +5321,15 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
)}
|
||||
</div>
|
||||
<div className="ecom-watermark-url-row">
|
||||
<input placeholder="粘贴图片 URL" aria-label="粘贴图片 URL" />
|
||||
<button type="button" onClick={() => toast.info("请先使用本地上传")}>导入</button>
|
||||
<input
|
||||
ref={watermarkUrlInputRef}
|
||||
placeholder="粘贴图片 URL"
|
||||
aria-label="粘贴图片 URL"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void handleWatermarkUrlImport();
|
||||
}}
|
||||
/>
|
||||
<button type="button" onClick={() => void handleWatermarkUrlImport()}>导入</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user