Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
447 changes: 0 additions & 447 deletions src/components/FftEditor.tsx

This file was deleted.

76 changes: 76 additions & 0 deletions src/components/edit-window/components/SidebarAdjustments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";

interface SidebarAdjustmentsProps {
brightness: number;
setBrightness: (val: number) => void;
contrast: number;
setContrast: (val: number) => void;
disabled: boolean;
}

export function SidebarAdjustments({
brightness,
setBrightness,
contrast,
setContrast,
disabled,
}: SidebarAdjustmentsProps) {
const { t } = useTranslation(["tooltip", "keywords"]);

return (
<div className="flex flex-col gap-4">
<h3 className="text-sm font-semibold text-muted-foreground">
{t("Adjustments", { ns: "keywords" })}
</h3>
<div className="flex flex-col items-center space-y-2">
<Label
htmlFor="brightness"
className="text-sm font-medium self-start"
>
{t("Brightness", { ns: "tooltip" })}
</Label>
<div className="flex items-center gap-3 w-full">
<Input
id="brightness"
type="range"
min="0"
max="200"
value={brightness}
onChange={e => setBrightness(Number(e.target.value))}
className="flex-1 h-2 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary"
disabled={disabled}
/>
<span className="text-sm text-muted-foreground min-w-[3rem] text-right">
{brightness}%
</span>
</div>
</div>
<div className="flex flex-col items-center space-y-2">
<Label
htmlFor="contrast"
className="text-sm font-medium self-start"
>
{t("Contrast", { ns: "tooltip" })}
</Label>
<div className="flex items-center gap-3 w-full">
<Input
id="contrast"
type="range"
min="0"
max="200"
value={contrast}
onChange={e => setContrast(Number(e.target.value))}
className="flex-1 h-2 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary"
disabled={disabled}
/>
<span className="text-sm text-muted-foreground min-w-[3rem] text-right">
{contrast}%
</span>
</div>
</div>
</div>
);
}
182 changes: 182 additions & 0 deletions src/components/edit-window/components/SidebarFFT.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils/shadcn";
import { Edit3, Hand, Waves, Trash2 } from "lucide-react";
import { ICON } from "@/lib/utils/const";
import { UseFftWorkspaceReturn } from "../hooks/useFftWorkspace";

interface SidebarFFTProps {
isFftActive: boolean;
setIsFftActive: (active: boolean) => void;
fft: UseFftWorkspaceReturn;
}

export function SidebarFFT({
isFftActive,
setIsFftActive,
fft,
}: SidebarFFTProps) {
const { t } = useTranslation(["tooltip", "keywords"]);

return (
<div className="flex flex-col gap-2">
<h3 className="text-sm font-semibold text-muted-foreground">FFT</h3>
<div className="space-y-3 w-full">
<Button
onClick={() => setIsFftActive(!isFftActive)}
variant={isFftActive ? "destructive" : "default"}
className="flex items-center justify-center gap-2 w-full"
disabled={
fft.status === "loading" || fft.status === "processing"
}
>
<Waves size={ICON.SIZE} />
{t("FFT Filter", { ns: "tooltip" })}
</Button>

{isFftActive && fft.status === "loading" && (
<span className="text-xs animate-pulse text-blue-400">
{t("Loading...", { ns: "keywords" })}
</span>
)}
{isFftActive && fft.status === "processing" && (
<span className="text-xs animate-pulse text-primary">
{t("Processing...", { ns: "keywords" })}
</span>
)}
{isFftActive && fft.status === "ready" && (
<>
<div className="space-y-4">
<div className="space-y-2">
<div className="flex bg-secondary/50 p-1 rounded-lg gap-1">
<button
type="button"
onClick={() =>
fft.setInteractionMode("draw")
}
className={cn(
"flex-1 flex items-center justify-center gap-2 py-1.5 px-2 rounded-md transition-all text-xs font-medium",
fft.interactionMode === "draw"
? "bg-background shadow-sm text-foreground"
: "text-muted-foreground hover:bg-secondary/80 hover:text-secondary-foreground"
)}
>
<Edit3 className="w-4 h-4" />
{t("Draw", { ns: "keywords" })}
</button>
<button
type="button"
onClick={() =>
fft.setInteractionMode("pan")
}
className={cn(
"flex-1 flex items-center justify-center gap-2 py-1.5 px-2 rounded-md transition-all text-xs font-medium",
fft.interactionMode === "pan"
? "bg-background shadow-sm text-foreground"
: "text-muted-foreground hover:bg-secondary/80 hover:text-secondary-foreground"
)}
>
<Hand className="w-4 h-4" />
{t("Pan", { ns: "keywords" })}
</button>
</div>
<Button
type="button"
variant={
fft.interactionMode === "erase"
? "default"
: "outline"
}
size="sm"
className="w-full"
onClick={() =>
fft.setInteractionMode(
fft.interactionMode === "erase"
? "draw"
: "erase"
)
}
>
{t("Eraser", { ns: "keywords" })}
</Button>
</div>

<div className="space-y-2">
<Label
htmlFor="fft-brush-size"
className="text-sm font-medium"
>
{t("Brush size", { ns: "keywords" })}
</Label>
<div className="flex items-center gap-3">
<Input
id="fft-brush-size"
type="range"
min="2"
max="64"
value={fft.brushSize}
onChange={e =>
fft.setBrushSize(
Number(e.target.value)
)
}
className="flex-1 h-2 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary"
/>
<span className="text-sm text-muted-foreground min-w-[3rem] text-right">
{fft.brushSize}px
</span>
</div>
</div>

<div className="space-y-2">
<Label
htmlFor="fft-brush-shape"
className="text-sm font-medium"
>
{t("Shape", { ns: "keywords" })}
</Label>
<select
id="fft-brush-shape"
value={fft.brushShape}
onChange={e =>
fft.setBrushShape(
e.target.value as "circle" | "oval"
)
}
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
<option value="circle">
{t("Round", { ns: "keywords" })}
</option>
<option value="oval">
{t("Oval", { ns: "keywords" })}
</option>
</select>
</div>
</div>
<div className="flex flex-col gap-2 pt-2">
<Button
onClick={fft.clearMask}
variant="outline"
className="w-full"
>
<Trash2 size={ICON.SIZE} className="mr-2" />
{t("Clear", { ns: "keywords" })}
</Button>
<Button
onClick={fft.applyFilter}
variant="default"
className="w-full"
>
{t("Apply", { ns: "keywords" })}
</Button>
</div>
</>
)}
</div>
</div>
);
}
61 changes: 61 additions & 0 deletions src/components/edit-window/components/SidebarTools.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Save } from "lucide-react";
import { ICON } from "@/lib/utils/const";

