feat: add on-canvas zoom controls for psd image slots
This commit is contained in:
parent
dc04c1d91b
commit
dceebf0652
@ -9,10 +9,12 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
Move,
|
Move,
|
||||||
MousePointerClick,
|
MousePointerClick,
|
||||||
|
RefreshCcw,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Type,
|
Type,
|
||||||
Upload,
|
Upload,
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
|
ZoomOut,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry";
|
import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry";
|
||||||
import {
|
import {
|
||||||
@ -565,6 +567,20 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nudgeImageScale(layerId: string, delta: number) {
|
||||||
|
updateImagePlacement(layerId, (current) => ({
|
||||||
|
...current,
|
||||||
|
scale: Math.max(0.4, Math.min(3, Number((current.scale + delta).toFixed(2)))),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetImagePlacement(layerId: string) {
|
||||||
|
setImagePlacements((current) => ({
|
||||||
|
...current,
|
||||||
|
[layerId]: getDefaultAgentPsdImagePlacement(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function handleImageDragStart(
|
function handleImageDragStart(
|
||||||
layer: AgentPsdTemplateImageLayerSlot,
|
layer: AgentPsdTemplateImageLayerSlot,
|
||||||
event: ReactPointerEvent<HTMLDivElement>
|
event: ReactPointerEvent<HTMLDivElement>
|
||||||
@ -611,6 +627,21 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
window.addEventListener("pointercancel", handlePointerUp);
|
window.addEventListener("pointercancel", handlePointerUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleImageWheel(
|
||||||
|
layer: AgentPsdTemplateImageLayerSlot,
|
||||||
|
event: ReactPointerEvent<HTMLDivElement>
|
||||||
|
) {
|
||||||
|
if (!String(effectiveImageAssignments[layer.id] || "").trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const delta = event.deltaY < 0 ? 0.08 : -0.08;
|
||||||
|
nudgeImageScale(layer.id, delta);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleRender() {
|
async function handleRender() {
|
||||||
if (!selectedTemplate) {
|
if (!selectedTemplate) {
|
||||||
toast.error("请先选择 PSD 模板");
|
toast.error("请先选择 PSD 模板");
|
||||||
@ -898,6 +929,7 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => setSelectedLayerId(layer.id)}
|
onClick={() => setSelectedLayerId(layer.id)}
|
||||||
onPointerDown={(event) => handleImageDragStart(layer, event)}
|
onPointerDown={(event) => handleImageDragStart(layer, event)}
|
||||||
|
onWheel={(event) => handleImageWheel(layer, event)}
|
||||||
className="absolute overflow-hidden rounded-lg transition-all"
|
className="absolute overflow-hidden rounded-lg transition-all"
|
||||||
style={{
|
style={{
|
||||||
...frameStyle,
|
...frameStyle,
|
||||||
@ -912,9 +944,50 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
<div className="absolute left-2 top-2 rounded-full bg-black/70 px-2 py-1 text-[11px] text-white">
|
<div className="absolute left-2 top-2 rounded-full bg-black/70 px-2 py-1 text-[11px] text-white">
|
||||||
{layer.layerName}
|
{layer.layerName}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="absolute right-2 top-2 flex items-center gap-1 rounded-full bg-black/70 p-1 text-white shadow-lg">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-full p-1 transition hover:bg-white/10"
|
||||||
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
nudgeImageScale(layer.id, -0.1);
|
||||||
|
}}
|
||||||
|
aria-label="缩小图片"
|
||||||
|
title="缩小"
|
||||||
|
>
|
||||||
|
<ZoomOut className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-full p-1 transition hover:bg-white/10"
|
||||||
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
nudgeImageScale(layer.id, 0.1);
|
||||||
|
}}
|
||||||
|
aria-label="放大图片"
|
||||||
|
title="放大"
|
||||||
|
>
|
||||||
|
<ZoomIn className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-full p-1 transition hover:bg-white/10"
|
||||||
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
resetImagePlacement(layer.id);
|
||||||
|
}}
|
||||||
|
aria-label="重置图片位置和缩放"
|
||||||
|
title="重置"
|
||||||
|
>
|
||||||
|
<RefreshCcw className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="pointer-events-none absolute bottom-2 right-2 flex items-center gap-1 rounded-full bg-orange-500/85 px-2 py-1 text-[11px] text-white">
|
<div className="pointer-events-none absolute bottom-2 right-2 flex items-center gap-1 rounded-full bg-orange-500/85 px-2 py-1 text-[11px] text-white">
|
||||||
<Move className="h-3 w-3" />
|
<Move className="h-3 w-3" />
|
||||||
拖动调整
|
拖动或滚轮缩放
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@ -1102,12 +1175,7 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() => resetImagePlacement(selectedImageLayer.id)}
|
||||||
setImagePlacements((current) => ({
|
|
||||||
...current,
|
|
||||||
[selectedImageLayer.id]: getDefaultAgentPsdImagePlacement(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
重置位置与缩放
|
重置位置与缩放
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user