Skip to content

Commit f335fdc

Browse files
committed
mermaid: Add drag functionality and support contious zooming
1 parent 11cc721 commit f335fdc

File tree

2 files changed

+114
-12
lines changed

2 files changed

+114
-12
lines changed

webview-ui/src/components/common/MermaidButton.tsx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export function MermaidButton({ containerRef, code, isLoading, svgToPng, childre
2222
const [copyFeedback, setCopyFeedback] = useState(false)
2323
const [isHovering, setIsHovering] = useState(false)
2424
const [modalViewMode, setModalViewMode] = useState<"diagram" | "code">("diagram")
25+
const [isDragging, setIsDragging] = useState(false)
26+
const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 })
2527
const { copyWithFeedback } = useCopyToClipboard()
2628
const { t } = useAppTranslation()
2729

@@ -82,7 +84,7 @@ export function MermaidButton({ containerRef, code, isLoading, svgToPng, childre
8284

8385
// Determine zoom direction and amount
8486
// Negative deltaY means scrolling up (zoom in), positive means scrolling down (zoom out)
85-
const delta = e.deltaY > 0 ? -0.1 : 0.1
87+
const delta = e.deltaY > 0 ? -0.25 : 0.25
8688
adjustZoom(delta)
8789
}, [])
8890

@@ -152,11 +154,25 @@ export function MermaidButton({ containerRef, code, isLoading, svgToPng, childre
152154
<>
153155
<div
154156
style={{
155-
transform: `scale(${zoomLevel})`,
157+
transform: `scale(${zoomLevel}) translate(${dragPosition.x}px, ${dragPosition.y}px)`,
156158
transformOrigin: "center center",
157-
transition: "transform 0.2s ease",
158-
cursor: "grab",
159-
}}>
159+
transition: isDragging ? "none" : "transform 0.1s ease",
160+
cursor: isDragging ? "grabbing" : "grab",
161+
}}
162+
onMouseDown={(e) => {
163+
setIsDragging(true)
164+
e.preventDefault()
165+
}}
166+
onMouseMove={(e) => {
167+
if (isDragging) {
168+
setDragPosition((prev) => ({
169+
x: prev.x + e.movementX / zoomLevel,
170+
y: prev.y + e.movementY / zoomLevel,
171+
}))
172+
}
173+
}}
174+
onMouseUp={() => setIsDragging(false)}
175+
onMouseLeave={() => setIsDragging(false)}>
160176
{containerRef.current && containerRef.current.innerHTML && (
161177
<div dangerouslySetInnerHTML={{ __html: containerRef.current.innerHTML }} />
162178
)}
@@ -179,10 +195,12 @@ export function MermaidButton({ containerRef, code, isLoading, svgToPng, childre
179195
<>
180196
<ZoomControls
181197
zoomLevel={zoomLevel}
182-
onZoomIn={() => adjustZoom(0.1)}
183-
onZoomOut={() => adjustZoom(-0.1)}
184198
zoomInTitle={t("common:mermaid.buttons.zoomIn")}
185199
zoomOutTitle={t("common:mermaid.buttons.zoomOut")}
200+
useContinuousZoom={true}
201+
adjustZoom={adjustZoom}
202+
zoomInStep={0.2}
203+
zoomOutStep={-0.2}
186204
/>
187205
<IconButton
188206
icon={copyFeedback ? "check" : "copy"}
Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,105 @@
11
import { IconButton } from "./IconButton"
2+
import { useRef, useEffect } from "react"
23

34
interface ZoomControlsProps {
45
zoomLevel: number
5-
onZoomIn: () => void
6-
onZoomOut: () => void
76
zoomInTitle?: string
87
zoomOutTitle?: string
8+
useContinuousZoom?: boolean
9+
adjustZoom?: (amount: number) => void
10+
zoomInStep?: number
11+
zoomOutStep?: number
12+
onZoomIn?: () => void
13+
onZoomOut?: () => void
914
}
1015

11-
export function ZoomControls({ zoomLevel, onZoomIn, onZoomOut, zoomInTitle, zoomOutTitle }: ZoomControlsProps) {
16+
export function ZoomControls({
17+
zoomLevel,
18+
zoomInTitle,
19+
zoomOutTitle,
20+
useContinuousZoom = false,
21+
adjustZoom,
22+
zoomInStep = 0.1,
23+
zoomOutStep = -0.1,
24+
onZoomIn,
25+
onZoomOut,
26+
}: ZoomControlsProps) {
27+
const zoomIntervalRef = useRef<NodeJS.Timeout | null>(null)
28+
29+
/**
30+
* Start continuous zoom on mouse down
31+
*/
32+
const startContinuousZoom = (amount: number) => {
33+
if (!useContinuousZoom || !adjustZoom) return
34+
35+
// Clear any existing interval first
36+
if (zoomIntervalRef.current) {
37+
clearInterval(zoomIntervalRef.current)
38+
}
39+
40+
// Immediately apply first zoom adjustment
41+
adjustZoom(amount)
42+
43+
// Set up interval for continuous zooming
44+
zoomIntervalRef.current = setInterval(() => {
45+
adjustZoom(amount)
46+
}, 150) // Adjust every 150ms while button is held down
47+
}
48+
49+
/**
50+
* Stop continuous zoom on mouse up or mouse leave
51+
*/
52+
const stopContinuousZoom = () => {
53+
if (zoomIntervalRef.current) {
54+
clearInterval(zoomIntervalRef.current)
55+
zoomIntervalRef.current = null
56+
}
57+
}
58+
59+
// Clean up interval on unmount
60+
useEffect(() => {
61+
return () => {
62+
if (zoomIntervalRef.current) {
63+
clearInterval(zoomIntervalRef.current)
64+
}
65+
}
66+
}, [])
67+
68+
// If using continuous zoom, render buttons with mouse down/up handlers
69+
if (useContinuousZoom && adjustZoom) {
70+
return (
71+
<div className="flex items-center gap-2">
72+
<button
73+
className="w-7 h-7 flex items-center justify-center border-none text-vscode-editor-foreground cursor-pointer rounded-[3px] bg-transparent hover:bg-vscode-toolbar-hoverBackground"
74+
onMouseDown={() => startContinuousZoom(zoomOutStep)}
75+
onMouseUp={stopContinuousZoom}
76+
onMouseLeave={stopContinuousZoom}
77+
title={zoomOutTitle}>
78+
<span className="codicon codicon-zoom-out"></span>
79+
</button>
80+
<div className="text-sm text-vscode-editor-foreground min-w-[50px] text-center">
81+
{Math.round(zoomLevel * 100)}%
82+
</div>
83+
<button
84+
className="w-7 h-7 flex items-center justify-center border-none text-vscode-editor-foreground cursor-pointer rounded-[3px] bg-transparent hover:bg-vscode-toolbar-hoverBackground"
85+
onMouseDown={() => startContinuousZoom(zoomInStep)}
86+
onMouseUp={stopContinuousZoom}
87+
onMouseLeave={stopContinuousZoom}
88+
title={zoomInTitle}>
89+
<span className="codicon codicon-zoom-in"></span>
90+
</button>
91+
</div>
92+
)
93+
}
94+
95+
// Default rendering with simple click handlers
1296
return (
1397
<div className="flex items-center gap-2">
14-
<IconButton icon="zoom-out" onClick={onZoomOut} title={zoomOutTitle} />
98+
<IconButton icon="zoom-out" onClick={onZoomOut || (() => adjustZoom?.(zoomOutStep))} title={zoomOutTitle} />
1599
<div className="text-sm text-vscode-editor-foreground min-w-[50px] text-center">
16100
{Math.round(zoomLevel * 100)}%
17101
</div>
18-
<IconButton icon="zoom-in" onClick={onZoomIn} title={zoomInTitle} />
102+
<IconButton icon="zoom-in" onClick={onZoomIn || (() => adjustZoom?.(zoomInStep))} title={zoomInTitle} />
19103
</div>
20104
)
21105
}

0 commit comments

Comments
 (0)