interface SidebarToolsProps {
imageName: string | null;
imageSize: { w: number; h: number } | null;
onSave: () => void;
disabled: boolean;
}

export function SidebarTools({
imageName,
imageSize,
onSave,
disabled,
}: SidebarToolsProps) {
const { t } = useTranslation(["tooltip", "keywords"]);

return (
<>
{imageName && (
<div className="flex flex-col gap-1">
<h3 className="text-sm font-semibold text-muted-foreground">
Info
</h3>
<p
className="text-xs text-foreground truncate"
title={imageName}
>
{imageName}
</p>
{imageSize && (
<p className="text-xs text-muted-foreground">
{imageSize.w} × {imageSize.h} px
</p>
)}
</div>
)}

<div className="border-t border-border/30" />

<div className="flex flex-col gap-2">
<h3 className="text-sm font-semibold text-muted-foreground">
{t("Tools", { ns: "keywords" })}
</h3>
<Button
onClick={onSave}
className="w-full"
variant="default"
disabled={disabled}
>
<Save size={ICON.SIZE} className="mr-2" />
{t("Save", { ns: "tooltip" })}
</Button>
</div>
</>
);
}
14 changes: 13 additions & 1 deletion src/components/edit-window/dpi/image-dpi-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ import { ImageDpiCalibration } from "./imageDpiCalibration";
interface ImageDpiControlsProps {
imageRef: RefObject<HTMLImageElement>;
canvasRef: RefObject<HTMLCanvasElement>;
disabled?: boolean;
}

export default function ImageDpiControls({
imageRef,
canvasRef,
disabled = false,
}: ImageDpiControlsProps) {
const [active, setActive] = useState(false);
const [targetDpi, setTargetDpi] = useState<500 | 1000>(1000);
const handlerRef = useRef<ImageDpiCalibration | null>(null);
// Disable dpi when fft editor is active
useEffect(() => {
if (disabled && active) {
setActive(false);
}
}, [disabled, active]);

useEffect(() => {
const canvas = canvasRef.current;
Expand Down Expand Up @@ -49,6 +57,7 @@ export default function ImageDpiControls({
onClick={() => setActive(prev => !prev)}
variant={active ? "destructive" : "default"}
className="flex items-center justify-center gap-2"
disabled={disabled}
>
<Ruler size={ICON.SIZE} />
DPI
Expand All @@ -66,7 +75,9 @@ export default function ImageDpiControls({
"flex cursor-pointer items-center gap-2 rounded-md border px-3 py-2 transition",
targetDpi === dpi
? "border-primary bg-primary/10"
: "border-border hover:bg-muted"
: "border-border hover:bg-muted",
disabled &&
"opacity-50 pointer-events-none cursor-not-allowed"
)}
>
<span
Expand All @@ -89,6 +100,7 @@ export default function ImageDpiControls({
className="hidden"
checked={targetDpi === dpi}
onChange={() => setTargetDpi(dpi)}
disabled={disabled}
/>

<span className="text-sm">{dpi} DPI</span>
Expand Down
Loading