Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
Liam Gammon committed Nov 11, 2024
2 parents b2679f9 + abba7df commit 0b4b6fd
Show file tree
Hide file tree
Showing 12 changed files with 961 additions and 380 deletions.
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

211 changes: 95 additions & 116 deletions src/app/(tools)/rounded-border/rounded-tool.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"use client";
import { usePlausible } from "next-plausible";
import { useMemo, useState } from "react";
import type { ChangeEvent } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useLocalStorage } from "@/hooks/use-local-storage";
import React from "react";
import { UploadBox } from "@/components/shared/upload-box";
import { OptionSelector } from "@/components/shared/option-selector";
import { BorderRadiusSelector } from "@/components/border-radius-selector";
import {
useFileUploader,
type FileUploaderResult,
} from "@/hooks/use-file-uploader";
import { FileDropzone } from "@/components/shared/file-dropzone";

type Radius = 2 | 4 | 8 | 16 | 32 | 64;
type Radius = number;

type BackgroundOption = "white" | "black" | "transparent";

Expand Down Expand Up @@ -69,58 +75,20 @@ function useImageConverter(props: {
};
}

export const useFileUploader = () => {
const [imageContent, setImageContent] = useState<string>("");

const [imageMetadata, setImageMetadata] = useState<{
width: number;
height: number;
name: string;
} | null>(null);

const handleFileUpload = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target?.result as string;
const img = new Image();
img.onload = () => {
setImageMetadata({
width: img.width,
height: img.height,
name: file.name,
});
setImageContent(content);
};
img.src = content;
};
reader.readAsDataURL(file);
}
};

const cancel = () => {
setImageContent("");
setImageMetadata(null);
};

return { imageContent, imageMetadata, handleFileUpload, cancel };
};

interface ImageRendererProps {
imageContent: string;
radius: Radius;
background: BackgroundOption;
}

