mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add replace-color tool and update tool page routing
- Add replace-color tool with pixel-level color replacement and tolerance - Register all 19 new Phase 3 tools in routes/tools/index.ts - Map all new tool IDs to settings components in tool-page.tsx - Add PDF, ZIP, ICO, JSON content types to download route - Install qrcode, jsqr, potrace, pdfkit dependencies
This commit is contained in:
@@ -12,6 +12,32 @@ 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";
|
||||
import * as icons from "lucide-react";
|
||||
|
||||
const COLOR_TOOL_IDS = new Set([
|
||||
@@ -21,7 +47,11 @@ const COLOR_TOOL_IDS = new Set([
|
||||
"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 <ResizeSettings />;
|
||||
if (toolId === "crop") return <CropSettings />;
|
||||
if (toolId === "rotate") return <RotateSettings />;
|
||||
@@ -29,6 +59,32 @@ function ToolSettingsPanel({ toolId }: { toolId: string }) {
|
||||
if (toolId === "compress") return <CompressSettings />;
|
||||
if (toolId === "strip-metadata") return <StripMetadataSettings />;
|
||||
if (COLOR_TOOL_IDS.has(toolId)) return <ColorSettings toolId={toolId} />;
|
||||
// Phase 3: Watermark & Overlay
|
||||
if (toolId === "watermark-text") return <WatermarkTextSettings />;
|
||||
if (toolId === "watermark-image") return <WatermarkImageSettings />;
|
||||
if (toolId === "text-overlay") return <TextOverlaySettings />;
|
||||
if (toolId === "compose") return <ComposeSettings />;
|
||||
// Phase 3: Utilities
|
||||
if (toolId === "info") return <InfoSettings />;
|
||||
if (toolId === "compare") return <CompareSettings />;
|
||||
if (toolId === "find-duplicates") return <FindDuplicatesSettings />;
|
||||
if (toolId === "color-palette") return <ColorPaletteSettings />;
|
||||
if (toolId === "qr-generate") return <QrGenerateSettings />;
|
||||
if (toolId === "barcode-read") return <BarcodeReadSettings />;
|
||||
// Phase 3: Layout & Composition
|
||||
if (toolId === "collage") return <CollageSettings />;
|
||||
if (toolId === "split") return <SplitSettings />;
|
||||
if (toolId === "border") return <BorderSettings />;
|
||||
// Phase 3: Format & Conversion
|
||||
if (toolId === "svg-to-raster") return <SvgToRasterSettings />;
|
||||
if (toolId === "vectorize") return <VectorizeSettings />;
|
||||
if (toolId === "gif-tools") return <GifToolsSettings />;
|
||||
// Phase 3: Optimization extras
|
||||
if (toolId === "bulk-rename") return <BulkRenameSettings />;
|
||||
if (toolId === "favicon") return <FaviconSettings />;
|
||||
if (toolId === "image-to-pdf") return <ImageToPdfSettings />;
|
||||
// Phase 3: Adjustments extra
|
||||
if (toolId === "replace-color") return <ReplaceColorSettings />;
|
||||
|
||||
return (
|
||||
<p className="text-xs text-muted-foreground italic">
|
||||
@@ -69,6 +125,7 @@ export function ToolPage() {
|
||||
)[tool.icon] || icons.FileImage;
|
||||
|
||||
const hasFile = files.length > 0;
|
||||
const isNoDropzone = NO_DROPZONE_TOOLS.has(tool.id);
|
||||
|
||||
return (
|
||||
<AppLayout showToolPanel={false}>
|
||||
@@ -84,37 +141,39 @@ export function ToolPage() {
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* File info */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">
|
||||
Files
|
||||
</h3>
|
||||
{hasFile ? (
|
||||
<div className="space-y-1">
|
||||
{files.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center justify-between text-xs text-foreground bg-muted rounded px-2 py-1"
|
||||
{/* File info - hidden for tools that don't need files */}
|
||||
{!isNoDropzone && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">
|
||||
Files
|
||||
</h3>
|
||||
{hasFile ? (
|
||||
<div className="space-y-1">
|
||||
{files.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center justify-between text-xs text-foreground bg-muted rounded px-2 py-1"
|
||||
>
|
||||
<span className="truncate">{f.name}</span>
|
||||
<span className="text-muted-foreground shrink-0 ml-2">
|
||||
{(f.size / 1024).toFixed(0)} KB
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={() => reset()}
|
||||
className="text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<span className="truncate">{f.name}</span>
|
||||
<span className="text-muted-foreground shrink-0 ml-2">
|
||||
{(f.size / 1024).toFixed(0)} KB
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={() => reset()}
|
||||
className="text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground italic">
|
||||
Drop or upload an image to get started
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground italic">
|
||||
Drop or upload an image to get started
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-border" />
|
||||
|
||||
@@ -129,7 +188,11 @@ export function ToolPage() {
|
||||
|
||||
{/* Dropzone / Preview */}
|
||||
<div className="flex-1 flex items-center justify-center p-6">
|
||||
{processedUrl && originalBlobUrl ? (
|
||||
{isNoDropzone ? (
|
||||
<div className="text-center text-muted-foreground">
|
||||
<p className="text-sm">Configure settings in the panel and generate.</p>
|
||||
</div>
|
||||
) : processedUrl && originalBlobUrl ? (
|
||||
<BeforeAfterSlider
|
||||
beforeSrc={originalBlobUrl}
|
||||
afterSrc={processedUrl}
|
||||
|
||||
Reference in New Issue
Block a user