Files
vexplor_dev/frontend/components/messenger/ScreenCapture.tsx

124 lines
3.6 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
interface Rect {
x: number;
y: number;
w: number;
h: number;
}
interface ScreenCaptureProps {
onCapture: (file: File) => void;
onCancel: () => void;
}
export function ScreenCapture({ onCapture, onCancel }: ScreenCaptureProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [selecting, setSelecting] = useState(false);
const startRef = useRef<{ x: number; y: number } | null>(null);
const [rect, setRect] = useState<Rect | null>(null);
// ESC to cancel
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") onCancel();
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [onCancel]);
const getRect = (ax: number, ay: number, bx: number, by: number): Rect => ({
x: Math.min(ax, bx),
y: Math.min(ay, by),
w: Math.abs(bx - ax),
h: Math.abs(by - ay),
});
const handleMouseDown = (e: React.MouseEvent) => {
startRef.current = { x: e.clientX, y: e.clientY };
setSelecting(true);
setRect(null);
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!selecting || !startRef.current) return;
setRect(getRect(startRef.current.x, startRef.current.y, e.clientX, e.clientY));
};
const handleMouseUp = async (e: React.MouseEvent) => {
if (!selecting || !startRef.current) return;
setSelecting(false);
const r = getRect(startRef.current.x, startRef.current.y, e.clientX, e.clientY);
startRef.current = null;
if (r.w < 4 || r.h < 4) {
onCancel();
return;
}
// Capture via modern-screenshot
try {
const { domToPng } = await import("modern-screenshot");
const dataUrl = await domToPng(document.body, {
width: window.innerWidth,
height: window.innerHeight,
});
// Crop the captured region — image is at CSS pixel scale
const img = new Image();
img.src = dataUrl;
await new Promise((res) => { img.onload = res; });
// Scale factor between actual image size and CSS pixels
const scaleX = img.naturalWidth / window.innerWidth;
const scaleY = img.naturalHeight / window.innerHeight;
const canvas = canvasRef.current!;
canvas.width = r.w * scaleX;
canvas.height = r.h * scaleY;
const ctx = canvas.getContext("2d")!;
ctx.drawImage(
img,
r.x * scaleX, r.y * scaleY, r.w * scaleX, r.h * scaleY,
0, 0, r.w * scaleX, r.h * scaleY,
);
canvas.toBlob((blob) => {
if (!blob) { onCancel(); return; }
const file = new File([blob], `capture-${Date.now()}.png`, { type: "image/png" });
onCapture(file);
}, "image/png");
} catch {
onCancel();
}
};
return (
<>
<canvas ref={canvasRef} className="hidden" />
<div
className="fixed inset-0 bg-black/40 cursor-crosshair select-none"
style={{ zIndex: 99999 }}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
{/* instruction */}
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-black/70 text-white text-sm px-4 py-2 rounded-full pointer-events-none">
&nbsp;·&nbsp; ESC로
</div>
{/* selection rect */}
{rect && rect.w > 0 && rect.h > 0 && (
<div
className="absolute border-2 border-blue-400 bg-blue-400/10 pointer-events-none"
style={{ left: rect.x, top: rect.y, width: rect.w, height: rect.h }}
/>
)}
</div>
</>
);
}