diff --git a/src-tauri/src/commands/fs.rs b/src-tauri/src/commands/fs.rs index 846795d..f4bc476 100644 --- a/src-tauri/src/commands/fs.rs +++ b/src-tauri/src/commands/fs.rs @@ -155,7 +155,7 @@ fn read_dir_recursive(dir_path: &Path, max_depth: u32) -> Result, } else if metadata.is_file() { let ext = path.extension().map(|e| e.to_string_lossy().to_lowercase()); - // Include markdown files and common text files + // Include markdown files, common text files, and images let include = matches!( ext.as_deref(), Some("md") @@ -178,6 +178,16 @@ fn read_dir_recursive(dir_path: &Path, max_depth: u32) -> Result, | Some("ts") | Some("xml") | Some("csv") + | Some("png") + | Some("jpg") + | Some("jpeg") + | Some("gif") + | Some("bmp") + | Some("svg") + | Some("webp") + | Some("ico") + | Some("tiff") + | Some("tif") ); if include { diff --git a/src/App.vue b/src/App.vue index f877072..26fbe85 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,7 +5,8 @@
- + +
@@ -45,6 +46,7 @@ import { invoke } from '@tauri-apps/api/core' import TabBar from './components/tabs/TabBar.vue' import Editor from './components/Editor.vue' import SourceEditor from './components/source/SourceEditor.vue' +import ImageViewer from './components/ImageViewer.vue' import Sidebar from './components/sidebar/Sidebar.vue' import OutlinePanel from './components/sidebar/OutlinePanel.vue' import StatusBar from './components/StatusBar.vue' diff --git a/src/components/ImageViewer.vue b/src/components/ImageViewer.vue new file mode 100644 index 0000000..dc82fb3 --- /dev/null +++ b/src/components/ImageViewer.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/components/sidebar/Sidebar.vue b/src/components/sidebar/Sidebar.vue index 6887abe..e78bf4a 100644 --- a/src/components/sidebar/Sidebar.vue +++ b/src/components/sidebar/Sidebar.vue @@ -10,6 +10,26 @@ const sidebar = useSidebarStore() const tabs = useTabsStore() const recentFiles = useRecentFilesStore() +/** Image file extensions that should open with the system viewer instead of as a tab */ +const IMAGE_EXTENSIONS = new Set([ + 'png', + 'jpg', + 'jpeg', + 'gif', + 'bmp', + 'svg', + 'webp', + 'ico', + 'tiff', + 'tif', +]) + +/** Check if a file path is an image based on its extension */ +function isImageFile(path: string): boolean { + const ext = path.split('.').pop()?.toLowerCase() ?? '' + return IMAGE_EXTENSIONS.has(ext) +} + /** The currently selected file path (from the active tab) */ const selectedFilePath = computed(() => { return tabs.activeTab?.filePath ?? null @@ -17,6 +37,13 @@ const selectedFilePath = computed(() => { /** Handle file selection in the tree — reads file from disk and opens in tab */ async function handleFileSelect(path: string) { + // Image files open in the built-in image viewer tab + if (isImageFile(path)) { + tabs.openImageFile(path) + recentFiles.addRecentFile(path) + return + } + // openFile handles deduplication: switches to existing tab or reads from disk const tab = await tabs.openFile(path) if (tab) { diff --git a/src/stores/tabs.ts b/src/stores/tabs.ts index 2fe397d..a3d9a16 100644 --- a/src/stores/tabs.ts +++ b/src/stores/tabs.ts @@ -51,6 +51,7 @@ export const useTabsStore = defineStore('tabs', () => { filePath, isModified: false, isUntitled, + isImage: false, editorState: createDefaultEditorState(content), } @@ -268,6 +269,32 @@ export const useTabsStore = defineStore('tabs', () => { } } + /** + * Open an image file in a preview tab (no text content loaded). + */ + function openImageFile(filePath: string): Tab { + // Check for existing tab + const existing = tabs.value.find((t) => t.filePath === filePath) + if (existing) { + activeTabId.value = existing.id + return existing + } + + const tab: Tab = { + id: generateId(), + title: fileNameFromPath(filePath), + filePath, + isModified: false, + isUntitled: false, + isImage: true, + editorState: createDefaultEditorState(), + } + + tabs.value.push(tab) + activeTabId.value = tab.id + return tab + } + /** * Show a native file picker dialog and open the selected file. */ @@ -305,6 +332,7 @@ export const useTabsStore = defineStore('tabs', () => { nextTab, previousTab, openFile, + openImageFile, openFileDialog, } }) diff --git a/src/types/tab.ts b/src/types/tab.ts index 4a662e8..df8bf4f 100644 --- a/src/types/tab.ts +++ b/src/types/tab.ts @@ -26,6 +26,8 @@ export interface Tab { isModified: boolean /** Whether the tab has never been saved */ isUntitled: boolean + /** Whether this tab displays an image file (not a text editor) */ + isImage: boolean /** Persisted editor state for this tab */ editorState: EditorState }