feat(renderer): Phase 9 foundation — settings, modal union, ipc, commands, docx-export

This commit is contained in:
2026-06-06 07:33:00 +05:30
parent 094b52a2b3
commit a8f7547c7e
10 changed files with 161 additions and 6 deletions
@@ -1,12 +1,16 @@
import { useAppStore } from '@/stores/app-store';
import { AboutDialog } from './AboutDialog';
import { AsciiGeneratorDialog } from './AsciiGeneratorDialog';
import { ConfirmDialog } from './ConfirmDialog';
import { ExportBatchDialog } from './ExportBatchDialog';
import { ExportDocxDialog } from './ExportDocxDialog';
import { ExportHtmlDialog } from './ExportHtmlDialog';
import { ExportPdfDialog } from './ExportPdfDialog';
import { FindInFilesDialog } from './FindInFilesDialog';
import { SettingsSheet } from './SettingsSheet';
import { TableGeneratorDialog } from './TableGeneratorDialog';
import { WelcomeDialog } from './WelcomeDialog';
import { WordExportDialog } from './WordExportDialog';
export function ModalLayer() {
const modal = useAppStore((s) => s.modal);
@@ -29,5 +33,13 @@ export function ModalLayer() {
return <WelcomeDialog />;
case 'confirm':
return <ConfirmDialog {...modal.props} />;
case 'export-word':
return <WordExportDialog sourcePath={modal.props.sourcePath} />;
case 'ascii-generator':
return <AsciiGeneratorDialog />;
case 'table-generator':
return <TableGeneratorDialog />;
case 'find-in-files':
return <FindInFilesDialog />;
}
}
@@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { useCommandStore } from '@/stores/command-store';
import { useFileStore } from '@/stores/file-store';
import { useAppStore } from '@/stores/app-store';
import { useSettingsStore } from '@/stores/settings-store';
import { useMenuAction } from '@/hooks/use-menu-action';
/**
@@ -45,6 +46,28 @@ export function registerMenuCommands(): void {
'app.quit': () => {
/* stub — wired in later phase */
},
'tools.ascii': () => useAppStore.getState().openModal('ascii-generator'),
'tools.table': () => useAppStore.getState().openModal('table-generator'),
'tools.findInFiles': () => useAppStore.getState().openModal('find-in-files'),
'tools.exportWord': () => {
const activeTabId = useFileStore.getState().activeTabId;
if (!activeTabId) return;
useAppStore.getState().openModal('export-word', { sourcePath: activeTabId });
},
'tools.repl': () => {
const current = useSettingsStore.getState().replOpen;
useSettingsStore.getState().setSetting('replOpen', !current);
},
'view.zenMode': () => {
const current = useAppStore.getState().zenMode;
useAppStore.getState().setZenMode(!current);
},
'file.print': () => {
/* stub — placeholder for print feature */
},
'git.refresh': () => {
/* stub — actual refresh is a useEffect in GitStatusPanel */
},
});
const { register } = useCommandStore.getState();
+31
View File
@@ -0,0 +1,31 @@
import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } from 'docx';
import { applyAsciiTransform } from './ascii-table';
export interface DocxOptions {
source: string;
title?: string;
customTemplatePath?: string | null; // for future use
}
export async function generateDocx(options: DocxOptions): Promise<Blob> {
// Apply ASCII transform (convert markdown tables to preformatted)
const transformed = applyAsciiTransform(options.source);
// Parse the source into a list of Paragraphs
// For v1, simple line-by-line: each line is a paragraph
// Headings (# ## ###) get heading styles
const lines = transformed.split('\n');
const children = lines.map((line) => {
if (line.startsWith('### ')) return new Paragraph({ text: line.slice(4), heading: HeadingLevel.HEADING_3 });
if (line.startsWith('## ')) return new Paragraph({ text: line.slice(3), heading: HeadingLevel.HEADING_2 });
if (line.startsWith('# ')) return new Paragraph({ text: line.slice(2), heading: HeadingLevel.HEADING_1 });
if (line.startsWith('```')) return new Paragraph({ text: line, alignment: AlignmentType.CENTER });
return new Paragraph({ children: [new TextRun(line)] });
});
const doc = new Document({
sections: [{ children }],
});
return await Packer.toBlob(doc);
}
+8
View File
@@ -76,7 +76,15 @@ export const ipc = {
}
return window.electronAPI.file.onChange(cb);
},
search: (args: { rootPath: string; query: string; isRegex: boolean; caseSensitive: boolean }): Promise<IpcResult<Array<{ filePath: string; line: number; content: string }> | ChannelMissing>> =>
safeCall('file', 'search', args),
gitStatus: (args: { rootPath: string }): Promise<IpcResult<Array<{ filePath: string; status: 'modified' | 'added' | 'deleted' | 'untracked' }> | ChannelMissing>> =>
safeCall('file', 'gitStatus', args),
writeBuffer: (args: { path: string; buffer: Uint8Array }): Promise<IpcResult<void | ChannelMissing>> =>
safeCall('file', 'writeBuffer', args),
},
print: (args: { html: string }): Promise<IpcResult<void | ChannelMissing>> =>
safeCall('print', 'show', args),
export: {
pdf: (opts: PdfOptions): Promise<IpcResult<ExportResult | ChannelMissing>> =>
safeCall('export', 'pdf', opts),
+3
View File
@@ -13,6 +13,9 @@ export const settingsSchema = z.object({
pdfMargins: z.enum(['normal', 'narrow', 'wide']).default('normal'),
pdfEmbedFonts: z.boolean().default(true),
docxTemplate: z.enum(['standard', 'minimal', 'modern']).default('standard'),
docxCustomTemplatePath: z.string().nullable().default(null),
replOpen: z.boolean().default(false),
breadcrumbSymbols: z.boolean().default(true),
htmlHighlightStyle: z.enum(['github', 'monokai', 'nord', 'none']).default('github'),
renderTablesAsAscii: z.boolean().default(false),
welcomeDismissed: z.boolean().default(false),
+5 -1
View File
@@ -26,7 +26,11 @@ export type ModalState =
| { kind: 'settings' }
| { kind: 'about' }
| { kind: 'welcome' }
| { kind: 'confirm'; props: ConfirmProps };
| { kind: 'confirm'; props: ConfirmProps }
| { kind: 'export-word'; props: { sourcePath: string } }
| { kind: 'ascii-generator' }
| { kind: 'table-generator' }
| { kind: 'find-in-files' };
export type ModalKind = ModalState['kind'];