feat(PlantUML): Add zoom and copy functionality to PlantUML image component
This commit is contained in:
parent
68acbe8f3d
commit
e0ba3b8968
@ -86,6 +86,7 @@ type PlantUMLServerImageProps = {
|
|||||||
format: 'png' | 'svg'
|
format: 'png' | 'svg'
|
||||||
diagram: string
|
diagram: string
|
||||||
onClick?: React.MouseEventHandler<HTMLDivElement>
|
onClick?: React.MouseEventHandler<HTMLDivElement>
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlantUMLImageUrl(format: 'png' | 'svg', diagram: string, isDark?: boolean) {
|
function getPlantUMLImageUrl(format: 'png' | 'svg', diagram: string, isDark?: boolean) {
|
||||||
@ -96,13 +97,13 @@ function getPlantUMLImageUrl(format: 'png' | 'svg', diagram: string, isDark?: bo
|
|||||||
return `${PlantUMLServer}/${format}/${encodedDiagram}`
|
return `${PlantUMLServer}/${format}/${encodedDiagram}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlantUMLServerImage: React.FC<PlantUMLServerImageProps> = ({ format, diagram, onClick }) => {
|
const PlantUMLServerImage: React.FC<PlantUMLServerImageProps> = ({ format, diagram, onClick, className }) => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const isDark = theme === 'dark'
|
const isDark = theme === 'dark'
|
||||||
const url = getPlantUMLImageUrl(format, diagram, isDark)
|
const url = getPlantUMLImageUrl(format, diagram, isDark)
|
||||||
return (
|
return (
|
||||||
<StyledPlantUML onClick={onClick}>
|
<StyledPlantUML onClick={onClick} className={className}>
|
||||||
<Spin
|
<Spin
|
||||||
spinning={loading}
|
spinning={loading}
|
||||||
indicator={
|
indicator={
|
||||||
@ -136,7 +137,7 @@ const PlantUMLPopupCantaier: React.FC<PlantUMLPopupProps> = ({ resolve, diagram
|
|||||||
png: false,
|
png: false,
|
||||||
svg: false
|
svg: false
|
||||||
})
|
})
|
||||||
|
const [scale, setScale] = useState(1)
|
||||||
const [activeTab, setActiveTab] = useState('preview')
|
const [activeTab, setActiveTab] = useState('preview')
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -151,6 +152,51 @@ const PlantUMLPopupCantaier: React.FC<PlantUMLPopupProps> = ({ resolve, diagram
|
|||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
resolve({})
|
resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleZoom = (delta: number) => {
|
||||||
|
const newScale = Math.max(0.1, Math.min(3, scale + delta))
|
||||||
|
setScale(newScale)
|
||||||
|
|
||||||
|
const container = document.querySelector('.plantuml-image-container')
|
||||||
|
if (container) {
|
||||||
|
const img = container.querySelector('img')
|
||||||
|
if (img) {
|
||||||
|
img.style.transformOrigin = 'top left'
|
||||||
|
img.style.transform = `scale(${newScale})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopyImage = async () => {
|
||||||
|
try {
|
||||||
|
const imageElement = document.querySelector('.plantuml-image-container img')
|
||||||
|
if (!imageElement) return
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
const img = imageElement as HTMLImageElement
|
||||||
|
|
||||||
|
if (!img.complete) {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
img.onload = resolve
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.width = img.naturalWidth
|
||||||
|
canvas.height = img.naturalHeight
|
||||||
|
|
||||||
|
if (ctx) {
|
||||||
|
ctx.drawImage(img, 0, 0)
|
||||||
|
const blob = await new Promise<Blob>((resolve) => canvas.toBlob((b) => resolve(b!), 'image/png'))
|
||||||
|
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })])
|
||||||
|
window.message.success(t('message.copy.success'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Copy failed:', error)
|
||||||
|
window.message.error(t('message.copy.failed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDownload = (format: 'svg' | 'png') => {
|
const handleDownload = (format: 'svg' | 'png') => {
|
||||||
const timestamp = Date.now()
|
const timestamp = Date.now()
|
||||||
const url = `${PlantUMLServer}/${format}/${encodedDiagram}`
|
const url = `${PlantUMLServer}/${format}/${encodedDiagram}`
|
||||||
@ -188,6 +234,9 @@ const PlantUMLPopupCantaier: React.FC<PlantUMLPopupProps> = ({ resolve, diagram
|
|||||||
)}
|
)}
|
||||||
{activeTab === 'preview' && (
|
{activeTab === 'preview' && (
|
||||||
<>
|
<>
|
||||||
|
<Button onClick={() => handleZoom(0.1)}>{t('mermaid.resize.zoom-in')}</Button>
|
||||||
|
<Button onClick={() => handleZoom(-0.1)}>{t('mermaid.resize.zoom-out')}</Button>
|
||||||
|
<Button onClick={handleCopyImage}>{t('common.copy')}</Button>
|
||||||
<Button onClick={() => handleDownload('svg')} loading={downloading.svg}>
|
<Button onClick={() => handleDownload('svg')} loading={downloading.svg}>
|
||||||
{t('plantuml.download.svg')}
|
{t('plantuml.download.svg')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -205,7 +254,7 @@ const PlantUMLPopupCantaier: React.FC<PlantUMLPopupProps> = ({ resolve, diagram
|
|||||||
{
|
{
|
||||||
key: 'preview',
|
key: 'preview',
|
||||||
label: t('plantuml.tabs.preview'),
|
label: t('plantuml.tabs.preview'),
|
||||||
children: <PlantUMLServerImage format="svg" diagram={diagram} />
|
children: <PlantUMLServerImage format="svg" diagram={diagram} className="plantuml-image-container" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'source',
|
key: 'source',
|
||||||
@ -268,6 +317,7 @@ const StyledPlantUML = styled.div`
|
|||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
background: var(--color-code-background);
|
background: var(--color-code-background);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
async function downloadUrl(url: string, filename: string) {
|
async function downloadUrl(url: string, filename: string) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user