import { useParams } from "react-router-dom"; import { useMemo, useCallback, useState } from "react"; import { TOOLS } from "@stirling-image/shared"; import { AppLayout } from "@/components/layout/app-layout"; import { Dropzone } from "@/components/common/dropzone"; import { ImageViewer } from "@/components/common/image-viewer"; import { BeforeAfterSlider } from "@/components/common/before-after-slider"; import { ReviewPanel } from "@/components/common/review-panel"; import { useFileStore } from "@/stores/file-store"; import { useMobile } from "@/hooks/use-mobile"; import { formatFileSize } from "@/lib/download"; import { ResizeSettings } from "@/components/tools/resize-settings"; import { CropSettings } from "@/components/tools/crop-settings"; import { RotateSettings } from "@/components/tools/rotate-settings"; import { ConvertSettings } from "@/components/tools/convert-settings"; import { CompressSettings } from "@/components/tools/compress-settings"; import { StripMetadataSettings } from "@/components/tools/strip-metadata-settings"; import { ColorSettings } from "@/components/tools/color-settings"; // Phase 3: Watermark & Overlay import { WatermarkTextSettings } from "@/components/tools/watermark-text-settings"; import { WatermarkImageSettings } from "@/components/tools/watermark-image-settings"; import { TextOverlaySettings } from "@/components/tools/text-overlay-settings"; import { ComposeSettings } from "@/components/tools/compose-settings"; // Phase 3: Utilities import { InfoSettings } from "@/components/tools/info-settings"; import { CompareSettings } from "@/components/tools/compare-settings"; import { FindDuplicatesSettings } from "@/components/tools/find-duplicates-settings"; import { ColorPaletteSettings } from "@/components/tools/color-palette-settings"; import { QrGenerateSettings } from "@/components/tools/qr-generate-settings"; import { BarcodeReadSettings } from "@/components/tools/barcode-read-settings"; // Phase 3: Layout & Composition import { CollageSettings } from "@/components/tools/collage-settings"; import { SplitSettings } from "@/components/tools/split-settings"; import { BorderSettings } from "@/components/tools/border-settings"; // Phase 3: Format & Conversion import { SvgToRasterSettings } from "@/components/tools/svg-to-raster-settings"; import { VectorizeSettings } from "@/components/tools/vectorize-settings"; import { GifToolsSettings } from "@/components/tools/gif-tools-settings"; // Phase 3: Optimization extras import { BulkRenameSettings } from "@/components/tools/bulk-rename-settings"; import { FaviconSettings } from "@/components/tools/favicon-settings"; import { ImageToPdfSettings } from "@/components/tools/image-to-pdf-settings"; // Phase 3: Adjustments extra import { ReplaceColorSettings } from "@/components/tools/replace-color-settings"; // Phase 4: AI Tools import { RemoveBgSettings } from "@/components/tools/remove-bg-settings"; import { UpscaleSettings } from "@/components/tools/upscale-settings"; import { OcrSettings } from "@/components/tools/ocr-settings"; import { BlurFacesSettings } from "@/components/tools/blur-faces-settings"; import { EraseObjectSettings } from "@/components/tools/erase-object-settings"; import { SmartCropSettings } from "@/components/tools/smart-crop-settings"; import * as icons from "lucide-react"; import { CheckCircle2 } from "lucide-react"; const COLOR_TOOL_IDS = new Set([ "brightness-contrast", "saturation", "color-channels", "color-effects", ]); // Tools that don't need a file dropzone (they generate content or have custom UI) const NO_DROPZONE_TOOLS = new Set(["qr-generate"]); function ToolSettingsPanel({ toolId }: { toolId: string }) { // Phase 2: Core tools if (toolId === "resize") return ; if (toolId === "crop") return ; if (toolId === "rotate") return ; if (toolId === "convert") return ; if (toolId === "compress") return ; if (toolId === "strip-metadata") return ; if (COLOR_TOOL_IDS.has(toolId)) return ; // Phase 3: Watermark & Overlay if (toolId === "watermark-text") return ; if (toolId === "watermark-image") return ; if (toolId === "text-overlay") return ; if (toolId === "compose") return ; // Phase 3: Utilities if (toolId === "info") return ; if (toolId === "compare") return ; if (toolId === "find-duplicates") return ; if (toolId === "color-palette") return ; if (toolId === "qr-generate") return ; if (toolId === "barcode-read") return ; // Phase 3: Layout & Composition if (toolId === "collage") return ; if (toolId === "split") return ; if (toolId === "border") return ; // Phase 3: Format & Conversion if (toolId === "svg-to-raster") return ; if (toolId === "vectorize") return ; if (toolId === "gif-tools") return ; // Phase 3: Optimization extras if (toolId === "bulk-rename") return ; if (toolId === "favicon") return ; if (toolId === "image-to-pdf") return ; // Phase 3: Adjustments extra if (toolId === "replace-color") return ; // Phase 4: AI Tools if (toolId === "remove-background") return ; if (toolId === "upscale") return ; if (toolId === "ocr") return ; if (toolId === "blur-faces") return ; if (toolId === "erase-object") return ; if (toolId === "smart-crop") return ; return (

