From 75ac03ea22a4ecee74fdca0be82101f1369510a8 Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Mon, 2 Mar 2026 12:13:59 -0600 Subject: [PATCH] refactor: preview layout --- src/components/AppLayout/FocusModePanel.tsx | 18 +++++-- .../export/ExportDialog/ExportDialog.tsx | 12 ++--- .../export/ExportDialog/ExportOptions.tsx | 12 ++++- src/components/export/MarkdownPdfDocument.tsx | 20 ++++++-- src/components/export/preview/PdfPreview.tsx | 48 +++++++++++++++--- src/components/export/preview/TextPreview.tsx | 49 +++++++++++++++---- src/pdf/constants.ts | 1 + src/pdf/types.ts | 1 + src/state/selectors.ts | 1 + src/state/stores/ui.ts | 2 + src/state/types.ts | 1 + 11 files changed, 133 insertions(+), 32 deletions(-) diff --git a/src/components/AppLayout/FocusModePanel.tsx b/src/components/AppLayout/FocusModePanel.tsx index d4ddf8f..80c40d4 100644 --- a/src/components/AppLayout/FocusModePanel.tsx +++ b/src/components/AppLayout/FocusModePanel.tsx @@ -10,8 +10,9 @@ import { FocusIcon } from "$icons"; import { useFocusModePanelState, useHelpSheetState } from "$state/selectors"; import type { SaveStatus } from "$types"; import { formatShortcut } from "$utils/shortcuts"; +import * as logger from "@tauri-apps/plugin-log"; import { AnimatePresence, motion } from "motion/react"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; type FocusModePanelProps = { editor: Pick; @@ -68,7 +69,18 @@ export function FocusModePanel({ editor, statusBar, saveStatus, hasActiveDocumen const handleOpenHelp = useCallback(() => { setHelpSheetOpen(true); }, [setHelpSheetOpen]); - const container = useMemo(() => ({ className: "flex-1 max-w-3xl mx-auto w-full pt-3", style: {} }), []); + const container = useMemo( + () => ({ className: "flex min-h-0 flex-1 max-w-3xl mx-auto w-full overflow-hidden pt-3", style: {} }), + [], + ); + + useEffect(() => { + void logger.debug( + `Focus mode layout ready: hasActiveDocument=${String(hasActiveDocument)}, statusBarCollapsed=${ + String(statusBarCollapsed) + }`, + ); + }, [hasActiveDocument, statusBarCollapsed]); return ( @@ -77,7 +89,7 @@ export function FocusModePanel({ editor, statusBar, saveStatus, hasActiveDocumen animate={FOCUS.ANIMATE} exit={FOCUS.EXIT} transition={transition} - className="fixed inset-0 z-50 flex flex-col bg-surface-primary"> + className="fixed inset-0 z-50 flex min-h-0 flex-col bg-surface-primary"> ( -
+
); const OptionsPane = ({ isFullWidth }: { isFullWidth: boolean }) => ( -
+
); @@ -138,7 +138,7 @@ function PdfExportContent(
{showPreview ? @@ -164,7 +164,7 @@ const TextExportInfo = ({ handleClick }: { handleClick: () => Promise }) = const { isExportingText } = useTextExportState(); return ( -
+

Export Plain Text

Strips Markdown syntax while keeping readable paragraph and list structure. @@ -199,11 +199,11 @@ function TextExportContent(

{showPreview ? ( -
+
) diff --git a/src/components/export/ExportDialog/ExportOptions.tsx b/src/components/export/ExportDialog/ExportOptions.tsx index b978699..5ef4a00 100644 --- a/src/components/export/ExportDialog/ExportOptions.tsx +++ b/src/components/export/ExportDialog/ExportOptions.tsx @@ -127,7 +127,11 @@ function PdfExportDialogMargins() { } function PdfExportDialogHeaderFooter() { - const { setIncludeHeader, setIncludeFooter, options } = usePdfDialogUiState(); + const { setIncludeTitle, setIncludeHeader, setIncludeFooter, options } = usePdfDialogUiState(); + + const onTitleChange = useCallback((event: React.ChangeEvent) => { + setIncludeTitle(event.target.checked); + }, [setIncludeTitle]); const onHeaderChange = useCallback((event: React.ChangeEvent) => { setIncludeHeader(event.target.checked); @@ -139,7 +143,11 @@ function PdfExportDialogHeaderFooter() { return ( -
+
+
); -const PreviewSuccess = ({ result }: { result: TextExportResult }) => ( -
-
- Preview - {result.word_count} words -
-
-
{result.text}
+const PreviewSuccess = ({ result }: { result: TextExportResult }) => { + const [copyState, setCopyState] = useState<"idle" | "copied" | "error">("idle"); + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(result.text); + setCopyState("copied"); + setTimeout(() => setCopyState("idle"), 1200); + } catch (error) { + setCopyState("error"); + void logger.warn(`Failed to copy text export preview: ${error instanceof Error ? error.message : String(error)}`); + setTimeout(() => setCopyState("idle"), 1600); + } + }, [result.text]); + + return ( +
+
+ Preview +
+ {result.word_count} words + +
+
+
+
{result.text}
+
-
-); + ); +}; export function TextPreviewPanel({ locationId, relPath, text }: TextPreviewPanelProps) { const previewState = useTextPreview({ locationId, relPath, text }); diff --git a/src/pdf/constants.ts b/src/pdf/constants.ts index ac25ebe..3709f16 100644 --- a/src/pdf/constants.ts +++ b/src/pdf/constants.ts @@ -18,6 +18,7 @@ export const DEFAULT_OPTIONS: PdfExportOptions = { orientation: "portrait", margins: { top: 50, right: 50, bottom: 50, left: 50 }, fontSize: 11, + includeTitle: false, includeHeader: false, includeFooter: false, }; diff --git a/src/pdf/types.ts b/src/pdf/types.ts index b3102fb..581ab81 100644 --- a/src/pdf/types.ts +++ b/src/pdf/types.ts @@ -16,6 +16,7 @@ export type PdfExportOptions = { margins: Margins; fontSize?: number; lineHeight?: number; + includeTitle?: boolean; includeHeader?: boolean; includeFooter?: boolean; }; diff --git a/src/state/selectors.ts b/src/state/selectors.ts index 942914b..552fd0d 100644 --- a/src/state/selectors.ts +++ b/src/state/selectors.ts @@ -269,6 +269,7 @@ export const usePdfDialogUiState = () => setOrientation: state.setPdfOrientation, setFontSize: state.setPdfFontSize, setMargin: state.setPdfMargin, + setIncludeTitle: state.setPdfIncludeTitle, setIncludeHeader: state.setPdfIncludeHeader, setIncludeFooter: state.setPdfIncludeFooter, })), diff --git a/src/state/stores/ui.ts b/src/state/stores/ui.ts index 20a07ac..390b0b2 100644 --- a/src/state/stores/ui.ts +++ b/src/state/stores/ui.ts @@ -48,6 +48,8 @@ export const useUiStore = create()((set, get) => ({ margins: { ...state.pdfExportOptions.margins, [side]: Number.isNaN(value) ? 0 : value }, }, })), + setPdfIncludeTitle: (value) => + set((state) => ({ pdfExportOptions: { ...state.pdfExportOptions, includeTitle: value } })), setPdfIncludeHeader: (value) => set((state) => ({ pdfExportOptions: { ...state.pdfExportOptions, includeHeader: value } })), setPdfIncludeFooter: (value) => diff --git a/src/state/types.ts b/src/state/types.ts index 91341f2..5def586 100644 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -232,6 +232,7 @@ export type UiActions = { setPdfOrientation: (value: Orientation) => void; setPdfFontSize: (value: number) => void; setPdfMargin: (side: MarginSide, value: number) => void; + setPdfIncludeTitle: (value: boolean) => void; setPdfIncludeHeader: (value: boolean) => void; setPdfIncludeFooter: (value: boolean) => void; setGlobalCaptureSettings: (value: GlobalCaptureSettings) => void; -- 2.51.2