@@ -42,6 +42,7 @@ import { useApplicationState } from '../../../../../../settings/static/Applicati
4242import { connectServerModal , connectServer } from '../../../../../sqleditor/static/js/components/connectServer' ;
4343import { useEffect } from 'react' ;
4444import { FileManagerUtils } from '../../../../../../misc/file_manager/static/js/components/FileManager' ;
45+ import SearchNode from './SearchNode' ;
4546
4647/* Custom react-diagram action for keyboard events */
4748export class KeyboardShortcutAction extends Action {
@@ -169,9 +170,9 @@ export default class ERDTool extends React.Component {
169170 this . eventBus = new EventBus ( ) ;
170171
171172 _ . bindAll ( this , [ 'onLoadDiagram' , 'onSaveDiagram' , 'onSQLClick' ,
172- 'onImageClick' , 'onAddNewNode' , 'onEditTable' , 'onCloneNode' , 'onDeleteNode' , 'onNoteClick' ,
173+ 'onImageClick' , 'onSearchNode' , ' onAddNewNode', 'onEditTable' , 'onCloneNode' , 'onDeleteNode' , 'onNoteClick' ,
173174 'onNoteClose' , 'onOneToOneClick' , 'onOneToManyClick' , 'onManyToManyClick' , 'onAutoDistribute' , 'onDetailsToggle' ,
174- 'onChangeColors' , 'onDropNode' , 'onNotationChange' , 'closePanel'
175+ 'onChangeColors' , 'onDropNode' , 'onNotationChange' , 'closePanel' , 'scrollToNode'
175176 ] ) ;
176177
177178 this . diagram . zoomToFit = this . diagram . zoomToFit . bind ( this . diagram ) ;
@@ -240,6 +241,7 @@ export default class ERDTool extends React.Component {
240241 this . eventBus . registerListener ( ERD_EVENTS . SAVE_DIAGRAM , this . onSaveDiagram ) ;
241242 this . eventBus . registerListener ( ERD_EVENTS . SHOW_SQL , this . onSQLClick ) ;
242243 this . eventBus . registerListener ( ERD_EVENTS . DOWNLOAD_IMAGE , this . onImageClick ) ;
244+ this . eventBus . registerListener ( ERD_EVENTS . SEARCH_NODE , this . onSearchNode ) ;
243245 this . eventBus . registerListener ( ERD_EVENTS . ADD_NODE , this . onAddNewNode ) ;
244246 this . eventBus . registerListener ( ERD_EVENTS . EDIT_NODE , this . onEditTable ) ;
245247 this . eventBus . registerListener ( ERD_EVENTS . CLONE_NODE , this . onCloneNode ) ;
@@ -276,6 +278,9 @@ export default class ERDTool extends React.Component {
276278 [ this . state . preferences . download_image , ( ) => {
277279 this . eventBus . fireEvent ( ERD_EVENTS . DOWNLOAD_IMAGE ) ;
278280 } ] ,
281+ [ this . state . preferences . search_table , ( ) => {
282+ this . eventBus . fireEvent ( ERD_EVENTS . SEARCH_NODE ) ;
283+ } ] ,
279284 [ this . state . preferences . add_table , ( ) => {
280285 this . eventBus . fireEvent ( ERD_EVENTS . ADD_NODE ) ;
281286 } ] ,
@@ -471,12 +476,69 @@ export default class ERDTool extends React.Component {
471476 }
472477 }
473478
479+ scrollToNode ( node ) {
480+ const engine = this . diagram . getEngine ( ) ;
481+ const model = engine . getModel ( ) ;
482+ const container = this . canvasEle ;
483+ if ( ! node || ! container ) return ;
484+
485+ const { x, y } = node . getPosition ( ) ;
486+ const zoom = model . getZoomLevel ( ) / 100 ;
487+ const offsetX = model . getOffsetX ( ) ;
488+ const offsetY = model . getOffsetY ( ) ;
489+
490+ const viewportWidth = container . clientWidth ;
491+ const viewportHeight = container . clientHeight ;
492+
493+ const nodeWidth = node . width ; // Approximate width of a table node
494+ const nodeHeight = node . height ; // Approximate height of a table node
495+
496+ // Node screen bounds
497+ const nodeLeft = x * zoom + offsetX ;
498+ const nodeRight = nodeLeft + nodeWidth * zoom ;
499+ const nodeTop = y * zoom + offsetY ;
500+ const nodeBottom = nodeTop + nodeHeight * zoom ;
501+
502+ let newOffsetX = offsetX ;
503+ let newOffsetY = offsetY ;
504+
505+ // Check horizontal visibility
506+ if ( nodeLeft < 0 ) {
507+ newOffsetX += - nodeLeft + 20 ; // 20px padding
508+ } else if ( nodeRight > viewportWidth ) {
509+ newOffsetX -= nodeRight - viewportWidth + 20 ;
510+ }
511+
512+ // Check vertical visibility
513+ if ( nodeHeight * zoom >= viewportHeight ) {
514+ // Node taller than viewport: snap top of node to top of viewport
515+ newOffsetY = offsetY + viewportHeight / 2 - ( nodeHeight * zoom ) / 2 ;
516+ newOffsetY = offsetY - ( nodeTop - 20 ) ; // aligns top
517+ } else {
518+ // Node fits in viewport: ensure fully visible
519+ if ( nodeTop < 0 ) {
520+ newOffsetY += - nodeTop + 20 ;
521+ } else if ( nodeBottom > viewportHeight ) {
522+ newOffsetY -= nodeBottom - viewportHeight + 20 ;
523+ }
524+ }
525+
526+ // Update offset only if needed
527+ if ( newOffsetX !== offsetX || newOffsetY !== offsetY ) {
528+ model . setOffset ( newOffsetX , newOffsetY ) ;
529+ }
530+
531+ this . diagram . repaint ( ) ;
532+ node . setSelected ( true ) ;
533+ node . fireEvent ( { } , 'highlightFlash' ) ;
534+ } ;
535+
536+
474537 addEditTable ( node ) {
475538 let dialog = this . getDialog ( 'table_dialog' ) ;
476539 if ( node ) {
477- let [ schema , table ] = node . getSchemaTableName ( ) ;
478540 let oldData = node . getData ( ) ;
479- dialog ( gettext ( 'Table: %s (%s) ' , _ . escape ( table ) , _ . escape ( schema ) ) , oldData , false , ( newData ) => {
541+ dialog ( gettext ( 'Table: %s' , node . getDisplayName ( ) ) , oldData , false , ( newData ) => {
480542 if ( this . diagram . anyDuplicateNodeName ( newData , oldData ) ) {
481543 return gettext ( 'Table name already exists' ) ;
482544 }
@@ -543,6 +605,12 @@ export default class ERDTool extends React.Component {
543605 }
544606 }
545607
608+ onSearchNode ( ) {
609+ this . context . showModal ( gettext ( 'Search' ) , ( closeModal ) => (
610+ < SearchNode tableNodes = { this . diagram . getModel ( ) . getNodesDict ( ) } onClose = { closeModal } scrollToNode = { this . scrollToNode } />
611+ ) , { id : 'id-erd-search-node' , showTitle : false , disableRestoreFocus : true } ) ;
612+ }
613+
546614 onAddNewNode ( ) {
547615 this . addEditTable ( ) ;
548616 }
0 commit comments