feat: transfer tab state to popout window and close tab in main window
- Restore transfer-tab-data IPC for transferring tab state - Create usePopOutTab hook to receive tab data in new window - Update handlePopOut to transfer data and close tab in main window - Add PopOutTabData interface for type safety
This commit is contained in:
@@ -491,6 +491,20 @@ ipcMain.handle('create-window', async (_event, tabData: { route: string; title:
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('transfer-tab-data', async (_event, windowId: number, tabData: any) => {
|
||||
try {
|
||||
const win = electronState.getWindow(windowId);
|
||||
if (!win) {
|
||||
return { success: false, error: 'Window not found' };
|
||||
}
|
||||
win.webContents.send('tab-data-received', tabData);
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
log.error('[PopOut] Failed to transfer tab data:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('window-minimize', async (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
|
||||
@@ -54,6 +54,12 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
terminalGetStatus: () => ipcRenderer.invoke('terminal-get-status'),
|
||||
terminalGetPort: () => ipcRenderer.invoke('terminal-get-port'),
|
||||
createWindow: (tabData: { route: string; title: string }) => ipcRenderer.invoke('create-window', tabData),
|
||||
transferTabData: (windowId: number, tabData: any) => ipcRenderer.invoke('transfer-tab-data', windowId, tabData),
|
||||
onTabDataReceived: (callback: (tabData: any) => void) => {
|
||||
const handler = (_event: Electron.IpcRendererEvent, tabData: any) => callback(tabData);
|
||||
ipcRenderer.on('tab-data-received', handler);
|
||||
return () => ipcRenderer.removeListener('tab-data-received', handler);
|
||||
},
|
||||
windowMinimize: () => ipcRenderer.invoke('window-minimize'),
|
||||
windowMaximize: () => ipcRenderer.invoke('window-maximize'),
|
||||
windowClose: () => ipcRenderer.invoke('window-close'),
|
||||
|
||||
@@ -3,6 +3,7 @@ export { useDialogState, useErrorDialogState } from './useDialogState'
|
||||
export { useNoteBrowser, type UseNoteBrowserReturn } from './useNoteBrowser'
|
||||
export { useNoteContent } from './useNoteContent'
|
||||
export { useAutoLoadTabContent } from './useAutoLoadTabContent'
|
||||
export { usePopOutTab } from './usePopOutTab'
|
||||
|
||||
export { useFileSystemController } from './useFileSystemController'
|
||||
export { useFileTabs } from './useFileTabs'
|
||||
|
||||
58
src/hooks/domain/usePopOutTab.ts
Normal file
58
src/hooks/domain/usePopOutTab.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useTabStore, TabState } from '@/stores'
|
||||
|
||||
export interface PopOutTabData {
|
||||
file: {
|
||||
name: string
|
||||
path: string
|
||||
type: 'file' | 'dir'
|
||||
size: number
|
||||
modified: string
|
||||
}
|
||||
content: string
|
||||
unsavedContent: string
|
||||
isEditing: boolean
|
||||
loading: boolean
|
||||
loaded: boolean
|
||||
}
|
||||
|
||||
export function usePopOutTab() {
|
||||
const { selectFile } = useTabStore()
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electronAPI?.onTabDataReceived((tabData: PopOutTabData) => {
|
||||
console.log('[PopOut] Received tab data:', tabData)
|
||||
|
||||
if (!tabData?.file?.path) {
|
||||
console.error('[PopOut] Invalid tab data received')
|
||||
return
|
||||
}
|
||||
|
||||
const { file, content, unsavedContent, isEditing, loading, loaded } = tabData
|
||||
|
||||
const newTab: TabState = {
|
||||
file: file as any,
|
||||
content: content || '',
|
||||
unsavedContent: unsavedContent || content || '',
|
||||
isEditing: isEditing || false,
|
||||
loading: loading || false,
|
||||
loaded: loaded !== undefined ? loaded : true,
|
||||
}
|
||||
|
||||
useTabStore.setState((state) => {
|
||||
const newTabs = new Map(state.tabs)
|
||||
newTabs.set(file.path, newTab)
|
||||
return {
|
||||
tabs: newTabs,
|
||||
activeTabId: file.path,
|
||||
}
|
||||
})
|
||||
|
||||
selectFile(file as any)
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubscribe?.()
|
||||
}
|
||||
}, [selectFile])
|
||||
}
|
||||
@@ -119,9 +119,29 @@ export const NoteBrowser = () => {
|
||||
}, [selectFile])
|
||||
|
||||
const handlePopOut = useCallback(async (file: FileItem) => {
|
||||
const tabState = tabs.get(file.path)
|
||||
if (!tabState) return
|
||||
|
||||
const tabData = {
|
||||
file: file,
|
||||
content: tabState.content,
|
||||
unsavedContent: tabState.unsavedContent,
|
||||
isEditing: tabState.isEditing,
|
||||
loading: tabState.loading,
|
||||
loaded: tabState.loaded,
|
||||
}
|
||||
|
||||
const route = `/popout?path=${encodeURIComponent(file.path)}&name=${encodeURIComponent(file.name)}`
|
||||
await window.electronAPI?.createWindow({ route, title: file.name })
|
||||
}, [])
|
||||
const result = await window.electronAPI?.createWindow({ route, title: file.name })
|
||||
|
||||
if (result?.success && result.windowId) {
|
||||
setTimeout(() => {
|
||||
window.electronAPI?.transferTabData(result.windowId!, tabData)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
closeFile(file)
|
||||
}, [tabs, closeFile])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMarkdown && showTOC) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MarkdownTabPage } from '@/components/tabs/MarkdownTabPage'
|
||||
import { RemoteTabPage } from '@/modules/remote/RemoteTabPage'
|
||||
import { FileTransferPage } from '@/modules/remote/components/file-transfer/FileTransferPage'
|
||||
import { Minus, Square, X, Maximize2 } from 'lucide-react'
|
||||
import { usePopOutTab } from '@/hooks/domain/usePopOutTab'
|
||||
|
||||
export const PopoutPage = () => {
|
||||
const [searchParams] = useSearchParams()
|
||||
@@ -13,6 +14,8 @@ export const PopoutPage = () => {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isMaximized, setIsMaximized] = useState(false)
|
||||
|
||||
usePopOutTab()
|
||||
|
||||
useEffect(() => {
|
||||
const path = searchParams.get('path')
|
||||
const name = searchParams.get('name')
|
||||
|
||||
2
src/types/electron.d.ts
vendored
2
src/types/electron.d.ts
vendored
@@ -54,6 +54,8 @@ export interface ElectronAPI {
|
||||
terminalGetStatus: () => Promise<{ running: boolean; port: number }>
|
||||
terminalGetPort: () => Promise<{ port: number }>
|
||||
createWindow: (tabData: { route: string; title: string }) => Promise<{ success: boolean; windowId?: number; error?: string }>
|
||||
transferTabData: (windowId: number, tabData: any) => Promise<{ success: boolean; error?: string }>
|
||||
onTabDataReceived: (callback: (tabData: any) => void) => () => void
|
||||
windowMinimize: () => Promise<{ success: boolean }>
|
||||
windowMaximize: () => Promise<{ success: boolean; isMaximized?: boolean }>
|
||||
windowClose: () => Promise<{ success: boolean }>
|
||||
|
||||
Reference in New Issue
Block a user