Settings for this tool are coming soon.

); } /** File selection indicator shown in left panel */ function FileSelectionInfo({ files, selectedFileName, selectedFileSize, onClear, }: { files: File[]; selectedFileName: string | null; selectedFileSize: number | null; onClear: () => void; }) { if (files.length === 0) { return (

Drop or upload an image to get started

); } return (
Selected: {selectedFileName ?? files[0].name} {formatFileSize(selectedFileSize ?? files[0].size)}
{files.length > 1 && (

+{files.length - 1} more file{files.length > 2 ? "s" : ""}

)}
); } export function ToolPage() { const { toolId } = useParams<{ toolId: string }>(); const tool = useMemo(() => TOOLS.find((t) => t.id === toolId), [toolId]); const { files, setFiles, reset, processedUrl, originalBlobUrl, originalSize, processedSize, selectedFileName, selectedFileSize, undoProcessing, } = useFileStore(); const isMobile = useMobile(); const [mobileSettingsOpen, setMobileSettingsOpen] = useState(true); const handleFiles = useCallback( (newFiles: File[]) => { reset(); setFiles(newFiles); }, [setFiles, reset], ); const handleUndo = useCallback(() => { undoProcessing(); }, [undoProcessing]); if (!tool) { return (
Tool not found
); } const IconComponent = ( icons as unknown as Record< string, React.ComponentType<{ className?: string }> > )[tool.icon] || icons.FileImage; const hasFile = files.length > 0; const hasProcessed = !!processedUrl; const isNoDropzone = NO_DROPZONE_TOOLS.has(tool.id); // Derive processed file info from context const processedFileName = selectedFileName ? `processed-${selectedFileName}` : "processed-image"; const processedFileType = selectedFileName ? selectedFileName.split(".").pop()?.toUpperCase() || "IMAGE" : "IMAGE"; // Mobile layout: settings above dropzone (stacked) if (isMobile) { return (
{/* Tool header */}

{tool.name}

{/* Collapsible settings */} {mobileSettingsOpen && (
{/* File info */} {!isNoDropzone && (

Files

)}

Settings

{/* Review panel (mobile) */} {hasProcessed && processedSize != null && ( )}
)} {/* Main area: Dropzone / Image Viewer / Before-After */}
{isNoDropzone ? (

Configure settings and generate.

) : hasProcessed && originalBlobUrl ? ( ) : hasFile && originalBlobUrl ? ( ) : ( )}
); } // Desktop layout: side-by-side return (
{/* Tool Settings Panel */}

{tool.name}

{/* File info - hidden for tools that don't need files */} {!isNoDropzone && (

Files

)}
{/* Tool-specific settings */}

Settings

{/* Review panel (desktop - below settings) */} {hasProcessed && processedSize != null && ( )}
{/* Main area: Dropzone / Image Viewer / Before-After */}
{isNoDropzone ? (

Configure settings in the panel and generate.

) : hasProcessed && originalBlobUrl ? ( ) : hasFile && originalBlobUrl ? ( ) : ( )}
); }