Skip to content

Commit 52c0a6f

Browse files
authored
Merge pull request #138 from koedame/feature/improve-erd-viewer-operation
Add zoom and drag mouse controls
2 parents dac4792 + 2e6c780 commit 52c0a6f

File tree

1 file changed

+204
-3
lines changed

1 file changed

+204
-3
lines changed

lib/templates/index.html.erb

Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,20 @@
230230
</div>
231231
<div v-show="tab === 'erd'" class="px-4 w-full min-h-[calc(100vh-56px-32px-56px)] relative overflow-hidden">
232232
<div class="absolute inset-0" :style="zoomStyle" ref="zoomArea">
233-
<div id="preview"></div>
233+
<div id="preview" :class="{ 'cursor-grab': isSpacePressed, 'cursor-grabbing': isDragging }"></div>
234+
</div>
235+
236+
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 p-4">
237+
<div class="bg-gray-800 text-white text-[10px] px-3 py-1.5 rounded inline-flex items-center space-x-6">
238+
<div class="inline-flex items-center">
239+
<span class="font-medium">{{i18n[language]["controls"]["movement"]}}:</span>
240+
<span class="ml-1 text-white/60">{{i18n[language]["controls"]["space_drag"]}} / {{i18n[language]["controls"]["middle_drag"]}}</span>
241+
</div>
242+
<div class="inline-flex items-center">
243+
<span class="font-medium">{{i18n[language]["controls"]["zoom"]}}:</span>
244+
<span class="ml-1 text-white/60">{{i18n[language]["controls"]["mouse_wheel"]}} / {{i18n[language]["controls"]["pinch"]}}</span>
245+
</div>
246+
</div>
234247
</div>
235248

236249
<div class="absolute bottom-0 right-0 p-4 space-x-4 flex">
@@ -247,7 +260,6 @@
247260
<button class="text-xs py-1 px-2 rounded hover:bg-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-900 bg-gray-400 text-gray-900" @click="moveRight"></button>
248261
</div>
249262
</div>
250-
251263
</div>
252264
<textarea v-show="tab === 'code'" class="px-4 bg-gray-900 text-gray-300 font-mono w-full text-xs min-h-[calc(100vh-56px-32px-56px)] border-0 focus:ring-0" readonly v-model="mermaidErd"></textarea>
253265
</div>
@@ -302,6 +314,14 @@
302314
erd: 'ERD',
303315
code: 'Code',
304316
},
317+
controls: {
318+
movement: 'Movement',
319+
zoom: 'Zoom',
320+
space_drag: 'Space + Mouse Drag',
321+
middle_drag: 'Middle Click + Drag',
322+
mouse_wheel: 'Mouse Wheel',
323+
pinch: 'Pinch In/Out',
324+
}
305325
},
306326
ja: {
307327
actions: {
@@ -336,6 +356,14 @@
336356
erd: 'ER図',
337357
code: 'コード',
338358
},
359+
controls: {
360+
movement: '移動方法',
361+
zoom: '拡大/縮小',
362+
space_drag: 'スペースキー + マウスドラッグ',
363+
middle_drag: '中クリック + ドラッグ',
364+
mouse_wheel: 'マウスホイール',
365+
pinch: 'ピンチイン/アウト',
366+
}
339367
}
340368
}
341369
</script>
@@ -362,6 +390,155 @@
362390
const posX = Vue.ref(0)
363391
const posY = Vue.ref(0)
364392
const zoomArea = Vue.ref(null)
393+
// Manage space key press state
394+
const isSpacePressed = Vue.ref(false)
395+
// Manage dragging state
396+
const isDragging = Vue.ref(false)
397+
let startX = 0
398+
let startY = 0
399+
// Record distance between two touch points
400+
let lastTouchDistance = 0
401+
// Record mouse position
402+
let lastMouseX = 0
403+
let lastMouseY = 0
404+
405+
// Calculate distance between two touch points
406+
const getDistance = (touches) => {
407+
return Math.hypot(
408+
touches[0].clientX - touches[1].clientX,
409+
touches[0].clientY - touches[1].clientY
410+
)
411+
}
412+
413+
// Calculate center point of two touch positions
414+
const getTouchCenter = (touches) => {
415+
return {
416+
x: (touches[0].clientX + touches[1].clientX) / 2,
417+
y: (touches[0].clientY + touches[1].clientY) / 2
418+
}
419+
}
420+
421+
// Handle touch start
422+
const handleTouchStart = (e) => {
423+
if (e.touches.length === 2) {
424+
e.preventDefault()
425+
lastTouchDistance = getDistance(e.touches)
426+
}
427+
}
428+
429+
// Handle touch move (pinch in/out for zoom)
430+
const handleTouchMove = (e) => {
431+
if (e.touches.length === 2) {
432+
e.preventDefault()
433+
const preview = document.getElementById('preview')
434+
if (!preview.contains(e.target)) {
435+
return
436+
}
437+
438+
const newDistance = getDistance(e.touches)
439+
const delta = (newDistance - lastTouchDistance) * 0.01
440+
lastTouchDistance = newDistance
441+
442+
const center = getTouchCenter(e.touches)
443+
const rect = preview.getBoundingClientRect()
444+
const touchX = center.x - rect.left
445+
const touchY = center.y - rect.top
446+
447+
const x = touchX / scale.value - posX.value
448+
const y = touchY / scale.value - posY.value
449+
450+
const newScale = Math.min(Math.max(scale.value + delta, 0.5), 3)
451+
if (newScale === scale.value) return
452+
453+
posX.value = touchX / newScale - x
454+
posY.value = touchY / newScale - y
455+
scale.value = newScale
456+
}
457+
}
458+
459+
// Handle touch end
460+
const handleTouchEnd = (e) => {
461+
if (e.touches.length < 2) {
462+
lastTouchDistance = 0
463+
}
464+
}
465+
466+
// Handle key down (space key for drag mode toggle)
467+
const handleKeyDown = (e) => {
468+
if (e.code === 'Space' && !isSpacePressed.value) {
469+
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
470+
return
471+
}
472+
isSpacePressed.value = true
473+
e.preventDefault()
474+
}
475+
}
476+
477+
// Handle key up
478+
const handleKeyUp = (e) => {
479+
if (e.code === 'Space') {
480+
isSpacePressed.value = false
481+
isDragging.value = false
482+
}
483+
}
484+
485+
// Handle mouse down (start dragging)
486+
const handleMouseDown = (e) => {
487+
if (isSpacePressed.value || e.button === 1) {
488+
isDragging.value = true
489+
lastMouseX = e.clientX
490+
lastMouseY = e.clientY
491+
e.preventDefault()
492+
}
493+
}
494+
495+
// Handle mouse move (movement while dragging)
496+
const handleMouseMove = (e) => {
497+
if (isDragging.value) {
498+
const dx = (e.clientX - lastMouseX) / scale.value
499+
const dy = (e.clientY - lastMouseY) / scale.value
500+
posX.value += dx
501+
posY.value += dy
502+
lastMouseX = e.clientX
503+
lastMouseY = e.clientY
504+
e.preventDefault()
505+
}
506+
}
507+
508+
// Handle mouse up (end dragging)
509+
const handleMouseUp = (e) => {
510+
if (e.button === 1) {
511+
e.preventDefault()
512+
}
513+
isDragging.value = false
514+
}
515+
516+
// Handle mouse wheel (zoom)
517+
const handleWheel = (e) => {
518+
const preview = document.getElementById('preview')
519+
if (!preview.contains(e.target)) {
520+
return
521+
}
522+
523+
e.preventDefault()
524+
525+
const rect = preview.getBoundingClientRect()
526+
const mouseX = e.clientX - rect.left
527+
const mouseY = e.clientY - rect.top
528+
529+
// Calculate relative coordinates based on mouse position
530+
const x = mouseX / scale.value - posX.value
531+
const y = mouseY / scale.value - posY.value
532+
533+
// Change scale
534+
const delta = e.deltaY < 0 ? 0.1 : -0.1
535+
const newScale = Math.min(Math.max(scale.value + delta, 0.5), 3)
536+
537+
// Calculate new position with new scale
538+
posX.value = mouseX / newScale - x
539+
posY.value = mouseY / newScale - y
540+
scale.value = newScale
541+
}
365542

