mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat(renderer): useAppStore extended with modal discriminated union
This commit is contained in:
@@ -7,15 +7,44 @@ export interface PaneSizes {
|
|||||||
preview: number;
|
preview: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConfirmProps {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
confirmLabel?: string;
|
||||||
|
cancelLabel?: string;
|
||||||
|
destructive?: boolean;
|
||||||
|
onConfirm: () => void | Promise<void>;
|
||||||
|
onCancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModalState =
|
||||||
|
| { kind: null }
|
||||||
|
| { kind: 'export-pdf'; props: { sourcePath: string } }
|
||||||
|
| { kind: 'export-docx'; props: { sourcePath: string } }
|
||||||
|
| { kind: 'export-html'; props: { sourcePath: string } }
|
||||||
|
| { kind: 'export-batch'; props: { sourcePaths: string[] } }
|
||||||
|
| { kind: 'settings' }
|
||||||
|
| { kind: 'about' }
|
||||||
|
| { kind: 'welcome' }
|
||||||
|
| { kind: 'confirm'; props: ConfirmProps };
|
||||||
|
|
||||||
|
export type ModalKind = ModalState['kind'];
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
sidebarVisible: boolean;
|
sidebarVisible: boolean;
|
||||||
previewVisible: boolean;
|
previewVisible: boolean;
|
||||||
zenMode: boolean;
|
zenMode: boolean;
|
||||||
paneSizes: PaneSizes;
|
paneSizes: PaneSizes;
|
||||||
|
modal: ModalState;
|
||||||
toggleSidebar: () => void;
|
toggleSidebar: () => void;
|
||||||
togglePreview: () => void;
|
togglePreview: () => void;
|
||||||
setZenMode: (value: boolean) => void;
|
setZenMode: (value: boolean) => void;
|
||||||
setPaneSizes: (sizes: PaneSizes) => void;
|
setPaneSizes: (sizes: PaneSizes) => void;
|
||||||
|
openModal: <K extends NonNullable<ModalKind>>(
|
||||||
|
kind: K,
|
||||||
|
...args: Extract<ModalState, { kind: K }> extends { props: infer P } ? [props?: P] : []
|
||||||
|
) => void;
|
||||||
|
closeModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAppStore = create<AppState>()(
|
export const useAppStore = create<AppState>()(
|
||||||
@@ -25,11 +54,27 @@ export const useAppStore = create<AppState>()(
|
|||||||
previewVisible: true,
|
previewVisible: true,
|
||||||
zenMode: false,
|
zenMode: false,
|
||||||
paneSizes: { sidebar: 20, editor: 50, preview: 30 },
|
paneSizes: { sidebar: 20, editor: 50, preview: 30 },
|
||||||
|
modal: { kind: null },
|
||||||
toggleSidebar: () => set((s) => ({ sidebarVisible: !s.sidebarVisible })),
|
toggleSidebar: () => set((s) => ({ sidebarVisible: !s.sidebarVisible })),
|
||||||
togglePreview: () => set((s) => ({ previewVisible: !s.previewVisible })),
|
togglePreview: () => set((s) => ({ previewVisible: !s.previewVisible })),
|
||||||
setZenMode: (value) => set({ zenMode: value }),
|
setZenMode: (value) => set({ zenMode: value }),
|
||||||
setPaneSizes: (sizes) => set({ paneSizes: sizes }),
|
setPaneSizes: (sizes) => set({ paneSizes: sizes }),
|
||||||
|
openModal: (kind, ...args) =>
|
||||||
|
set(() => {
|
||||||
|
const candidate = { kind, ...(args[0] ? { props: args[0] } : {}) } as ModalState;
|
||||||
|
return { modal: candidate };
|
||||||
}),
|
}),
|
||||||
{ name: 'mc-app-store' }
|
closeModal: () => set({ modal: { kind: null } }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'mc-app-store',
|
||||||
|
partialize: (state) => ({
|
||||||
|
sidebarVisible: state.sidebarVisible,
|
||||||
|
previewVisible: state.previewVisible,
|
||||||
|
zenMode: state.zenMode,
|
||||||
|
paneSizes: state.paneSizes,
|
||||||
|
// modal is intentionally NOT persisted (runtime-only)
|
||||||
|
}),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -33,3 +33,32 @@ describe('useAppStore', () => {
|
|||||||
expect(useAppStore.getState().zenMode).toBe(true);
|
expect(useAppStore.getState().zenMode).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('useAppStore (modal)', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
useAppStore.setState({
|
||||||
|
sidebarVisible: true,
|
||||||
|
previewVisible: true,
|
||||||
|
zenMode: false,
|
||||||
|
paneSizes: { sidebar: 20, editor: 50, preview: 30 },
|
||||||
|
modal: { kind: null },
|
||||||
|
} as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('openModal sets the modal state', () => {
|
||||||
|
useAppStore.getState().openModal('about');
|
||||||
|
expect(useAppStore.getState().modal).toEqual({ kind: 'about' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('openModal with kind requiring props passes them through', () => {
|
||||||
|
useAppStore.getState().openModal('export-pdf', { sourcePath: '/a.md' });
|
||||||
|
expect(useAppStore.getState().modal).toEqual({ kind: 'export-pdf', props: { sourcePath: '/a.md' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closeModal clears the modal state', () => {
|
||||||
|
useAppStore.getState().openModal('about');
|
||||||
|
useAppStore.getState().closeModal();
|
||||||
|
expect(useAppStore.getState().modal).toEqual({ kind: null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user