@@ -6637,6 +6637,27 @@ async function loadFile(filePath: string, createNewTab: boolean = true): Promise
66376637 return ;
66386638 }
66396639
6640+ // Handle image files directly (no backend indexing needed)
6641+ if ( isImageFile ( filePath ) ) {
6642+ if ( state . activeTabId && createNewTab ) {
6643+ saveCurrentTabState ( ) ;
6644+ }
6645+ if ( createNewTab && ! existingTab ) {
6646+ const newTab = createTab ( filePath ) ;
6647+ state . tabs . push ( newTab ) ;
6648+ state . activeTabId = newTab . id ;
6649+ }
6650+ state . filePath = filePath ;
6651+ hideImageViewer ( ) ; // reset first
6652+ elements . markdownPreview . classList . add ( 'hidden' ) ;
6653+ elements . markdownPreview . innerHTML = '' ;
6654+ showImageViewer ( filePath ) ;
6655+ renderTabBar ( ) ;
6656+ renderFolderTree ( ) ;
6657+ updateStatusBar ( ) ;
6658+ return ;
6659+ }
6660+
66406661 showProgress ( 'Indexing file...' ) ;
66416662
66426663 const unsubscribe = window . api . onIndexingProgress ( ( progress ) => {
@@ -6797,6 +6818,7 @@ async function loadFile(filePath: string, createNewTab: boolean = true): Promise
67976818 isMarkdownFile = false ;
67986819 elements . markdownPreview . classList . add ( 'hidden' ) ;
67996820 elements . markdownPreview . innerHTML = '' ;
6821+ hideImageViewer ( ) ;
68006822 elements . btnWordWrap . textContent = 'Wrap' ;
68016823 elements . btnWordWrap . title = 'Toggle word wrap (⌥Z)' ;
68026824 const wrapper = document . querySelector ( '.log-viewer-wrapper' ) as HTMLElement ;
@@ -9307,6 +9329,76 @@ function isMarkdownExtension(filePath: string): boolean {
93079329 return ext === 'md' || ext === 'markdown' ;
93089330}
93099331
9332+ const IMAGE_EXTENSIONS = new Set ( [ 'png' , 'jpg' , 'jpeg' , 'gif' , 'svg' , 'bmp' , 'webp' , 'ico' , 'avif' ] ) ;
9333+
9334+ function isImageFile ( filePath : string ) : boolean {
9335+ const ext = filePath . toLowerCase ( ) . split ( '.' ) . pop ( ) || '' ;
9336+ return IMAGE_EXTENSIONS . has ( ext ) ;
9337+ }
9338+
9339+ // Image viewer state
9340+ let imageZoom = 1 ;
9341+ const imageViewerEl = document . getElementById ( 'image-viewer' ) as HTMLDivElement ;
9342+ const imageViewerContainer = document . getElementById ( 'image-viewer-container' ) as HTMLDivElement ;
9343+ const imageViewerImg = document . getElementById ( 'image-viewer-img' ) as HTMLImageElement ;
9344+ const imageZoomLevelEl = document . getElementById ( 'image-zoom-level' ) as HTMLSpanElement ;
9345+ const imageDimensionsEl = document . getElementById ( 'image-dimensions' ) as HTMLSpanElement ;
9346+
9347+ function showImageViewer ( filePath : string ) : void {
9348+ imageZoom = 1 ;
9349+ imageViewerImg . src = '' ;
9350+ imageViewerEl . classList . remove ( 'hidden' ) ;
9351+
9352+ // Hide log viewer
9353+ const wrapper = document . querySelector ( '.log-viewer-wrapper' ) as HTMLElement ;
9354+ if ( wrapper ) wrapper . style . display = 'none' ;
9355+
9356+ // Load image using file:// protocol
9357+ imageViewerImg . onload = ( ) => {
9358+ imageDimensionsEl . textContent = `${ imageViewerImg . naturalWidth } × ${ imageViewerImg . naturalHeight } ` ;
9359+ fitImageToWindow ( ) ;
9360+ } ;
9361+ imageViewerImg . src = `file://${ filePath } ` ;
9362+ }
9363+
9364+ function hideImageViewer ( ) : void {
9365+ imageViewerEl . classList . add ( 'hidden' ) ;
9366+ imageViewerImg . src = '' ;
9367+ imageDimensionsEl . textContent = '' ;
9368+ const wrapper = document . querySelector ( '.log-viewer-wrapper' ) as HTMLElement ;
9369+ if ( wrapper ) wrapper . style . display = '' ;
9370+ }
9371+
9372+ function setImageZoom ( zoom : number ) : void {
9373+ imageZoom = Math . max ( 0.05 , Math . min ( zoom , 20 ) ) ;
9374+ imageViewerImg . style . width = `${ imageViewerImg . naturalWidth * imageZoom } px` ;
9375+ imageViewerImg . style . height = `${ imageViewerImg . naturalHeight * imageZoom } px` ;
9376+ imageZoomLevelEl . textContent = `${ Math . round ( imageZoom * 100 ) } %` ;
9377+ }
9378+
9379+ function fitImageToWindow ( ) : void {
9380+ const containerW = imageViewerContainer . clientWidth - 40 ;
9381+ const containerH = imageViewerContainer . clientHeight - 40 ;
9382+ const imgW = imageViewerImg . naturalWidth ;
9383+ const imgH = imageViewerImg . naturalHeight ;
9384+ if ( imgW === 0 || imgH === 0 ) return ;
9385+ const scale = Math . min ( containerW / imgW , containerH / imgH , 1 ) ;
9386+ setImageZoom ( scale ) ;
9387+ }
9388+
9389+ // Image viewer controls
9390+ document . getElementById ( 'btn-image-zoom-in' ) ?. addEventListener ( 'click' , ( ) => setImageZoom ( imageZoom * 1.25 ) ) ;
9391+ document . getElementById ( 'btn-image-zoom-out' ) ?. addEventListener ( 'click' , ( ) => setImageZoom ( imageZoom / 1.25 ) ) ;
9392+ document . getElementById ( 'btn-image-zoom-fit' ) ?. addEventListener ( 'click' , fitImageToWindow ) ;
9393+ document . getElementById ( 'btn-image-zoom-actual' ) ?. addEventListener ( 'click' , ( ) => setImageZoom ( 1 ) ) ;
9394+
9395+ // Mouse wheel zoom on image
9396+ imageViewerContainer ?. addEventListener ( 'wheel' , ( e ) => {
9397+ e . preventDefault ( ) ;
9398+ const delta = e . deltaY > 0 ? 0.9 : 1.1 ;
9399+ setImageZoom ( imageZoom * delta ) ;
9400+ } , { passive : false } ) ;
9401+
93109402async function renderMarkdownPreview ( ) : Promise < void > {
93119403 if ( ! state . filePath || ! isMarkdownFile ) return ;
93129404
@@ -11211,23 +11303,30 @@ async function switchToTab(tabId: string): Promise<void> {
1121111303 // Restore tab state
1121211304 restoreTabState ( tab ) ;
1121311305
11214- // Handle markdown preview state for the new tab
11215- isMarkdownFile = isMarkdownExtension ( tab . filePath ) ;
11216- if ( isMarkdownFile ) {
11217- markdownPreviewMode = true ;
11218- elements . btnWordWrap . textContent = 'Raw' ;
11219- elements . btnWordWrap . title = 'Show raw markdown' ;
11220- await renderMarkdownPreview ( ) ;
11221- showMarkdownPreview ( ) ;
11222- } else {
11223- markdownPreviewMode = false ;
11306+ // Handle special file viewers for the new tab
11307+ if ( isImageFile ( tab . filePath ) ) {
1122411308 elements . markdownPreview . classList . add ( 'hidden' ) ;
1122511309 elements . markdownPreview . innerHTML = '' ;
11226- elements . btnWordWrap . textContent = 'Wrap' ;
11227- elements . btnWordWrap . title = 'Toggle word wrap (⌥Z)' ;
11228- const wrapper = document . querySelector ( '.log-viewer-wrapper' ) as HTMLElement ;
11229- if ( wrapper ) {
11230- wrapper . style . display = '' ;
11310+ showImageViewer ( tab . filePath ) ;
11311+ } else {
11312+ hideImageViewer ( ) ;
11313+ isMarkdownFile = isMarkdownExtension ( tab . filePath ) ;
11314+ if ( isMarkdownFile ) {
11315+ markdownPreviewMode = true ;
11316+ elements . btnWordWrap . textContent = 'Raw' ;
11317+ elements . btnWordWrap . title = 'Show raw markdown' ;
11318+ await renderMarkdownPreview ( ) ;
11319+ showMarkdownPreview ( ) ;
11320+ } else {
11321+ markdownPreviewMode = false ;
11322+ elements . markdownPreview . classList . add ( 'hidden' ) ;
11323+ elements . markdownPreview . innerHTML = '' ;
11324+ elements . btnWordWrap . textContent = 'Wrap' ;
11325+ elements . btnWordWrap . title = 'Toggle word wrap (⌥Z)' ;
11326+ const wrapper = document . querySelector ( '.log-viewer-wrapper' ) as HTMLElement ;
11327+ if ( wrapper ) {
11328+ wrapper . style . display = '' ;
11329+ }
1123111330 }
1123211331 }
1123311332
0 commit comments