Skip to content
Merged
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
6 changes: 3 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ function AppContent() {
return;
}

// Ctrl/Cmd + 1-0 to switch tools
if ((e.ctrlKey || e.metaKey) && e.key >= '1' && e.key <= '0') {
// Ctrl/Cmd + 1-9 and 0 to switch tools
if ((e.ctrlKey || e.metaKey) && (e.key >= '1' && e.key <= '9' || e.key === '0')) {
e.preventDefault();
const tools: ToolType[] = ['config', 'markdown', 'diff', 'image', 'regex', 'decoder', 'timestamp', 'generator', 'case', 'url'];
const toolIndex = parseInt(e.key) === 0 ? 9 : parseInt(e.key) - 1;
const toolIndex = e.key === '0' ? 9 : parseInt(e.key) - 1;
setActiveTool(tools[toolIndex]);
}
};
Expand Down
77 changes: 69 additions & 8 deletions src/components/ImageBeautifier/ImageBeautifier.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { Upload, Download, Image as ImageIcon, RefreshCw } from 'lucide-react';
import { Upload, Download, Image as ImageIcon, RefreshCw, Copy, Check } from 'lucide-react';
import clsx from 'clsx';

const gradientPresets = [
Expand All @@ -26,6 +26,7 @@ export const ImageBeautifier: React.FC = () => {
const [borderRadius, setBorderRadius] = useState(12);
const [padding, setPadding] = useState(64);
const [shadow, setShadow] = useState(true);
const [copied, setCopied] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const dropZoneRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -92,6 +93,43 @@ export const ImageBeautifier: React.FC = () => {
link.click();
};

const handleCopy = async () => {
if (!canvasRef.current || !image) return;

try {
// Check if Clipboard API is supported
if (!navigator.clipboard || !window.ClipboardItem) {
alert('Copy to clipboard is not supported in this browser. Please use the Download button instead.');
return;
}

const canvas = canvasRef.current;

// Convert canvas to blob using Promise-based approach
const blob = await new Promise<Blob | null>((resolve) => {
canvas.toBlob((blob) => resolve(blob), 'image/png');
});

if (!blob) {
throw new Error('Failed to create blob from canvas');
}

// Use Clipboard API to copy the image
await navigator.clipboard.write([
new window.ClipboardItem({
'image/png': blob
})
]);

// Show success feedback
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy image:', err);
alert('Failed to copy image to clipboard. Please try downloading instead.');
}
};

const swapColors = () => {
const temp = gradientColor1;
setGradientColor1(gradientColor2);
Expand Down Expand Up @@ -405,13 +443,36 @@ export const ImageBeautifier: React.FC = () => {
<div className="px-4 py-3 border-b border-gray-200 dark:border-[#30363d] flex items-center justify-between">
<span className="text-xs font-medium text-gray-500 dark:text-[#8b949e] uppercase tracking-wider">Preview</span>
{image && (
<button
onClick={handleDownload}
className="flex items-center gap-2 px-3 py-1.5 rounded text-xs font-medium text-white bg-[#238636] hover:bg-[#2ea043] transition-colors"
>
<Download size={14} />
<span>Download</span>
</button>
<div className="flex gap-2">
<button
onClick={handleCopy}
className={clsx(
'flex items-center gap-2 px-3 py-1.5 rounded text-xs font-medium text-white transition-colors',
copied
? 'bg-[#2ea043]'
: 'bg-[#238636] hover:bg-[#2ea043]'
)}
>
{copied ? (
<>
<Check size={14} />
<span>Copied!</span>
</>
) : (
<>
<Copy size={14} />
<span>Copy</span>
</>
)}
</button>
<button
onClick={handleDownload}
className="flex items-center gap-2 px-3 py-1.5 rounded text-xs font-medium text-white bg-[#238636] hover:bg-[#2ea043] transition-colors"
>
<Download size={14} />
<span>Download</span>
</button>
</div>
)}
</div>

Expand Down
14 changes: 11 additions & 3 deletions src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ activeTool, onToolChange }) =>

{/* Tools */}
<div className="flex-1 py-2">
{tools.map((tool) => {
{tools.map((tool, index) => {
const Icon = tool.icon;
const isActive = activeTool === tool.id;
const shortcutKey = index === 9 ? '0' : (index + 1).toString();
return (
<button
key={tool.id}
Expand All @@ -90,13 +91,20 @@ export const Sidebar: React.FC<SidebarProps> = ({ activeTool, onToolChange }) =>
? 'text-blue-600 dark:text-[#58a6ff] bg-blue-50 dark:bg-[#21262d]/50'
: 'text-gray-500 dark:text-[#8b949e] hover:bg-gray-100 dark:hover:bg-[#21262d]/30 hover:text-gray-900 dark:hover:text-[#c9d1d9]'
)}
title={isCollapsed ? tool.name : undefined}
title={isCollapsed ? `${tool.name} (Ctrl+${shortcutKey})` : undefined}
>
{isActive && (
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-6 bg-blue-600 dark:bg-[#58a6ff] rounded-r" />
)}
<Icon size={20} />
{!isCollapsed && <span className="text-sm">{tool.name}</span>}
{!isCollapsed && (
<>
<span className="text-sm flex-1 text-left">{tool.name}</span>
<span className="text-[10px] text-gray-400 dark:text-[#6e7681] font-mono">
⌃{shortcutKey}
</span>
</>
)}
</button>
);
})}
Expand Down
Loading