@@ -277,7 +277,20 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
277
277
</ div >
278
278
< div v-show ="tab === 'erd' " class ="px-4 w-full min-h-[calc(100vh-56px-32px-56px)] relative overflow-hidden ">
279
279
< div class ="absolute inset-0 " :style ="zoomStyle " ref ="zoomArea ">
280
- < div id ="preview "> </ div >
280
+ < div id ="preview " :class ="{ 'cursor-grab': isSpacePressed, 'cursor-grabbing': isDragging } "> </ div >
281
+ </ div >
282
+
283
+ < div class ="absolute bottom-0 left-1/2 -translate-x-1/2 p-4 ">
284
+ < div class ="bg-gray-800 text-white text-[10px] px-3 py-1.5 rounded inline-flex items-center space-x-6 ">
285
+ < div class ="inline-flex items-center ">
286
+ < span class ="font-medium "> {{i18n[language]["controls"]["movement"]}}:</ span >
287
+ < span class ="ml-1 text-white/60 "> {{i18n[language]["controls"]["space_drag"]}} / {{i18n[language]["controls"]["middle_drag"]}}</ span >
288
+ </ div >
289
+ < div class ="inline-flex items-center ">
290
+ < span class ="font-medium "> {{i18n[language]["controls"]["zoom"]}}:</ span >
291
+ < span class ="ml-1 text-white/60 "> {{i18n[language]["controls"]["mouse_wheel"]}} / {{i18n[language]["controls"]["pinch"]}}</ span >
292
+ </ div >
293
+ </ div >
281
294
</ div >
282
295
283
296
< div class ="absolute bottom-0 right-0 p-4 space-x-4 flex ">
@@ -294,7 +307,6 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
294
307
< 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 >
295
308
</ div >
296
309
</ div >
297
-
298
310
</ div >
299
311
< 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 >
300
312
</ div >
@@ -303,7 +315,7 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
303
315
< footer class ="bg-gray-100 ">
304
316
< p class ="text-center text-xs text-gray-600 py-2 ">
305
317
< a href ="https://github.com/koedame/rails-mermaid_erd " class ="hover:text-gray-400 " target ="_blank " rel ="noopener noreferrer ">
306
- Rails Mermaid ERD v0.5.1
318
+ Rails Mermaid ERD v0.6.0
307
319
</ a >
308
320
</ p >
309
321
</ footer >
@@ -349,6 +361,14 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
349
361
erd : 'ERD' ,
350
362
code : 'Code' ,
351
363
} ,
364
+ controls : {
365
+ movement : 'Movement' ,
366
+ zoom : 'Zoom' ,
367
+ space_drag : 'Space + Mouse Drag' ,
368
+ middle_drag : 'Middle Click + Drag' ,
369
+ mouse_wheel : 'Mouse Wheel' ,
370
+ pinch : 'Pinch In/Out' ,
371
+ }
352
372
} ,
353
373
ja : {
354
374
actions : {
@@ -383,6 +403,14 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
383
403
erd : 'ER図' ,
384
404
code : 'コード' ,
385
405
} ,
406
+ controls : {
407
+ movement : '移動方法' ,
408
+ zoom : '拡大/縮小' ,
409
+ space_drag : 'スペースキー + マウスドラッグ' ,
410
+ middle_drag : '中クリック + ドラッグ' ,
411
+ mouse_wheel : 'マウスホイール' ,
412
+ pinch : 'ピンチイン/アウト' ,
413
+ }
386
414
}
387
415
}
388
416
</ script >
@@ -409,6 +437,155 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
409
437
const posX = Vue . ref ( 0 )
410
438
const posY = Vue . ref ( 0 )
411
439
const zoomArea = Vue . ref ( null )
440
+ // Manage space key press state
441
+ const isSpacePressed = Vue . ref ( false )
442
+ // Manage dragging state
443
+ const isDragging = Vue . ref ( false )
444
+ let startX = 0
445
+ let startY = 0
446
+ // Record distance between two touch points
447
+ let lastTouchDistance = 0
448
+ // Record mouse position
449
+ let lastMouseX = 0
450
+ let lastMouseY = 0
451
+
452
+ // Calculate distance between two touch points
453
+ const getDistance = ( touches ) => {
454
+ return Math . hypot (
455
+ touches [ 0 ] . clientX - touches [ 1 ] . clientX ,
456
+ touches [ 0 ] . clientY - touches [ 1 ] . clientY
457
+ )
458
+ }
459
+
460
+ // Calculate center point of two touch positions
461
+ const getTouchCenter = ( touches ) => {
462
+ return {
463
+ x : ( touches [ 0 ] . clientX + touches [ 1 ] . clientX ) / 2 ,
464
+ y : ( touches [ 0 ] . clientY + touches [ 1 ] . clientY ) / 2
465
+ }
466
+ }
467
+
468
+ // Handle touch start
469
+ const handleTouchStart = ( e ) => {
470
+ if ( e . touches . length === 2 ) {
471
+ e . preventDefault ( )
472
+ lastTouchDistance = getDistance ( e . touches )
473
+ }
474
+ }
475
+
476
+ // Handle touch move (pinch in/out for zoom)
477
+ const handleTouchMove = ( e ) => {
478
+ if ( e . touches . length === 2 ) {
479
+ e . preventDefault ( )
480
+ const preview = document . getElementById ( 'preview' )
481
+ if ( ! preview . contains ( e . target ) ) {
482
+ return
483
+ }
484
+
485
+ const newDistance = getDistance ( e . touches )
486
+ const delta = ( newDistance - lastTouchDistance ) * 0.01
487
+ lastTouchDistance = newDistance
488
+
489
+ const center = getTouchCenter ( e . touches )
490
+ const rect = preview . getBoundingClientRect ( )
491
+ const touchX = center . x - rect . left
492
+ const touchY = center . y - rect . top
493
+
494
+ const x = touchX / scale . value - posX . value
495
+ const y = touchY / scale . value - posY . value
496
+
497
+ const newScale = Math . min ( Math . max ( scale . value + delta , 0.5 ) , 3 )
498
+ if ( newScale === scale . value ) return
499
+
500
+ posX . value = touchX / newScale - x
501
+ posY . value = touchY / newScale - y
502
+ scale . value = newScale
503
+ }
504
+ }
505
+
506
+ // Handle touch end
507
+ const handleTouchEnd = ( e ) => {
508
+ if ( e . touches . length < 2 ) {
509
+ lastTouchDistance = 0
510
+ }
511
+ }
512
+
513
+ // Handle key down (space key for drag mode toggle)
514
+ const handleKeyDown = ( e ) => {
515
+ if ( e . code === 'Space' && ! isSpacePressed . value ) {
516
+ if ( document . activeElement . tagName === 'INPUT' || document . activeElement . tagName === 'TEXTAREA' ) {
517
+ return
518
+ }
519
+ isSpacePressed . value = true
520
+ e . preventDefault ( )
521
+ }
522
+ }
523
+
524
+ // Handle key up
525
+ const handleKeyUp = ( e ) => {
526
+ if ( e . code === 'Space' ) {
527
+ isSpacePressed . value = false
528
+ isDragging . value = false
529
+ }
530
+ }
531
+
532
+ // Handle mouse down (start dragging)
533
+ const handleMouseDown = ( e ) => {
534
+ if ( isSpacePressed . value || e . button === 1 ) {
535
+ isDragging . value = true
536
+ lastMouseX = e . clientX
537
+ lastMouseY = e . clientY
538
+ e . preventDefault ( )
539
+ }
540
+ }
541
+
542
+ // Handle mouse move (movement while dragging)
543
+ const handleMouseMove = ( e ) => {
544
+ if ( isDragging . value ) {
545
+ const dx = ( e . clientX - lastMouseX ) / scale . value
546
+ const dy = ( e . clientY - lastMouseY ) / scale . value
547
+ posX . value += dx
548
+ posY . value += dy
549
+ lastMouseX = e . clientX
550
+ lastMouseY = e . clientY
551
+ e . preventDefault ( )
552
+ }
553
+ }
554
+
555
+ // Handle mouse up (end dragging)
556
+ const handleMouseUp = ( e ) => {
557
+ if ( e . button === 1 ) {
558
+ e . preventDefault ( )
559
+ }
560
+ isDragging . value = false
561
+ }
562
+
563
+ // Handle mouse wheel (zoom)
564
+ const handleWheel = ( e ) => {
565
+ const preview = document . getElementById ( 'preview' )
566
+ if ( ! preview . contains ( e . target ) ) {
567
+ return
568
+ }
569
+
570
+ e . preventDefault ( )
571
+
572
+ const rect = preview . getBoundingClientRect ( )
573
+ const mouseX = e . clientX - rect . left
574
+ const mouseY = e . clientY - rect . top
575
+
576
+ // Calculate relative coordinates based on mouse position
577
+ const x = mouseX / scale . value - posX . value
578
+ const y = mouseY / scale . value - posY . value
579
+
580
+ // Change scale
581
+ const delta = e . deltaY < 0 ? 0.1 : - 0.1
582
+ const newScale = Math . min ( Math . max ( scale . value + delta , 0.5 ) , 3 )
583
+
584
+ // Calculate new position with new scale
585
+ posX . value = mouseX / newScale - x
586
+ posY . value = mouseY / newScale - y
587
+ scale . value = newScale
588
+ }
412
589
413
590
const zoomStyle = Vue . computed ( ( ) => {
414
591
return {
@@ -660,6 +837,28 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
660
837
setLanguage ( window . navigator . language )
661
838
restoreFromHash ( )
662
839
reRender ( )
840
+
841
+ window . addEventListener ( 'keydown' , handleKeyDown )
842
+ window . addEventListener ( 'keyup' , handleKeyUp )
843
+ window . addEventListener ( 'mousedown' , handleMouseDown )
844
+ window . addEventListener ( 'mousemove' , handleMouseMove )
845
+ window . addEventListener ( 'mouseup' , handleMouseUp )
846
+ window . addEventListener ( 'wheel' , handleWheel , { passive : false } )
847
+ window . addEventListener ( 'touchstart' , handleTouchStart , { passive : false } )
848
+ window . addEventListener ( 'touchmove' , handleTouchMove , { passive : false } )
849
+ window . addEventListener ( 'touchend' , handleTouchEnd )
850
+ } )
851
+
852
+ Vue . onUnmounted ( ( ) => {
853
+ window . removeEventListener ( 'keydown' , handleKeyDown )
854
+ window . removeEventListener ( 'keyup' , handleKeyUp )
855
+ window . removeEventListener ( 'mousedown' , handleMouseDown )
856
+ window . removeEventListener ( 'mousemove' , handleMouseMove )
857
+ window . removeEventListener ( 'mouseup' , handleMouseUp )
858
+ window . removeEventListener ( 'wheel' , handleWheel )
859
+ window . removeEventListener ( 'touchstart' , handleTouchStart )
860
+ window . removeEventListener ( 'touchmove' , handleTouchMove )
861
+ window . removeEventListener ( 'touchend' , handleTouchEnd )
663
862
} )
664
863
665
864
window . addEventListener ( 'hashchange' , ( ) => {
@@ -703,7 +902,9 @@ <h2 class="font-medium text-gray-900 mb-1">{{i18n[language]["models"]["title"]}}
703
902
moveUp,
704
903
moveDown,
705
904
moveLeft,
706
- moveRight
905
+ moveRight,
906
+ isSpacePressed,
907
+ isDragging
707
908
}
708
909
}
709
910
}
0 commit comments