Conversation
WalkthroughThese changes introduce a comprehensive, feature-rich PDF viewer to the application. The update spans backend session and protocol management, utility refactoring, and a significant expansion of the renderer with a modular PDF viewer interface. The viewer includes a toolbar, sidebar with tabs (thumbnails, outline, attachments), annotation tools, search, zoom, and document info modals. Supporting modules and UI libraries are added, and settings are extended for experimental features. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant App
participant PDFViewerApp
participant Toolbar
participant Thumbsbar
participant PDFSlick (PDF engine)
User->>App: Navigates to flow://pdf-viewer?url=...
App->>PDFViewerApp: Renders with pdfFilePath=url
PDFViewerApp->>PDFSlick: Loads PDF (with progress)
PDFSlick-->>PDFViewerApp: Loading progress updates
PDFSlick-->>PDFViewerApp: PDF loaded
PDFViewerApp->>Thumbsbar: Open sidebar (on load)
User->>Toolbar: Interacts (zoom, page nav, annotation, search, info)
Toolbar->>PDFSlick: Dispatches actions/events
User->>Thumbsbar: Switches tabs, selects page/outline/attachment
Thumbsbar->>PDFSlick: Navigates or downloads content
PDFSlick-->>PDFViewerApp: Updates state
PDFViewerApp-->>App: Rerenders with updated state
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. Note ⚡️ Faster reviews with cachingCodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Build is in progress... 🚀Currently building for this pull request! |
There was a problem hiding this comment.
Actionable comments posted: 27
🧹 Nitpick comments (38)
package.json (2)
54-56: Review new UI devDependencies
The@headlessui/reactand@radix-ui/react-alert-dialoglibraries provide accessible UI components for the viewer toolbar and dialogs. Verify that these are tree-shakeable in your production build and that their bundle size impact is acceptable.
74-75: Inspect D3 modules and type definitions
The D3 packages (d3-drag,d3-selection) and their corresponding type definitions are introduced for interactive sidebar resizing in the PDF viewer. Confirm that only the needed D3 sub-modules are bundled (e.g., using selective imports) to minimize bundle size.Also applies to: 83-84
src/main/modules/utils.ts (1)
70-77: Function duplication with existingwaitFor.The newly added
sleepfunction duplicates the functionality of the existingwaitForfunction (lines 33-35). Consider consolidating these functions to avoid maintaining two identical utilities with different names.You could either:
- Use
waitForeverywhere and removesleep- Refactor
waitForto usesleepinternally:export function waitFor(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); + return sleep(ms); }src/renderer/src/routes/pdf-viewer/page.tsx (1)
25-33: Consider adding loading states and error boundaries.The App component would benefit from loading state management and error boundaries to handle PDF loading failures gracefully.
Wrap the
Pagecomponent with an error boundary:+import { ErrorBoundary } from 'react-error-boundary'; function App() { const ThemeProviderOrFragment = THEME_PROVIDER_ENABLED ? ThemeProvider : Fragment; return ( <ThemeProviderOrFragment> + <ErrorBoundary fallback={<div className="p-4">Something went wrong loading the PDF viewer.</div>}> <Page /> + </ErrorBoundary> </ThemeProviderOrFragment> ); }src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfo.tsx (2)
22-34: Improve accessibility of the document info button.The button lacks appropriate ARIA attributes for screen readers. Also, consider memoizing the click handler.
Enhance the button with accessibility attributes and optimize the event handler:
+ const handleOpenModal = useCallback(() => { + openModal(); + }, []); return ( <> <button className={`enabled:hover:bg-slate-200 enabled:hover:text-black text-slate-500 disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent`} - onClick={() => { - openModal(); - }} + onClick={handleOpenModal} + aria-label="Document Properties" + title="Document Properties" > <VscInfo className="w-4 h-4" /> <Tooltip position="bottom" alignX="right"> <p className="whitespace-nowrap">Document Properties</p> </Tooltip> </button> <DocumentInfoModal {...{ usePDFSlickStore, isOpen, closeModal }} /> </> );
24-34: Consider extracting the button styles to a shared class.The long inline class string could be refactored into a shared class or component for better maintainability.
Extract the button styles to a shared class or component:
+// In a shared styles file or component +const toolbarButtonClass = `enabled:hover:bg-slate-200 enabled:hover:text-black text-slate-500 disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent`; // In this component return ( <> <button - className={`enabled:hover:bg-slate-200 enabled:hover:text-black text-slate-500 disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent`} + className={toolbarButtonClass} onClick={() => { openModal(); }} >src/renderer/src/routes/pdf-viewer/pdf-viewer/colors.ts (1)
1-211: Consider extracting this color palette to a shared location.This comprehensive color palette appears to be a reusable resource that could benefit other parts of the application beyond the PDF viewer. Consider moving it to a shared UI utilities directory to promote reuse across the application.
Additionally, adding documentation comments explaining the color system's purpose and how to use it would make it easier for other developers to understand and utilize these colors correctly.
src/main/browser/sessions.ts (1)
101-101: Document why a 50ms sleep is necessary.The code includes a 50ms delay before resolving the promise, but it's unclear why this specific timeout value is needed. Adding a comment explaining the purpose of this delay would help future developers understand why it's there and whether it can be safely modified.
- // wait for 50 ms before returning + // A small delay is needed to ensure all session initialization is complete + // before other components that depend on the session can use itsrc/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Outline.tsx (2)
23-28: Use a more accessible approach for hiding checkbox inputs.Positioning elements thousands of pixels off-screen is a non-standard approach and may cause accessibility issues. Consider using the
sr-onlyclass from Tailwind orvisually-hiddenpattern.- className="peer absolute -top-[10000px] -left-[10000px]" + className="peer sr-only"
61-71: Add empty state handling and memoization for better performance.The component doesn't handle the case when there's no outline data, and it could benefit from memoization to prevent unnecessary re-renders.
-const Outline = ({ usePDFSlickStore, show }: OutlineProps) => { +import { useMemo } from "react"; + +const Outline = ({ usePDFSlickStore, show }: OutlineProps) => { const documentOutline = usePDFSlickStore((s) => s.documentOutline); const pdfSlick = usePDFSlickStore((s) => s.pdfSlick); + + const outlineContent = useMemo(() => { + if (!documentOutline || documentOutline.length === 0) { + return ( + <div className="p-4 text-center text-slate-500"> + No outline available for this document. + </div> + ); + } + + return renderOutlineItems({ outline: documentOutline, pdfSlick }); + }, [documentOutline, pdfSlick]); return ( <div className={clsx("overflow-auto absolute inset-0", { invisible: !show })}> <div className="p-2 pl-0.5 text-slate-700 text-sm"> - {renderOutlineItems({ outline: documentOutline, pdfSlick })} + {outlineContent} </div> </div> ); };src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Attachments.tsx (1)
31-43: Add empty state handling and improve accessibility.The component doesn't handle the case when there are no attachments, which would provide a better user experience.
const Attachments = ({ usePDFSlickStore, show }: AttachmentsProps) => { const attachments = usePDFSlickStore((s) => s.attachments); + const hasAttachments = attachments.size > 0; return ( <div className={clsx("overflow-auto absolute inset-0", { invisible: !show })}> <div className="p-2 text-slate-700 text-sm"> - {Array.from(attachments.entries()).map(([key, { filename, content }]) => ( - <AttachmentButton key={key} {...{ usePDFSlickStore, filename, content }} /> - ))} + {hasAttachments ? ( + Array.from(attachments.entries()).map(([key, { filename, content }]) => ( + <AttachmentButton key={key} {...{ usePDFSlickStore, filename, content }} /> + )) + ) : ( + <div className="p-4 text-center text-slate-500"> + No attachments found in this document. + </div> + )} </div> </div> ); };src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/SearchBar.tsx (5)
17-17: Consider renaming state setter for consistencyThe state variable is
termbut you're usingsetTermsfor the setter. For consistency, consider renaming tosetTerm.- const [term, setTerms] = useState(""); + const [term, setTerm] = useState("");
77-89: Add debouncing for search-as-you-typeThe search is triggered on every input change, which can cause performance issues in large PDF documents. Consider implementing debouncing to reduce the number of search operations.
+ import { useCallback } from "react"; + import debounce from "lodash/debounce"; // Inside component, before return: + const debouncedSearch = useCallback( + debounce((query) => { + pdfSlick?.eventBus.dispatch("find", { + type: "", + query, + caseSensitive, + highlightAll, + entireWord + }); + }, 300), + [pdfSlick, caseSensitive, highlightAll, entireWord] + ); // Then in the onInput handler: onInput={(e) => { const query = (e.target as HTMLInputElement).value; - pdfSlick?.eventBus.dispatch("find", { - type: "", - query, - caseSensitive, - highlightAll, - entireWord - }); + debouncedSearch(query); - setTerms(query); + setTerm(query); }}
21-35: Add support for F3 and Shift+F3 keyboard shortcutsThe component handles Cmd+F to open, but doesn't support common shortcuts like F3 or Shift+F3 for cycling through search results. Consider adding these for better user experience.
const handleEscape = (e: KeyboardEvent) => { let handled = false; if (e.key === "Escape") { setIsOpen(false); } else if (e.key === "f" && e.metaKey) { handled = true; setIsOpen(true); + } else if (e.key === "F3") { + handled = true; + if (term) { + pdfSlick?.eventBus.dispatch("find", { + type: "again", + query: term, + caseSensitive, + highlightAll, + entireWord, + findPrevious: e.shiftKey + }); + } } if (handled) { e.preventDefault(); return; } };
88-88: Update function name to match state variableFor consistency with the state variable name (
term), usesetTerminstead ofsetTerms.- setTerms(query); + setTerm(query);
54-64: Add ARIA attributes to improve accessibilityThe search button lacks proper ARIA attributes for better screen reader support.
<button onClick={() => setIsOpen(!isOpen)} + aria-label="Search document" + aria-expanded={isOpen} + aria-controls="search-panel" className={`enabled:hover:bg-slate-200 enabled:hover:text-black disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent ${ isOpen ? "bg-slate-200 text-black" : "text-slate-500" }`} > <VscSearch className="h-4 w-4" /> </button> {isOpen && ( <div + id="search-panel" className="absolute top-full mt-2 left-0 w-fit">src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx (4)
74-82: Remove unnecessary preventDefault callsThe
e.preventDefault()calls in button click handlers are unnecessary since these are button click events, not form submissions.onClick={() => pdfSlick?.requestPresentationMode()}For the rotation handlers:
onClick={(e) => { - e.preventDefault(); pdfSlick?.setRotation(pagesRotation + 90); }}onClick={(e) => { - e.preventDefault(); pdfSlick?.setRotation(pagesRotation - 90); }}Also applies to: 174-177, 191-194
30-40: Consider adding error handling for Zustand store operationsThe component accesses multiple properties from the Zustand store but doesn't handle cases where these might be undefined. Consider adding error handling or fallbacks.
const { pdfSlick, pagesRotation, pageNumber, numPages, scrollMode, spreadMode } = usePDFSlickStore( (s) => ({ pdfSlick: s.pdfSlick, pagesRotation: s.pagesRotation ?? 0, pageNumber: s.pageNumber ?? 1, numPages: s.numPages ?? 1, scrollMode: s.scrollMode ?? ScrollMode.VERTICAL, spreadMode: s.spreadMode ?? SpreadMode.NONE }), shallow );
69-361: Extract menu item rendering to reduce duplicationThe menu items have a lot of duplicated code for styling and structure. Consider extracting a reusable menu item component to reduce duplication.
You could create a helper component like:
type MenuItemProps = { onClick: () => void; disabled?: boolean; icon: React.ReactNode; label: string; isActive?: boolean; }; const MenuItem = ({ onClick, disabled, icon, label, isActive }: MenuItemProps) => { return ( <Menu.Item> {({ active }) => ( <button disabled={disabled} onClick={onClick} className={clsx( "w-full items-center flex space-x-2 box-border text-left px-2 py-1.5 text-xs disabled:opacity-50", { "bg-slate-100 text-gray-900": active, "text-gray-700": !active } )} > {icon} <span className="flex-1">{label}</span> {isActive && <VscCheck className="w-3" />} </button> )} </Menu.Item> ); };Then use it like:
<MenuItem onClick={() => pdfSlick?.requestPresentationMode()} icon={<VscPlay className="w-4 h-4" />} label="Presentation Mode" />
57-58: Add aria-haspopup attribute to improve accessibilityThe menu button should include additional ARIA attributes for better accessibility.
<Menu.Button disabled={!pdfSlick} + aria-haspopup="true" className="enabled:hover:bg-slate-200 enabled:hover:text-black text-slate-500 disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent" > <span className="sr-only">Open more actions menu</span> <VscKebabVertical className="w-4 h-4" /> </Menu.Button>src/renderer/src/routes/pdf-viewer/pdf-viewer/Tooltip.tsx (3)
10-62: Add a delay to tooltip displayThe tooltip appears immediately on hover, which could lead to flickering when moving the mouse across multiple elements. Consider adding a small delay.
const Tooltip = ({ children, position = "right", alignX = "center" }: TooltipProps) => { return ( <div role="tooltip" className={clsx( "absolute z-10 inline-block text-xs transition-opacity duration-300", - "rounded-sm shadow opacity-0 invisible", + "rounded-sm shadow opacity-0 invisible transition-all delay-300", "text-gray-500 bg-slate-50 border border-slate-300", "group-hover:visible group-hover:opacity-100", { "left-full top-1/2 -translate-y-1/2 ml-4": position === "right", "top-full mt-2": position === "bottom", "right-1/2 translate-x-1/2": position === "bottom" && alignX === "center", "right-0": position === "bottom" && alignX === "right", "left-0": position === "bottom" && alignX === "left" } )} >
4-8: Improve documentation of component usageThe
Tooltipcomponent depends on the parent element having agroupclass, but this isn't documented in the props interface.type TooltipProps = { children: ReactNode; position?: "bottom" | "right"; alignX?: "center" | "right" | "left"; + /** + * Note: This component must be wrapped in a parent element with the 'group' class + * for the hover functionality to work properly. + */ };
1-64: Add mobile/touch device supportThe tooltip relies on CSS hover, which doesn't work well on touch devices. Consider adding support for touch interactions.
To properly implement this, you would need to:
- Add state to track whether the tooltip is visible
- Add touch event handlers to toggle the tooltip
- Use the state to conditionally apply classes instead of just CSS hover
Basic implementation:
import { ReactNode, useState } from "react"; import clsx from "clsx"; type TooltipProps = { children: ReactNode; position?: "bottom" | "right"; alignX?: "center" | "right" | "left"; }; const Tooltip = ({ children, position = "right", alignX = "center" }: TooltipProps) => { const [isVisible, setIsVisible] = useState(false); return ( <div role="tooltip" className={clsx( "absolute z-10 inline-block text-xs transition-opacity duration-300", "rounded-sm shadow opacity-0 invisible", "text-gray-500 bg-slate-50 border border-slate-300", { "opacity-100 visible": isVisible, "group-hover:visible group-hover:opacity-100": true, "left-full top-1/2 -translate-y-1/2 ml-4": position === "right", "top-full mt-2": position === "bottom", "right-1/2 translate-x-1/2": position === "bottom" && alignX === "center", "right-0": position === "bottom" && alignX === "right", "left-0": position === "bottom" && alignX === "left" } )} > {/* ... rest of component */} </div> ); };src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/index.tsx (4)
83-89: Strengthen page number validationThe page number validation is good, but could be more robust by using a regex check for numeric input before parsing.
onSubmit={(e) => { e.preventDefault(); - const newPageNumber = parseInt(wantedPageNumber + ""); - if (Number.isInteger(newPageNumber) && newPageNumber > 0 && newPageNumber <= numPages) { + const pageStr = String(wantedPageNumber); + if (/^\d+$/.test(pageStr)) { + const newPageNumber = parseInt(pageStr, 10); + if (newPageNumber > 0 && newPageNumber <= numPages) { pdfSlick?.linkService.goToPage(newPageNumber); + } else { + setWantedPageNumber(pageNumber); + } } else { setWantedPageNumber(pageNumber); } }}
35-35: Add type safety to updatePageNumber functionThe
updatePageNumberfunction should have stronger typing to ensurepageNumberis always a number.- const updatePageNumber = ({ pageNumber }: { pageNumber: number }) => setWantedPageNumber(pageNumber); + const updatePageNumber = ({ pageNumber }: { pageNumber: number }) => setWantedPageNumber(pageNumber || 1);
125-149: Implement a more responsive layout for smaller screensSome UI elements are already hidden on small screens (
smbreakpoint), but the toolbar could still be crowded on very small screens. Consider implementing a more responsive design that collapses more elements into the "More" menu at smaller sizes.You could:
- Move more elements into the
MoreActionsMenucomponent- Use additional responsive breakpoints (like
mdorlg)- Consider implementing a collapsible toolbar that shows only essential controls initially
For example:
<div className="px-1 space-x-1 flex items-center justify-end"> - <FreetextMenu {...{ usePDFSlickStore }} /> - <InkMenu {...{ usePDFSlickStore }} /> + <div className="hidden xs:flex items-center space-x-1"> + <FreetextMenu {...{ usePDFSlickStore }} /> + <InkMenu {...{ usePDFSlickStore }} /> + </div> <Splitter /> <div className="items-center space-x-1 hidden sm:flex">And then add these elements to the
MoreActionsMenuwhen they're hidden.
46-55: Add tooltip to sidebar toggle buttonThe sidebar toggle button lacks a tooltip, unlike other buttons in the toolbar. For consistency, add a tooltip.
<button className={`enabled:hover:bg-slate-200 enabled:hover:text-black text-slate-500 disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent`} onClick={() => setIsThumbsbarOpen(!isThumbsbarOpen)} > {isThumbsbarOpen ? ( <VscLayoutSidebarLeftOff className="h-4 w-4" /> ) : ( <VscLayoutSidebarLeft className="h-4 w-4" /> )} + <Tooltip position="bottom"> + <p className="whitespace-nowrap">{isThumbsbarOpen ? "Hide sidebar" : "Show sidebar"}</p> + </Tooltip> </button>src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx (2)
108-115: Consider using dynamic locale instead of hardcoded "en-US".The date formatting currently uses a hardcoded "en-US" locale. Consider using the user's system locale for a more localized experience:
-new Intl.DateTimeFormat("en-US", { +new Intl.DateTimeFormat(navigator.language || "en-US", {Also applies to: 122-129
62-180: Consider extracting repeated property display pattern.There's significant repetition in the property display pattern. Consider extracting this into a reusable component to improve maintainability:
+type PropertyRowProps = { + label: string; + value: React.ReactNode; +}; + +const PropertyRow = ({ label, value }: PropertyRowProps) => ( + <div className="grid grid-cols-3 gap-4 px-6 py-1 my-0.5 hover:bg-slate-50"> + <dt className="text-sm font-medium text-gray-500">{label}</dt> + <dd className="text-sm text-gray-900 col-span-2"> + {value ?? <span className="text-xs text-gray-500">N/A</span>} + </dd> + </div> +);Then use it throughout the component.
src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/ButtonsBar.tsx (1)
28-32: Extract common button styles.All three buttons share similar styling logic. Consider extracting the common styles to reduce duplication:
+const buttonBaseClasses = "enabled:hover:bg-slate-200 enabled:hover:text-black text-slate-500 disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent"; +const buttonActiveClasses = "enabled:bg-blue-100 enabled:text-black border-blue-200"; // Then use them like: className={clsx( buttonBaseClasses, { [buttonActiveClasses]: tab === "thumbnails" } )}Also applies to: 43-47, 59-63
src/renderer/src/routes/pdf-viewer/pdf-viewer/index.tsx (1)
45-49: Improve progress indicator appearance.The current progress bar is very thin (h-px) which might be hard to see on high-DPI displays. Consider making it slightly taller and adding a subtle animation:
<div - className="fixed top-0 left-0 h-px bg-blue-600 z-50 transition-all duration-150 ease-out" + className="fixed top-0 left-0 h-1 bg-blue-600 z-50 transition-all duration-150 ease-out animate-pulse" style={{ width: `${loadedPerc}%` }} ></div>src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/ZoomSelector.tsx (1)
49-49: Replace bitwise operator with more readable Math function.The code uses the bitwise NOT operator twice (
~~) as a clever way to round numbers, but this reduces readability. Consider using standard Math methods:- {scaleValue && presets.has(scaleValue) ? presets.get(scaleValue) : `${~~(scale * 100)}%`} + {scaleValue && presets.has(scaleValue) ? presets.get(scaleValue) : `${Math.floor(scale * 100)}%`}src/main/browser/utility/intercept-rules.ts (2)
13-14: Consider using the modern URL API instead of deprecated URL.parseThe code is using the deprecated
URL.parse()method. It's recommended to use the modern URL constructor instead.- const protocol = URL.parse(currentUrl ?? "")?.protocol; + const protocol = new URL(currentUrl ?? "").protocol;
48-52: Add early return pattern for improved readabilityThe code could benefit from using an early return pattern when the URL object is null, which would make the logic clearer and reduce nesting.
const url = details.url; const urlObject = URL.parse(url); if (!urlObject) { - return callback({}); + callback({}); + return; }src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/FreetextMenu.tsx (1)
54-55: Remove commented codeThere are commented-out
<Menu.Item>tags that should be removed to maintain clean code.- // <Menu.Item key={`${name}-${s}`}> <button key={`${name}-${s}`} className="p-2 rounded-full border border-slate-200 hover:border-slate-400 hover:shadow-sm focus:ring-blue-500" style={{ backgroundColor: colors[name][s] }} onClick={() => { pdfSlick?.setAnnotationEditorMode(AnnotationEditorType.FREETEXT); pdfSlick?.setAnnotationEditorParams([ { type: AnnotationEditorParamsType.FREETEXT_COLOR, value: colors[name][s] } ]); }} /> - // </Menu.Item>Also applies to: 71-72
src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/index.tsx (1)
23-36: Extract magic numbers to named constantsThe code uses magic numbers (233 and 620) for the minimum and maximum width constraints. It would be better to define these as named constants at the top of the component for better readability and maintainability.
const Thumbsbar = ({ usePDFSlickStore, isThumbsbarOpen, thumbsRef }: ThumbsbarProps) => { const [tab, setTab] = useState<"thumbnails" | "outline" | "attachments">("thumbnails"); + const MIN_SIDEBAR_WIDTH = 233; + const MAX_SIDEBAR_WIDTH = 620; + const containerRef = useRef<HTMLDivElement>(null); const resizerRef = useRef<HTMLDivElement>(null); const [isResizing, setIsResizing] = useState(false); - const [width, setWidth] = useState(233); + const [width, setWidth] = useState(MIN_SIDEBAR_WIDTH); useEffect(() => { let newWidth = 0; const dragResize = drag<HTMLDivElement, unknown>() .on("start", () => { newWidth = containerRef.current?.clientWidth ?? 0; setIsResizing(true); }) .on("drag", (e) => { newWidth += e.dx; - const width = Math.min(620, Math.max(233, newWidth)); + const width = Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, newWidth)); setWidth(width); })src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Thumbnails.tsx (2)
40-41: Shadowingwidth/heightcauses mental overhead
({ pageNumber, width, height, … })shadows the outerwidthfromuseMeasure, making the code harder to scan. A small rename improves readability:- {({ pageNumber, width, height, src, pageLabel, loaded }) => ( + {({ pageNumber, width: thumbW, height: thumbH, src, pageLabel, loaded }) => ( … - width: `${width + 2}px`, - height: `${height + 2}px` + width: `${thumbW + 2}px`, + height: `${thumbH + 2}px` … - {src && <img src={src} width={width} height={height} />} + {src && <img src={src} width={thumbW} height={thumbH} />}
25-29: Minor ref forwarding ergonomics
ref(el!)will throw ifelisnull(when unmounting) because the non-null assertion forces the type. Forwarding the value as-is avoids the unnecessary assertion:- ref={(el) => { - ref(el!); - }} + ref={ref}This works because the
reffunction returned byuseMeasurematches the expected signature.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (37)
package.json(4 hunks)src/main/browser/sessions.ts(3 hunks)src/main/browser/tabs/tab.ts(2 hunks)src/main/browser/utility/intercept-rules.ts(1 hunks)src/main/browser/utility/protocols.ts(6 hunks)src/main/browser/utility/utils.ts(0 hunks)src/main/browser/window.ts(1 hunks)src/main/ipc/session/profiles.ts(1 hunks)src/main/ipc/session/spaces.ts(1 hunks)src/main/modules/basic-settings.ts(2 hunks)src/main/modules/utils.ts(2 hunks)src/main/onboarding/main.ts(1 hunks)src/main/sessions/profiles.ts(1 hunks)src/main/settings/main.ts(1 hunks)src/preload/index.ts(2 hunks)src/renderer/src/App.tsx(3 hunks)src/renderer/src/components/main/platform.tsx(1 hunks)src/renderer/src/lib/url.ts(2 hunks)src/renderer/src/routes/pdf-viewer/page.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Attachments.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/ButtonsBar.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Outline.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Thumbnails.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/index.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfo.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/FreetextMenu.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/InkMenu.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/SearchBar.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/Splitter.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/ZoomSelector.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/index.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Tooltip.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/colors.ts(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/index.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/route.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/browser/utility/utils.ts
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/renderer/src/routes/pdf-viewer/page.tsx (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/index.tsx (1)
PDFViewerApp(10-53)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfo.tsx (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx (1)
DocumentInfoModal(20-198)
src/renderer/src/App.tsx (2)
src/renderer/src/routes/pdf-viewer/route.tsx (1)
Route(3-14)src/renderer/src/components/main/platform.tsx (1)
PlatformProvider(5-29)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/FreetextMenu.tsx (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/colors.ts (2)
colorStrengths(209-209)colorNames(208-208)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx (1)
DocumentInfoModal(20-198)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/InkMenu.tsx (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/colors.ts (2)
colorStrengths(209-209)colorNames(208-208)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/index.tsx (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfo.tsx (1)
DocumentInfo(11-39)
src/main/browser/utility/protocols.ts (2)
src/main/modules/paths.ts (1)
PATHS(18-23)src/main/browser/utility/intercept-rules.ts (1)
setupInterceptRules(78-84)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: build (windows-latest)
- GitHub Check: build (ubuntu-latest)
🔇 Additional comments (33)
package.json (2)
97-97: Approve addition ofreact-icons
Includingreact-iconswill simplify iconography across the PDF viewer UI. Ensure that only the required icon sets are imported to avoid including the entire icon library in the bundle.
33-47:Details
❓ Verification inconclusive
Validate new runtime dependencies
The addition of@pdfslick/reactanduse-query-paramsis crucial for the new PDF viewer and URL state management. Please confirm these package versions are compatible with your existing React/Electron setup, check for any licensing constraints, and ensure there are no known security vulnerabilities.Run the following script to audit these new dependencies:
🏁 Script executed:
#!/bin/bash npm install npm audit --json | jq '.advisories | to_entries[] | select(.value.module_name == "pdfslick-react" or .value.module_name == "use-query-params")'Length of output: 736
🏁 Script executed:
#!/bin/bash # Generate a lockfile and rerun the audit for the two new dependencies npm install --package-lock-only npm audit --json | jq '.advisories | to_entries[] | select(.value.module_name == "@pdfslick/react" or .value.module_name == "use-query-params")'Length of output: 763
Manual verification required for new runtime dependencies
I wasn’t able to generate a lockfile or run a fullnpm auditin CI (no existing lockfile and audit errors), so please double-check the following for both@pdfslick/reactanduse-query-params:
- Compatibility: confirm these versions work with your current React and Electron versions.
- Licensing: run
npm view @pdfslick/react licenseandnpm view use-query-params licenseto ensure there are no restrictive terms.- Security: add or regenerate a lockfile (
npm install --package-lock-only), then runnpm auditor an equivalent scan over these two modules.src/main/ipc/session/profiles.ts (1)
2-2: Approve updated import path
ThegenerateIDutility import has been correctly updated to reflect its new location in@/modules/utils, aligning with the refactoring of utility functions.src/main/settings/main.ts (1)
3-3: Approve updated session import
ThedefaultSessionReadyconstant import path has been updated to@/browser/sessions, matching the relocation in your refactoring.src/main/onboarding/main.ts (1)
4-4: Approve updated session import
The import path fordefaultSessionReadyhas been updated to the new@/browser/sessionsmodule, maintaining consistency with related files.src/main/ipc/session/spaces.ts (1)
14-14: Approve updated import path
ThegenerateIDimport has been correctly switched to@/modules/utilsfollowing the utility module relocation.src/main/sessions/profiles.ts (1)
8-8: Import path update to reflect utility function relocation.The import path for
generateIDhas been updated from@/browser/utility/utilsto@/modules/utilsas part of the broader refactoring where utility functions have been consolidated.src/main/browser/window.ts (1)
13-13: Import path update to reflect session initialization centralization.The import path for
defaultSessionReadyhas been updated from@/browser/utility/protocolsto@/browser/sessionsas part of centralizing session initialization, which includes protocol registrations and preload script setup.src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/Splitter.tsx (1)
1-5: Well-designed UI separator component.This component creates a visually clean vertical line separator using Tailwind CSS. The implementation is simple, focused, and reusable, making it ideal for separating toolbar elements in the PDF viewer.
src/main/browser/tabs/tab.ts (3)
9-9: Removed unused PATHS import.The import of PATHS is now commented out as it's no longer needed after moving the preload script implementation.
13-13: Import path update to reflect utility function relocation.The import path for
generateIDhas been updated from@/browser/utility/utilsto@/modules/utilsas part of the broader utility functions consolidation.
97-101: Improved tab transparency and preload script handling.Two changes here:
- Added
transparent: trueto support transparency in web contents views, which is necessary for the PDF viewer.- Removed the direct preload script path, replaced with a comment indicating it's now handled in protocols.ts.
This improves the architecture by centralizing preload script management at the session level rather than hardcoding it in each tab.
src/renderer/src/routes/pdf-viewer/route.tsx (1)
1-14: Well-implemented dynamic route loading.This component implements code splitting through dynamic imports, which is a good practice for improving application performance. The PDF viewer page will only be loaded when this route is accessed, reducing the initial bundle size.
The implementation follows React best practices for code splitting and lazy loading.
src/renderer/src/components/main/platform.tsx (1)
8-23: Robust error handling for platform detection.Adding the try-catch block is a good defensive programming practice that prevents runtime errors when the
flowobject is unavailable in contexts like iframes. The comments clearly explain the rationale behind this change, which is helpful for future maintenance.src/renderer/src/lib/url.ts (1)
84-87: Support for PDF viewer protocol implementation.The implementation for handling
flow://pdf-viewerURLs is consistent with the existing pattern for special Flow URLs. The nullish coalescing operator (??) ensures a sensible default when the URL parameter is absent.src/main/modules/basic-settings.ts (2)
211-218: Experimental feature toggle for PDF viewer.Adding the PDF viewer as an opt-in experimental feature is a good approach. Setting the default value to
falseis appropriate for a new feature that may still need refinement.
273-278: New settings category for experimental features.Creating a dedicated "Experimental Settings" category provides a clear distinction between stable and experimental features. This helps set appropriate user expectations for features that may change or have limited functionality.
src/main/modules/utils.ts (1)
79-85: Efficient ID generation implementation.The
generateIDfunction provides a clean approach to generate short random identifiers by using the first segment of a UUID. This is more efficient than generating and storing full UUIDs when only short identifiers are needed.src/renderer/src/App.tsx (4)
5-6: Good addition of query parameter support.The integration of
QueryParamProviderandWindowHistoryAdapteris a sensible choice for managing URL parameters throughout the application.
24-24: Appropriate import for the new PDF viewer route.Adding this import follows the established pattern for route components in the codebase.
63-65: Well-structured route definition for the PDF viewer.The new route follows the same pattern as existing routes, providing consistency in the routing architecture.
72-77: Correct implementation of the updated App component.Wrapping the application with
QueryParamProviderensures query parameter functionality is available throughout the app. The component hierarchy is maintained withPlatformProvideras a child component.src/renderer/src/routes/pdf-viewer/page.tsx (2)
1-7: Appropriate imports for PDF viewer functionality.The imports include the necessary components for theme support, query parameter handling, and PDF rendering. The PDF viewer CSS is correctly imported to ensure proper styling.
8-9:Details
✅ Verification successful
Consider addressing theming issues.
The comment suggests there are unresolved issues with the theme provider. It would be beneficial to investigate and fix these issues rather than disabling the theme functionality.
🏁 Script executed:
#!/bin/bash # Search for other theme provider issues or workarounds in the codebase rg -n "Theme makes it go all weird" --context 3 rg -n "THEME_PROVIDER_ENABLED" --context 3Length of output: 1548
Intentionally disabling ThemeProvider for PDF viewer
The PDF viewer’s bundled CSS (@pdfslick/react/dist/pdf_viewer.css) conflicts with our theme styles, so disabling the ThemeProvider here is an intentional workaround. No further theming changes are required for this page.src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfo.tsx (2)
1-9: Well-structured imports and props definition.The component has clear type definitions and imports the necessary dependencies for its functionality.
11-21: Good state management for modal visibility.The component correctly uses React useState for managing modal visibility state with clear open/close functions.
src/preload/index.ts (1)
502-504: Consistent permission override implementation.The permission override for
getPlatformfollows the established pattern for other API methods that require special access, making it a maintainable solution.src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx (1)
5-12: Good utility function for byte formatting.The
formatBytesfunction is well implemented with proper handling of different size units and decimal precision.src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/ButtonsBar.tsx (1)
58-58:Details
✅ Verification successful
Verify attachments type.
The check
attachments.size < 1assumesattachmentsis a Map or Set. Ensure this is consistent with how attachments are stored in the PDF store.
🏁 Script executed:
#!/bin/bash # Finding the attachments type in the PDFSlick store definition rg -A 3 -B 3 "attachments" --glob "*.ts*" | grep -i "type"Length of output: 152
🏁 Script executed:
#!/bin/bash # Search for the definition and usage of `attachments` across the TypeScript codebase rg -n -C3 "attachments" --glob "*.ts*" .Length of output: 6176
🏁 Script executed:
#!/bin/bash # Locate the PDFSlick store definition and inspect attachments type store_file=$(rg -l "export const usePDFSlickStore" --glob "*.ts*") echo "Store file: $store_file" rg -n -C3 "attachments" "$store_file"Length of output: 172
🏁 Script executed:
#!/bin/bash # Locate all occurrences of the usePDFSlickStore hook to find its import and definition rg -n "usePDFSlickStore" -C2 --glob "*.ts*" .Length of output: 25301
🏁 Script executed:
#!/bin/bash FILE="src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/ButtonsBar.tsx" echo "Showing import section of $FILE" sed -n '1,50p' "$FILE"Length of output: 2481
Attachments type confirmed
Theattachmentsprop returned byusePDFSlickStoreis a Map (as shown by its use withattachments.entries()inAttachments.tsx), so usingattachments.sizeandattachments.size < 1is valid. No changes are needed here.src/main/browser/utility/intercept-rules.ts (2)
66-74: Handle potential error when accessing urlObject.originThe code assumes that
urlObject.originwill always be available, but it might be undefined for some URL types. Consider adding a fallback.- const newHeaders = { ...details.requestHeaders, Origin: urlObject.origin }; + const origin = urlObject.origin || `${urlObject.protocol}//${urlObject.host}`; + const newHeaders = { ...details.requestHeaders, Origin: origin }; callback({ requestHeaders: newHeaders });
77-84: Well-structured setup functionThe
setupInterceptRulesfunction is well-structured, providing a clear entry point for setting up both interception rules. The code is modular and follows good separation of concerns.src/main/browser/utility/protocols.ts (2)
47-49: No objections to domain-whitelist extensionAdding
extensionsandpdf-viewertoFLOW_PROTOCOL_ALLOWED_DOMAINSlooks consistent with the new feature set and doesn’t introduce any obvious security concerns beyond those already present for the existing domains.
352-357:Details
❓ Verification inconclusive
session.registerPreloadScriptmay not exist in current Electron versionsElectron ≤ v29 exposes
session.setPreloads()(static list) and, in newer nightlies,session.addPreload(); aregisterPreloadScript()method is not present in any stable release at the time of writing. Calling an undefined API will throw at runtime and break the session initialisation path.-export function registerPreloadScript(session: Session) { - session.registerPreloadScript({ - id: "flow-preload", - type: "frame", - filePath: PATHS.PRELOAD - }); +export function registerPreloadScript(session: Session) { + // Prefer upcoming API when available, else fall back to the stable one. + // NOTE: keep this block until the project locks to an Electron version + if (typeof (session as any).registerPreloadScript === "function") { + (session as any).registerPreloadScript({ + id: "flow-preload", + type: "frame", + filePath: PATHS.PRELOAD + }); + } else if (typeof session.setPreloads === "function") { + // Avoid duplicates if registerPreloadScript gets called twice for the same session + const current = new Set(session.getPreloads?.() ?? []); + current.add(PATHS.PRELOAD); + session.setPreloads([...current]); + } else { + console.error("No supported preload-registration API found on Session"); + } +}Please verify which Electron version the application ships with and adjust accordingly.
#!/bin/bash # Retrieve the Electron version from package.json if [ -f package.json ]; then jq -r '(.dependencies.electron // .devDependencies.electron // .peerDependencies.electron) // "not specified"' package.json else echo "package.json not found" fi
🏁 Script executed:
#!/bin/bash # 1. Electron version from package.json if [ -f package.json ]; then echo "Electron version from package.json:" jq -r '(.dependencies.electron // .devDependencies.electron // .peerDependencies.electron) // "not specified"' package.json else echo "package.json not found" fi # 2. Search for any occurrences of registerPreloadScript in the codebase echo "Searching for registerPreloadScript references in codebase:" rg "registerPreloadScript" -n .Length of output: 829
Ensure Electron preload API compatibility
The project is using Electron “github:castlabs/electron-releases#v35.3.0+wvcus,” which may not exposesession.registerPreloadScript. Verify whether your custom build includes this API; if not, fall back to the stablesession.setPreloads()approach to avoid runtime errors.Files to review:
- src/main/browser/utility/protocols.ts (lines 352–357)
- src/main/browser/sessions.ts (calls at lines 73 & 98)
Suggested update in protocols.ts:
export function registerPreloadScript(session: Session) { - session.registerPreloadScript({ - id: "flow-preload", - type: "frame", - filePath: PATHS.PRELOAD - }); + // Use new API if available; otherwise fall back to stable setPreloads() + if (typeof (session as any).registerPreloadScript === "function") { + (session as any).registerPreloadScript({ + id: "flow-preload", + type: "frame", + filePath: PATHS.PRELOAD + }); + } else if (typeof session.setPreloads === "function") { + const current = new Set(session.getPreloads?.() ?? []); + current.add(PATHS.PRELOAD); + session.setPreloads([...current]); + } else { + console.error("No supported preload-registration API found on Session"); + } }Please confirm your Electron fork’s API surface and adjust accordingly.
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/InkMenu.tsx
Outdated
Show resolved
Hide resolved
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/InkMenu.tsx
Outdated
Show resolved
Hide resolved
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/InkMenu.tsx
Outdated
Show resolved
Hide resolved
aa9cfb0 to
eb98c1a
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx (1)
343-343: Fix incorrect button disable conditionThe "Document Properties" button is incorrectly disabled when on the last page. Document properties should be accessible regardless of the current page.
-disabled={pageNumber === numPages}
🧹 Nitpick comments (6)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx (6)
73-85: Consider adding keyboard shortcuts for quick accessThe presentation mode button (and other frequently used actions) would benefit from keyboard shortcuts to improve user experience and accessibility.
Add a keyboard shortcut indicator to the presentation mode button:
<button onClick={() => pdfSlick?.requestPresentationMode()} className={clsx( "w-full items-center flex space-x-2 box-border text-left px-2 py-1.5 text-xs disabled:opacity-50", { "bg-slate-100 text-gray-900": active, "text-gray-700": !active } )} > <VscPlay className="w-4 h-4" /> - <span>Presentation Mode</span> + <span className="flex-1">Presentation Mode</span> + <span className="text-xs text-gray-500">F11</span> </button>
27-40: Optimize component rendering with useCallbackEvent handlers are recreated on each render, which can impact performance. Using
useCallbackwould maintain referential equality between renders.const MoreActionsMenu = ({ usePDFSlickStore }: MoreActionsMenuProps) => { const [isOpen, setIsOpen] = useState(false); const { pdfSlick, pagesRotation, pageNumber, numPages, scrollMode, spreadMode } = usePDFSlickStore( (s) => ({ pdfSlick: s.pdfSlick, pagesRotation: s.pagesRotation, pageNumber: s.pageNumber, numPages: s.numPages, scrollMode: s.scrollMode, spreadMode: s.spreadMode }), shallow ); + + const handleSave = useCallback(() => pdfSlick?.downloadOrSave(), [pdfSlick]); + const handlePrint = useCallback(() => pdfSlick?.triggerPrinting(), [pdfSlick]); + const handleRotateClockwise = useCallback(() => pdfSlick?.setRotation(pagesRotation + 90), [pdfSlick, pagesRotation]);Then use these callbacks in your click handlers.
50-69: Enhance menu accessibility with ARIA attributesThe menu button has good screen reader support, but the menu items could benefit from additional ARIA attributes for improved accessibility.
Add ARIA attributes to the menu component:
return ( <> - <Menu as="span" className=""> + <Menu as="span" className="" aria-label="PDF viewer options"> <Menu.Button disabled={!pdfSlick} className="enabled:hover:bg-slate-200 enabled:hover:text-black text-slate-500 disabled:text-slate-300 p-1 rounded-sm transition-all group relative focus:border-blue-400 focus:ring-0 focus:shadow outline-none border border-transparent" + aria-haspopup="true" > <span className="sr-only">Open more actions menu</span> <VscKebabVertical className="w-4 h-4" /> </Menu.Button> <Transition as={Fragment} enter="transition ease-out duration-100" enterFrom="transform opacity-0 scale-95" enterTo="transform opacity-100 scale-100" leave="transition ease-in duration-75" leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - <Menu.Items className="absolute right-2 w-52 z-30 mt-2 origin-top-right divide-y divide-slate-200 rounded text-left bg-white shadow-lg ring-1 ring-black/5 focus:outline-none"> + <Menu.Items className="absolute right-2 w-52 z-30 mt-2 origin-top-right divide-y divide-slate-200 rounded text-left bg-white shadow-lg ring-1 ring-black/5 focus:outline-none" aria-orientation="vertical" role="menu">
74-106: Extract repeated menu item pattern into a reusable componentThere's significant code duplication in the structure and styling of menu items. Extracting this pattern would improve maintainability.
Create a reusable menu item component:
type MenuItemProps = { icon: React.ReactNode; label: string; onClick: () => void; disabled?: boolean; isActive?: boolean; rightElement?: React.ReactNode; }; const MenuItem = ({ icon, label, onClick, disabled = false, rightElement }: MenuItemProps) => ( <Menu.Item> {({ active }) => ( <button onClick={onClick} disabled={disabled} className={clsx( "w-full items-center flex space-x-2 box-border text-left px-2 py-1.5 text-xs disabled:opacity-50", { "bg-slate-100 text-gray-900": active, "text-gray-700": !active } )} > {icon} <span className="flex-1">{label}</span> {rightElement} </button> )} </Menu.Item> );Then use it in the menu:
<MenuItem icon={<VscDesktopDownload className="w-4 h-4" />} label="Save" onClick={() => pdfSlick?.downloadOrSave()} />
173-183: Improve handling of rotation functionsThe current implementation directly sets rotation angles, but doesn't normalize them to stay within 0-359 degrees, which could lead to unnecessarily large values after multiple rotations.
Consider normalizing the rotation value:
<button onClick={(e) => { e.preventDefault(); - pdfSlick?.setRotation(pagesRotation + 90); + pdfSlick?.setRotation((pagesRotation + 90) % 360); }} className={clsx("w-full flex space-x-2 box-border text-left px-2 py-1.5 text-xs", { "bg-slate-100 text-gray-900": active, "text-gray-700": !active })} > <VscRefresh className="w-4 h-4" />
23-25: Type definition for usePDFSlickStore could be more specificThe current type
TUsePDFSlickStoreis imported from a third-party library, but it might be beneficial to define more specific types for your use case.Consider creating a more specific type that only includes the properties you're using:
+import { type StoreApi, type UseBoundStore } from 'zustand'; + +interface PDFSlickState { + pdfSlick: any; // Replace with more specific type if available + pagesRotation: number; + pageNumber: number; + numPages: number; + scrollMode: ScrollMode; + spreadMode: SpreadMode; +} + type MoreActionsMenuProps = { - usePDFSlickStore: TUsePDFSlickStore; + usePDFSlickStore: UseBoundStore<StoreApi<PDFSlickState>>; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (37)
package.json(4 hunks)src/main/browser/sessions.ts(3 hunks)src/main/browser/tabs/tab.ts(2 hunks)src/main/browser/utility/intercept-rules.ts(1 hunks)src/main/browser/utility/protocols.ts(6 hunks)src/main/browser/utility/utils.ts(0 hunks)src/main/browser/window.ts(1 hunks)src/main/ipc/session/profiles.ts(1 hunks)src/main/ipc/session/spaces.ts(1 hunks)src/main/modules/basic-settings.ts(2 hunks)src/main/modules/utils.ts(2 hunks)src/main/onboarding/main.ts(1 hunks)src/main/sessions/profiles.ts(1 hunks)src/main/settings/main.ts(1 hunks)src/preload/index.ts(2 hunks)src/renderer/src/App.tsx(3 hunks)src/renderer/src/components/main/platform.tsx(1 hunks)src/renderer/src/lib/url.ts(2 hunks)src/renderer/src/routes/pdf-viewer/page.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Attachments.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/ButtonsBar.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Outline.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Thumbnails.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/index.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfo.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/FreetextMenu.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/InkMenu.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/SearchBar.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/Splitter.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/ZoomSelector.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/index.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/Tooltip.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/colors.ts(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/index.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/route.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/browser/utility/utils.ts
🚧 Files skipped from review as they are similar to previous changes (35)
- src/main/onboarding/main.ts
- src/main/ipc/session/profiles.ts
- src/main/browser/window.ts
- src/main/ipc/session/spaces.ts
- src/renderer/src/components/main/platform.tsx
- src/main/sessions/profiles.ts
- src/main/settings/main.ts
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/Splitter.tsx
- src/renderer/src/routes/pdf-viewer/route.tsx
- src/main/browser/tabs/tab.ts
- src/main/modules/utils.ts
- src/renderer/src/lib/url.ts
- src/main/modules/basic-settings.ts
- src/renderer/src/App.tsx
- package.json
- src/renderer/src/routes/pdf-viewer/pdf-viewer/colors.ts
- src/renderer/src/routes/pdf-viewer/page.tsx
- src/preload/index.ts
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Attachments.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/SearchBar.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfo.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Tooltip.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Outline.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/index.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/index.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/FreetextMenu.tsx
- src/main/browser/sessions.ts
- src/main/browser/utility/protocols.ts
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/ZoomSelector.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/ButtonsBar.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/InkMenu.tsx
- src/main/browser/utility/intercept-rules.ts
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/Thumbnails.tsx
- src/renderer/src/routes/pdf-viewer/pdf-viewer/Thumbsbar/index.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx (1)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/DocumentInfoModal.tsx (1)
DocumentInfoModal(20-198)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: build (macos-latest)
- GitHub Check: build (windows-latest)
- GitHub Check: build (ubuntu-latest)
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx
Show resolved
Hide resolved
src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/MoreActionsMenu.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/browser/utility/intercept-rules.ts (1)
43-58:⚠️ Potential issueSecurity issue: Missing URL scheme validation
The code only checks if the pathname ends with ".pdf" but doesn't validate the URL scheme, which could be a security risk if a malicious URL with a PDF extension is processed.
Implement secure URL validation by checking the protocol:
const { pathname } = urlObject; - if (pathname && pathname.toLowerCase().endsWith(".pdf") && getSettingValueById("enableFlowPdfViewer") === true) { + // Only redirect HTTP/HTTPS URLs or other trusted protocols + const isValidProtocol = url.startsWith('http:') || url.startsWith('https:') || url.startsWith('file:'); + if (pathname && pathname.toLowerCase().endsWith(".pdf") && isValidProtocol && getSettingValueById("enableFlowPdfViewer") === true) {🧰 Tools
🪛 Biome (1.9.4)
[error] 57-57: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🧹 Nitpick comments (9)
src/main/browser/utility/intercept-rules.ts (2)
57-57: Use optional chaining for code safetyThe static analysis hint suggests using optional chaining here for better safety.
- if (pathname && pathname.toLowerCase().endsWith(".pdf") && getSettingValueById("enableFlowPdfViewer") === true) { + if (pathname?.toLowerCase().endsWith(".pdf") && getSettingValueById("enableFlowPdfViewer") === true) {🧰 Tools
🪛 Biome (1.9.4)
[error] 57-57: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
90-101: Origin header handling may be too broadThe function is replacing the Origin header for all requests, not just PDF-related ones. This could potentially affect other requests unnecessarily.
Consider limiting the Origin header modification to only PDF-related requests:
webRequest.onBeforeSendHeaders((details, callback) => { const url = details.url; const urlObject = URL.parse(url); if (!urlObject) { return callback({}); } + // Only modify headers for PDF-related URLs + const { pathname } = urlObject; + if (pathname?.toLowerCase().endsWith(".pdf")) { const newHeaders = { ...details.requestHeaders, Origin: urlObject.origin }; callback({ requestHeaders: newHeaders }); + return; + } callback({}); });src/main/browser/utility/protocols/_protocols/flow-external.ts (1)
29-36: Misleading comment in protocol handlerThe comment
// flow://:pathdoesn't match the actual protocol being handled (flow-external).protocol.handle("flow-external", async (request) => { const urlString = request.url; const url = new URL(urlString); - // flow://:path + // flow-external://:path return await handleDomainRequest(request, url); });src/main/browser/utility/protocols/utils.ts (3)
39-47: Well-implemented development server integrationThe code properly checks for development mode with hot reload enabled and attempts to serve files from the development server when appropriate.
Consider adding a timeout for the fetch operation to prevent hanging in case the development server is slow to respond.
if (ping) { - return await fetchFromDevServer(transformedPath, request); + // Add timeout to prevent hanging if dev server is slow + return await Promise.race([ + fetchFromDevServer(transformedPath, request), + new Promise<Response>((_, reject) => + setTimeout(() => reject(new Error('Dev server timeout')), 5000) + ).catch(error => { + console.warn('Dev server timeout:', error); + return new Response('Development server timeout', { status: 504 }); + }) + ]); }
49-65: Well-structured file serving implementationThe code correctly checks for directories, reads file contents, determines appropriate content types, and returns proper responses.
Consider adding caching headers to improve performance for static assets:
return new Response(buffer, { headers: { - "Content-Type": contentType + "Content-Type": contentType, + "Cache-Control": "public, max-age=86400" } });
66-69: Error handling could be more specificThe current implementation logs the error and returns a generic 404 response for all error types.
Consider differentiating between common error types for better debugging and user experience:
} catch (error) { console.error("Error serving file:", error); - return new Response("File not found", { status: 404 }); + if (error.code === 'ENOENT') { + return new Response("File not found", { status: 404 }); + } else if (error.code === 'EACCES') { + return new Response("Permission denied", { status: 403 }); + } else { + return new Response("Internal server error", { status: 500 }); + } }src/main/browser/utility/protocols/_protocols/flow.ts (3)
22-34: PDF caching implementation needs documentationThe PDF cache implementation is functional but lacks explanatory comments.
Add documentation to explain the purpose and lifecycle of cached PDF responses:
+/** + * In-memory cache for PDF responses. + * Used to temporarily store PDF content that needs to be displayed + * in the PDF viewer without re-downloading or processing it again. + */ const PDF_CACHE = new Map<string, Response>(); +/** + * Adds a PDF response to the cache with the specified key. + * @param key - Unique identifier for the cached response + * @param response - The PDF response to cache + */ export function addPdfResponseToCache(key: string, response: Response) { PDF_CACHE.set(key, response); } +/** + * Retrieves a PDF response from the cache. + * @param key - The key to look up + * @returns The cached response or undefined if not found + */ function getPdfResponseFromCache(key: string): Response | undefined { return PDF_CACHE.get(key); } +/** + * Removes a PDF response from the cache. + * @param key - The key of the response to remove + */ function removePdfResponseFromCache(key: string) { PDF_CACHE.delete(key); }
82-98: Asset serving error handling could be improvedThe error handling for asset requests is basic but functional.
Consider improving error handling with more specific error types and adding logging of the specific asset path for debugging:
try { // Read file contents const buffer = await fsPromises.readFile(filePath); // Determine content type based on file extension const contentType = getContentType(filePath); return new Response(buffer, { headers: { "Content-Type": contentType } }); } catch (error) { - console.error("Error serving asset:", error); + console.error(`Error serving asset '${normalizedPath}':`, error); + + if (error.code === 'ENOENT') { return new Response("Asset not found", { status: 404 }); + } else if (error.code === 'EACCES') { + return new Response("Permission denied", { status: 403 }); + } else { + return new Response("Internal server error", { status: 500 }); + } }
135-150: PDF cache request handler should document cleanupThe PDF cache handler removes the response from cache after retrieving it, but this behavior isn't obvious.
Add a comment explaining the one-time use nature of the cached PDF responses:
const handlePdfCacheRequest = async (_request: Request, url: URL) => { const pdfURL = url.searchParams.get("url"); const key = url.searchParams.get("key"); if (!pdfURL || !key) { return new Response("Invalid request path", { status: 400 }); } const pdfResponse = getPdfResponseFromCache(key); if (!pdfResponse) { // redirect to actual url return Response.redirect(pdfURL); } + // Remove from cache after use - PDFs are meant to be retrieved only once removePdfResponseFromCache(key); return pdfResponse; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (10)
src/main/browser/sessions.ts(3 hunks)src/main/browser/utility/intercept-rules.ts(1 hunks)src/main/browser/utility/protocols.ts(0 hunks)src/main/browser/utility/protocols/_protocols/flow-external.ts(1 hunks)src/main/browser/utility/protocols/_protocols/flow-internal.ts(1 hunks)src/main/browser/utility/protocols/_protocols/flow.ts(1 hunks)src/main/browser/utility/protocols/index.ts(1 hunks)src/main/browser/utility/protocols/utils.ts(1 hunks)src/renderer/src/routes/pdf-viewer/page.tsx(1 hunks)src/renderer/src/routes/pdf-viewer/pdf-viewer/README.md(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/browser/utility/protocols.ts
✅ Files skipped from review due to trivial changes (1)
- src/renderer/src/routes/pdf-viewer/pdf-viewer/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/browser/sessions.ts
- src/renderer/src/routes/pdf-viewer/page.tsx
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/main/browser/utility/protocols/_protocols/flow-external.ts (1)
src/main/browser/utility/protocols/utils.ts (2)
AllowedDomains(10-12)serveStaticFile(14-70)
src/main/browser/utility/protocols/index.ts (3)
src/main/browser/utility/protocols/_protocols/flow.ts (1)
registerFlowProtocol(36-179)src/main/browser/utility/protocols/_protocols/flow-external.ts (1)
registerFlowExternalProtocol(15-36)src/main/modules/paths.ts (1)
PATHS(18-23)
src/main/browser/utility/protocols/_protocols/flow-internal.ts (2)
src/main/browser/utility/protocols/utils.ts (2)
AllowedDomains(10-12)serveStaticFile(14-70)src/main/index.ts (1)
browser(14-14)
src/main/browser/utility/protocols/utils.ts (4)
src/main/modules/paths.ts (1)
PATHS(18-23)src/main/modules/flags.ts (1)
FLAGS(16-41)src/main/browser/utility/hot-reload.ts (3)
setupHotReloadFileDescriptors(9-15)isDevelopmentServerRunning(21-27)fetchFromDevServer(45-81)src/main/modules/utils.ts (1)
getContentType(24-26)
src/main/browser/utility/protocols/_protocols/flow.ts (4)
src/main/browser/utility/protocols/utils.ts (2)
AllowedDomains(10-12)serveStaticFile(14-70)src/main/modules/paths.ts (1)
PATHS(18-23)src/main/modules/utils.ts (1)
getContentType(24-26)src/main/index.ts (1)
browser(14-14)
🪛 Biome (1.9.4)
src/main/browser/utility/intercept-rules.ts
[error] 57-57: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: build (ubuntu-latest)
- GitHub Check: build (windows-latest)
- GitHub Check: build (macos-latest)
🔇 Additional comments (25)
src/main/browser/utility/intercept-rules.ts (6)
4-7: Imports and dependencies look goodThe file correctly imports all required dependencies for the interceptor functions.
8-37: CORS bypass implementation for custom protocolsThe CORS bypass implementation for Flow protocols is well-structured with proper header manipulation. It correctly identifies whitelisted protocols, removes existing CORS headers, and adds a wildcard CORS header.
39-42: Setup function for PDF viewer correctly createdThe setup function for the PDF viewer with appropriate web request creation looks good.
58-72: PDF content type verification looks goodThe implementation correctly fetches the PDF, verifies its content type, and handles potential errors.
73-84: PDF caching and redirection logic is well-implementedThe codebase properly generates cache keys, caches PDF responses, and constructs appropriate redirect URLs.
103-110: Export function is well-structuredThe export function correctly sets up both intercept rules for the session.
src/main/browser/utility/protocols/_protocols/flow-external.ts (3)
1-4: Imports and dependencies look goodThe file correctly imports all necessary dependencies including AllowedDomains, serveStaticFile, and the Protocol type.
4-13: Well-documented domain whitelistThe FLOW_EXTERNAL_ALLOWED_DOMAINS whitelist is well-structured and properly documented, including sources for the games.
15-27: Protocol handler function properly validates hostnamesThe handler function correctly validates if the requested hostname is in the allowed domains list and returns a 400 status for invalid hosts.
src/main/browser/utility/protocols/index.ts (3)
1-19: Privilege registration for protocols is well-structuredThe code correctly registers three protocols with appropriate privileges, which is important for security and functionality.
21-25: Protocol registration with session is clean and modularThe registerProtocolsWithSession function follows good separation of concerns by correctly delegating the registration to the respective protocol handlers.
27-33: Preload script registration is well-implementedThe registerPreloadScript function properly registers the preload script with the correct parameters.
src/main/browser/utility/protocols/_protocols/flow-internal.ts (4)
1-13: Domain whitelist is well-definedThe allowed domains for the flow-internal protocol are properly defined with a clear structure.
26-71: Favicon handler implementation handles edge cases wellThe handleActiveFaviconRequest function properly validates input parameters and handles edge cases like missing tab, favicon, or profile.
73-84: Domain request handler follows secure practicesThe domain request handler validates hostnames against the whitelist and returns appropriate error responses for invalid requests.
86-98: Protocol handler routing is clear and effectiveThe protocol.handle function appropriately routes requests to specific handlers based on the URL host.
src/main/browser/utility/protocols/utils.ts (4)
10-12: Well-defined interface for allowed domainsThe
AllowedDomainsinterface provides a clear contract for defining which domains are permitted and how they should be handled.
14-19: Function signature looks goodThe function has meaningful parameter names, appropriate defaults, and good type annotations.
20-26: Good path normalizationThe path normalization logic handles both leading and trailing slashes correctly, ensuring consistent path handling.
28-30: Proper default file handlingCorrectly defaults to serving "index.html" when an empty path is provided, following web server conventions.
src/main/browser/utility/protocols/_protocols/flow.ts (5)
12-20: Well-defined protocol allowed domainsThe allowed domains are clearly specified and include all necessary system pages.
72-73: Good path normalization for securityThe implementation properly normalizes paths and prevents directory traversal attacks by removing parent directory references.
77-80: Proper path validation for securityThe code ensures the requested path is within the allowed directory, which is an important security measure.
100-133: Extension icon handler has thorough error checkingThe handler properly validates inputs and handles all potential error cases in the process of retrieving extension icons.
152-178: Well-structured protocol handler with clear routingThe main protocol handler efficiently routes requests to the appropriate specialized handlers based on the URL host.
Summary by CodeRabbit
New Features
Improvements
Chores