const ImageRenderer: React.FC<ImageRendererProps> = ({
const ImageRenderer = ({
imageContent,
radius,
background,
}) => {
const containerRef = React.useRef<HTMLDivElement>(null);
}: ImageRendererProps) => {
const containerRef = useRef<HTMLDivElement>(null);

React.useEffect(() => {
useEffect(() => {
if (containerRef.current) {
const imgElement = containerRef.current.querySelector("img");
if (imgElement) {
Expand All @@ -130,7 +98,7 @@ const ImageRenderer: React.FC<ImageRendererProps> = ({
}, [imageContent, radius]);

return (
<div ref={containerRef} className="relative max-h-full max-w-full">
<div ref={containerRef} className="relative w-[500px]">
<div
className="absolute inset-0"
style={{ backgroundColor: background, borderRadius: 0 }}
Expand All @@ -139,7 +107,7 @@ const ImageRenderer: React.FC<ImageRendererProps> = ({
src={imageContent}
alt="Preview"
className="relative rounded-lg"
style={{ width: "100%", height: "auto" }}
style={{ width: "100%", height: "auto", objectFit: "contain" }}
/>
</div>
);
Expand All @@ -156,9 +124,7 @@ function SaveAsPngButton({
background: BackgroundOption;
imageMetadata: { width: number; height: number; name: string };
}) {
const [canvasRef, setCanvasRef] = React.useState<HTMLCanvasElement | null>(
null,
);
const [canvasRef, setCanvasRef] = useState<HTMLCanvasElement | null>(null);
const { convertToPng, canvasProps } = useImageConverter({
canvas: canvasRef,
imageContent,
Expand All @@ -185,91 +151,104 @@ function SaveAsPngButton({
);
}

export function RoundedTool() {
const { imageContent, imageMetadata, handleFileUpload, cancel } =
useFileUploader();

function RoundedToolCore(props: { fileUploaderProps: FileUploaderResult }) {
const { imageContent, imageMetadata, handleFileUploadEvent, cancel } =
props.fileUploaderProps;
const [radius, setRadius] = useLocalStorage<Radius>("roundedTool_radius", 2);
const [isCustomRadius, setIsCustomRadius] = useState(false);
const [background, setBackground] = useLocalStorage<BackgroundOption>(
"roundedTool_background",
"transparent",
);

if (!imageMetadata)
const handleRadiusChange = (value: number | "custom") => {
if (value === "custom") {
setIsCustomRadius(true);
} else {
setRadius(value);
setIsCustomRadius(false);
}
};

if (!imageMetadata) {
return (
<div className="flex flex-col gap-4 p-4">
<p className="text-center">Round the corners of any image</p>
<div className="flex justify-center">
<label className="inline-flex cursor-pointer items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white shadow-md transition-colors duration-200 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-75">
<span>Upload Image</span>
<input
type="file"
onChange={handleFileUpload}
accept="image/*"
className="hidden"
/>
</label>
</div>
</div>
<UploadBox
title="Add rounded borders to your images. Quick and easy."
subtitle="Allows pasting images from clipboard"
description="Upload Image"
accept="image/*"
onChange={handleFileUploadEvent}
/>
);
}

return (
<div className="flex flex-col items-center justify-center gap-4 p-4 text-2xl">
<ImageRenderer
imageContent={imageContent}
radius={radius}
background={background}
/>
<p>{imageMetadata.name}</p>
<p>
Original size: {imageMetadata.width}px x {imageMetadata.height}px
</p>
<div className="flex gap-2">
{([2, 4, 8, 16, 32, 64] as Radius[]).map((value) => (
<button
key={value}
onClick={() => setRadius(value)}
className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
radius === value
? "bg-green-600 text-white"
: "bg-gray-200 text-gray-800 hover:bg-gray-300"
}`}
>
{value}px
</button>
))}
</div>
<div className="flex gap-2">
{(["white", "black", "transparent"] as BackgroundOption[]).map(
(option) => (
<button
key={option}
onClick={() => setBackground(option)}
className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
background === option
? "bg-purple-600 text-white"
: "bg-gray-200 text-gray-800 hover:bg-gray-300"
}`}
>
{option.charAt(0).toUpperCase() + option.slice(1)}
</button>
),
)}
</div>
<div className="flex gap-2">
<SaveAsPngButton
<div className="mx-auto flex max-w-2xl flex-col items-center justify-center gap-6 p-6">
<div className="flex w-full flex-col items-center gap-4 rounded-xl p-6">
<ImageRenderer
imageContent={imageContent}
radius={radius}
background={background}
imageMetadata={imageMetadata}
/>
<p className="text-lg font-medium text-white/80">
{imageMetadata.name}
</p>
</div>

<div className="flex flex-col items-center rounded-lg bg-white/5 p-3">
<span className="text-sm text-white/60">Original Size</span>
<span className="font-medium text-white">
{imageMetadata.width} × {imageMetadata.height}
</span>
</div>

<BorderRadiusSelector
title="Border Radius"
options={[2, 4, 8, 16, 32, 64]}
selected={isCustomRadius ? "custom" : radius}
onChange={handleRadiusChange}
customValue={radius}
onCustomValueChange={setRadius}
/>

<OptionSelector
title="Background"
options={["white", "black", "transparent"]}
selected={background}
onChange={setBackground}
formatOption={(option) =>
option.charAt(0).toUpperCase() + option.slice(1)
}
/>

<div className="flex gap-3">
<button
onClick={cancel}
className="rounded-md bg-red-700 px-3 py-1 text-sm font-medium text-white transition-colors hover:bg-red-800"
className="rounded-lg bg-red-700 px-4 py-2 text-sm font-medium text-white/90 transition-colors hover:bg-red-800"
>
Cancel
</button>
<SaveAsPngButton
imageContent={imageContent}
radius={radius}
background={background}
imageMetadata={imageMetadata}
/>
</div>
</div>
);
}

export function RoundedTool() {
const fileUploaderProps = useFileUploader();

return (
<FileDropzone
setCurrentFile={fileUploaderProps.handleFileUpload}
acceptedFileTypes={["image/*", ".jpg", ".jpeg", ".png", ".webp", ".svg"]}
dropText="Drop image file"
>
<RoundedToolCore fileUploaderProps={fileUploaderProps} />
</FileDropzone>
);
}
Loading

0 comments on commit 0b4b6fd

Please sign in to comment.