mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat(renderer): persist last-opened folder (mc-file-store) + restore on mount
This commit is contained in:
@@ -9,9 +9,11 @@ import { Sidebar } from '@/components/sidebar/Sidebar';
|
|||||||
import { useAppStore } from '@/stores/app-store';
|
import { useAppStore } from '@/stores/app-store';
|
||||||
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
|
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
|
||||||
import { useFileShortcuts } from '@/hooks/use-file-shortcuts';
|
import { useFileShortcuts } from '@/hooks/use-file-shortcuts';
|
||||||
|
import { useRestoreLastFolder } from '@/hooks/use-restore-last-folder';
|
||||||
|
|
||||||
export function AppShell() {
|
export function AppShell() {
|
||||||
useFileShortcuts();
|
useFileShortcuts();
|
||||||
|
useRestoreLastFolder();
|
||||||
const { sidebarVisible, previewVisible, paneSizes, setPaneSizes } = useAppStore();
|
const { sidebarVisible, previewVisible, paneSizes, setPaneSizes } = useAppStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFileStore } from '@/stores/file-store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On mount, re-opens the last folder if one was persisted via the file store's
|
||||||
|
* zustand persist middleware. Idempotent — re-running with the same path is a
|
||||||
|
* no-op (openFolder's list IPC call would just re-fetch, which is acceptable).
|
||||||
|
*/
|
||||||
|
export function useRestoreLastFolder(): void {
|
||||||
|
useEffect(() => {
|
||||||
|
const { rootPath, openFolder, tree } = useFileStore.getState();
|
||||||
|
if (rootPath && !tree) {
|
||||||
|
void openFolder(rootPath);
|
||||||
|
}
|
||||||
|
// Run once on mount only. Persist hydration happens before AppShell renders.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||||
import { immer } from 'zustand/middleware/immer';
|
import { immer } from 'zustand/middleware/immer';
|
||||||
import { enableMapSet } from 'immer';
|
import { enableMapSet } from 'immer';
|
||||||
import { ipc } from '@/lib/ipc';
|
import { ipc } from '@/lib/ipc';
|
||||||
@@ -68,6 +69,7 @@ function updateNode(tree: FileNode, dirPath: string, updater: (node: FileNode) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useFileStore = create<FileState>()(
|
export const useFileStore = create<FileState>()(
|
||||||
|
persist(
|
||||||
immer((set) => ({
|
immer((set) => ({
|
||||||
tree: null,
|
tree: null,
|
||||||
rootPath: null,
|
rootPath: null,
|
||||||
@@ -221,5 +223,13 @@ export const useFileStore = create<FileState>()(
|
|||||||
useFileStore.getState().markTabClean(activeTabId);
|
useFileStore.getState().markTabClean(activeTabId);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}))
|
})),
|
||||||
|
{
|
||||||
|
name: 'mc-file-store',
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
|
// Only persist the folder path; tree, expanded Set, and openTabs are
|
||||||
|
// rebuilt on demand. The app re-opens the folder on startup.
|
||||||
|
partialize: (state) => ({ rootPath: state.rootPath }),
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -396,4 +396,24 @@ describe('useFileStore', () => {
|
|||||||
expect(useEditorStore.getState().buffers.get('/root/doc.md')!.dirty).toBe(true);
|
expect(useEditorStore.getState().buffers.get('/root/doc.md')!.dirty).toBe(true);
|
||||||
expect(useFileStore.getState().openTabs[0].dirty).toBe(true);
|
expect(useFileStore.getState().openTabs[0].dirty).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- persistence ---
|
||||||
|
|
||||||
|
it('persists rootPath to localStorage and restores on next mount', async () => {
|
||||||
|
fakeList.mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
data: [{ name: 'a.md', path: '/root/a.md', isDirectory: false }],
|
||||||
|
});
|
||||||
|
await useFileStore.getState().openFolder('/root');
|
||||||
|
|
||||||
|
// Force the persist middleware to flush to localStorage
|
||||||
|
await useFileStore.persist?.flush?.();
|
||||||
|
|
||||||
|
const stored = JSON.parse(localStorage.getItem('mc-file-store') ?? '{}');
|
||||||
|
expect(stored.state.rootPath).toBe('/root');
|
||||||
|
// Ensure non-persisted fields are NOT in storage
|
||||||
|
expect(stored.state.tree).toBeUndefined();
|
||||||
|
expect(stored.state.openTabs).toBeUndefined();
|
||||||
|
expect(stored.state.expanded).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user