mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: make beautify live preview reflect all settings in real time
Background images, device frames, custom shadows, and watermark text were not rendering in the right-pane preview. The preview now updates in real time for all settings: gradient/solid/image backgrounds, macOS/ Windows/Browser frame chrome, iPhone/MacBook/iPad frame indicators, custom shadow parameters, and watermark text overlay. Also fixes a React StrictMode effect-ordering race where the parent tool-page reset cleared preview state set by the child Settings component on initial mount.
This commit is contained in:
@@ -214,6 +214,7 @@ export function ToolPage() {
|
||||
const [previewTransform, setPreviewTransform] = useState<PreviewTransform | null>(null);
|
||||
const [previewFilter, setPreviewFilter] = useState<string>("");
|
||||
const [imageWrapperStyle, setImageWrapperStyle] = useState<React.CSSProperties | null>(null);
|
||||
const [imageWrapperChildren, setImageWrapperChildren] = useState<React.ReactNode>(null);
|
||||
const [bgPreview, setBgPreview] = useState<BgPreviewState | null>(null);
|
||||
|
||||
const [cropCrop, setCropCrop] = useState<Crop>({
|
||||
@@ -260,7 +261,6 @@ export function ToolPage() {
|
||||
|
||||
setPreviewTransform(null);
|
||||
setPreviewFilter("");
|
||||
setImageWrapperStyle(null);
|
||||
setBgPreview(null);
|
||||
setCropCrop({ unit: "%", x: 0, y: 0, width: 100, height: 100 });
|
||||
setCropAspect(undefined);
|
||||
@@ -272,6 +272,26 @@ export function ToolPage() {
|
||||
setMobileSettingsOpen(true);
|
||||
}, [toolId]);
|
||||
|
||||
const toolAccept = registryEntry?.accept;
|
||||
const toolAcceptExts = useMemo(
|
||||
() => toolAccept?.split(",").map((e) => e.trim().replace(/^\./, "").toLowerCase()),
|
||||
[toolAccept],
|
||||
);
|
||||
const toolFileFilter = useMemo(() => {
|
||||
if (!toolAcceptExts) return undefined;
|
||||
return (file: File) => {
|
||||
const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
|
||||
return toolAcceptExts.includes(ext);
|
||||
};
|
||||
}, [toolAcceptExts]);
|
||||
const toolAcceptDescription = useMemo(
|
||||
() =>
|
||||
toolAcceptExts
|
||||
? `${toolAcceptExts.map((e) => e.toUpperCase()).join(", ")} files only`
|
||||
: undefined,
|
||||
[toolAcceptExts],
|
||||
);
|
||||
|
||||
const handleFiles = useCallback(
|
||||
(newFiles: File[]) => {
|
||||
setEraserSliderInitPos(null);
|
||||
@@ -298,13 +318,15 @@ export function ToolPage() {
|
||||
input.type = "file";
|
||||
input.multiple = true;
|
||||
input.accept =
|
||||
toolAccept ??
|
||||
"image/*,.avif,.heic,.heif,.hif,.jxl,.dng,.cr2,.cr3,.nef,.nrw,.arw,.orf,.rw2,.raf,.pef,.3fr,.iiq,.srw,.x3f,.rwl,.gpr,.fff,.mrw,.mef,.kdc,.dcr,.erf,.ptx,.tga,.psd,.exr,.hdr,.svgz,.jp2,.j2k,.qoi,.eps,.dds,.cur,.apng,.dpx,.cin,.fits,.ppm,.pgm,.pbm,.pfm";
|
||||
input.onchange = (e) => {
|
||||
const newFiles = Array.from((e.target as HTMLInputElement).files || []);
|
||||
const selected = Array.from((e.target as HTMLInputElement).files || []);
|
||||
const newFiles = toolFileFilter ? selected.filter(toolFileFilter) : selected;
|
||||
if (newFiles.length > 0) addFiles(newFiles);
|
||||
};
|
||||
input.click();
|
||||
}, [addFiles]);
|
||||
}, [addFiles, toolAccept, toolFileFilter]);
|
||||
|
||||
const handleDownloadAll = useCallback(() => {
|
||||
if (!batchZipBlob) return;
|
||||
@@ -381,6 +403,7 @@ export function ToolPage() {
|
||||
onPreviewTransform: isLivePreview ? setPreviewTransform : undefined,
|
||||
onPreviewFilter: isLivePreview ? setPreviewFilter : undefined,
|
||||
onImageStyle: isLivePreview ? setImageWrapperStyle : undefined,
|
||||
onImageOverlay: isLivePreview ? (c: React.ReactNode) => setImageWrapperChildren(c) : undefined,
|
||||
onBgPreview: setBgPreview,
|
||||
cropProps:
|
||||
displayMode === "interactive-crop"
|
||||
@@ -442,9 +465,11 @@ export function ToolPage() {
|
||||
<Dropzone
|
||||
onFiles={handleFiles}
|
||||
onUrlImport={handleUrlImport}
|
||||
accept="image/*"
|
||||
accept={toolAccept ?? "image/*"}
|
||||
multiple
|
||||
currentFiles={files}
|
||||
fileFilter={toolFileFilter}
|
||||
acceptDescription={toolAcceptDescription}
|
||||
/>
|
||||
);
|
||||
const ResultsPanel = registryEntry.ResultsPanel;
|
||||
@@ -560,6 +585,7 @@ export function ToolPage() {
|
||||
filename={selectedFileName ?? files[0].name}
|
||||
fileSize={selectedFileSize ?? files[0].size}
|
||||
imageWrapperStyle={imageWrapperStyle}
|
||||
imageWrapperChildren={imageWrapperChildren}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -642,6 +668,7 @@ export function ToolPage() {
|
||||
: {})}
|
||||
{...(isLivePreview && previewFilter ? { cssFilter: previewFilter } : {})}
|
||||
{...(isLivePreview && imageWrapperStyle ? { imageWrapperStyle } : {})}
|
||||
{...(isLivePreview && imageWrapperChildren ? { imageWrapperChildren } : {})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -650,9 +677,11 @@ export function ToolPage() {
|
||||
<Dropzone
|
||||
onFiles={handleFiles}
|
||||
onUrlImport={handleUrlImport}
|
||||
accept="image/*"
|
||||
accept={toolAccept ?? "image/*"}
|
||||
multiple
|
||||
currentFiles={files}
|
||||
fileFilter={toolFileFilter}
|
||||
acceptDescription={toolAcceptDescription}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user