mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: disable auto-save, add Save to Files button, accept all file types in library
- Disabled auto-save of processed files to library (worker no longer calls autoSaveToLibrary) - Added "Save to Files" button in the review panel for explicit saving - Files library upload area now accepts all file types, not just images - Removed "Drop images here" hardcoded text, replaced with i18n - Removed image-only file filter from library upload
This commit is contained in:
@@ -238,15 +238,8 @@ async function processToolJob(job: Job<ToolJobData>): Promise<ToolJobResult> {
|
||||
// Generate preview for non-browser-previewable formats
|
||||
const previewRef = await generatePreview(resultBuffer, resultContentType, jobId, inputBuffer);
|
||||
|
||||
// Auto-save to user file library
|
||||
const savedFileId = await autoSaveToLibrary({
|
||||
fileId: data.fileId,
|
||||
userId: data.userId,
|
||||
buffer: resultBuffer,
|
||||
outName,
|
||||
contentType: resultContentType,
|
||||
toolId: data.toolId,
|
||||
});
|
||||
// No auto-save -- users save to library explicitly via the UI
|
||||
const savedFileId: string | undefined = undefined;
|
||||
|
||||
const durationMs = Date.now() - startTime;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { AlertCircle, ArrowLeft, CheckCircle2, Download, FileText } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { AlertCircle, ArrowLeft, CheckCircle2, Download, FileText, FolderPlus } from "lucide-react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "@/contexts/i18n-context";
|
||||
import { formatHeaders } from "@/lib/api";
|
||||
import { formatFileSize, triggerDownload } from "@/lib/download";
|
||||
import { format } from "@/lib/format";
|
||||
|
||||
@@ -72,6 +73,28 @@ export function ReviewPanel({
|
||||
triggerDownload(downloadUrl, filename);
|
||||
};
|
||||
|
||||
const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle");
|
||||
|
||||
const handleSaveToFiles = useCallback(async () => {
|
||||
setSaveStatus("saving");
|
||||
try {
|
||||
const res = await fetch(downloadUrl);
|
||||
const blob = await res.blob();
|
||||
const formData = new FormData();
|
||||
formData.append("file", new File([blob], filename, { type: fileType }));
|
||||
const uploadRes = await fetch("/api/v1/files/upload", {
|
||||
method: "POST",
|
||||
headers: formatHeaders(),
|
||||
body: formData,
|
||||
});
|
||||
if (!uploadRes.ok) throw new Error("Upload failed");
|
||||
setSaveStatus("saved");
|
||||
} catch {
|
||||
setSaveStatus("error");
|
||||
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||
}
|
||||
}, [downloadUrl, filename, fileType]);
|
||||
|
||||
const hasBatchStats =
|
||||
totalCount != null && totalCount > 1 && successCount != null && failedCount != null;
|
||||
|
||||
@@ -165,6 +188,23 @@ export function ReviewPanel({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Save to Files */}
|
||||
{!isDataOutput && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveToFiles}
|
||||
disabled={saveStatus === "saving" || saveStatus === "saved"}
|
||||
className="w-full py-2 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted text-sm flex items-center justify-center gap-2 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
<FolderPlus className="h-4 w-4" />
|
||||
{saveStatus === "saving"
|
||||
? t.common.saving
|
||||
: saveStatus === "saved"
|
||||
? t.toolPage.savedToFiles
|
||||
: t.toolPage.saveToFiles}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Adjust settings */}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Upload } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { isImageFile } from "@/components/common/dropzone";
|
||||
import { useTranslation } from "@/contexts/i18n-context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFilesPageStore } from "@/stores/files-page-store";
|
||||
|
||||
export function FileUploadArea() {
|
||||
const { t } = useTranslation();
|
||||
const { uploadFiles, loading, uploadProgress } = useFilesPageStore();
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
@@ -20,14 +21,13 @@ export function FileUploadArea() {
|
||||
function handleDrop(e: React.DragEvent) {
|
||||
e.preventDefault();
|
||||
setDragging(false);
|
||||
const files = Array.from(e.dataTransfer.files).filter(isImageFile);
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
if (files.length > 0) uploadFiles(files);
|
||||
}
|
||||
|
||||
function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const files = Array.from(e.target.files ?? []);
|
||||
if (files.length > 0) uploadFiles(files);
|
||||
// Reset input so the same file can be re-selected
|
||||
e.target.value = "";
|
||||
}
|
||||
|
||||
@@ -57,19 +57,13 @@ export function FileUploadArea() {
|
||||
)}
|
||||
<div className="text-center">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
{loading ? "Uploading..." : "Drop images here"}
|
||||
{loading ? t.common.loading : t.files.dropFilesHere}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{loading ? "" : "or click to select files"}
|
||||
{loading ? "" : t.files.orClickToSelect}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
accept="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"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<input type="file" multiple className="hidden" onChange={handleInputChange} />
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user