366543
const zoomStyle = Vue.computed(() => {
367544
return {
@@ -613,6 +790,28 @@
613790
setLanguage(window.navigator.language)
614791
restoreFromHash()
615792
reRender()
793+
794+
window.addEventListener('keydown', handleKeyDown)
795+
window.addEventListener('keyup', handleKeyUp)
796+
window.addEventListener('mousedown', handleMouseDown)
797+
window.addEventListener('mousemove', handleMouseMove)
798+
window.addEventListener('mouseup', handleMouseUp)
799+
window.addEventListener('wheel', handleWheel, { passive: false })
800+
window.addEventListener('touchstart', handleTouchStart, { passive: false })
801+
window.addEventListener('touchmove', handleTouchMove, { passive: false })
802+
window.addEventListener('touchend', handleTouchEnd)
803+
})
804+
805+
Vue.onUnmounted(() => {
806+
window.removeEventListener('keydown', handleKeyDown)
807+
window.removeEventListener('keyup', handleKeyUp)
808+
window.removeEventListener('mousedown', handleMouseDown)
809+
window.removeEventListener('mousemove', handleMouseMove)
810+
window.removeEventListener('mouseup', handleMouseUp)
811+
window.removeEventListener('wheel', handleWheel)
812+
window.removeEventListener('touchstart', handleTouchStart)
813+
window.removeEventListener('touchmove', handleTouchMove)
814+
window.removeEventListener('touchend', handleTouchEnd)
616815
})
617816

618817
window.addEventListener('hashchange', () => {
@@ -656,7 +855,9 @@
656855
moveUp,
657856
moveDown,
658857
moveLeft,
659-
moveRight
858+
moveRight,
859+
isSpacePressed,
860+
isDragging
660861
}
661862
}
662863
}

0 commit comments

Comments
 (0)