feat(UI): Introduce new UI components for file attachments and related views (#191)
Add file management API and UI components Implement directory listing, search and CRUD operations in desktop backend Expose new Files API on frontend to list, search, and modify files
This commit is contained in:
committed by
GitHub
parent
1f23b63c0b
commit
d97181bcd2
@@ -2,7 +2,7 @@ import React, { useMemo, useRef, useState, useCallback, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { FileDiff } from '@pierre/diffs/react';
|
||||
import { parseDiffFromFile, type FileContents, type FileDiffMetadata, type SelectedLineRange } from '@pierre/diffs';
|
||||
import { RiArrowDownSLine, RiEyeLine, RiSendPlane2Line } from '@remixicon/react';
|
||||
import { RiSendPlane2Line } from '@remixicon/react';
|
||||
|
||||
import { useOptionalThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
@@ -141,7 +141,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
|
||||
const [selection, setSelection] = useState<SelectedLineRange | null>(null);
|
||||
const [commentText, setCommentText] = useState('');
|
||||
|
||||
const commentContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Calculate initial center synchronously to avoid flicker
|
||||
const getMainContentCenter = useCallback(() => {
|
||||
@@ -212,8 +212,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// Check if click is inside the comment UI portal
|
||||
const commentUI = document.querySelector('[data-comment-ui]');
|
||||
if (commentUI?.contains(target)) return;
|
||||
if (commentContainerRef.current?.contains(target)) return;
|
||||
|
||||
// Check if click is inside toast (sonner)
|
||||
if (target.closest('[data-sonner-toast]') || target.closest('[data-sonner-toaster]')) return;
|
||||
@@ -302,21 +301,8 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
fileDiff: FileDiffMetadata;
|
||||
} | null>(null);
|
||||
|
||||
// Threshold for lazy loading (total lines > 1500 or content size > 150KB)
|
||||
const isLargeDiff = useMemo(() => {
|
||||
const totalLines = (original || '').split('\n').length + (modified || '').split('\n').length;
|
||||
const totalSize = (original?.length || 0) + (modified?.length || 0);
|
||||
return totalLines > 1500 || totalSize > 150 * 1024;
|
||||
}, [original, modified]);
|
||||
|
||||
// State for large diff loading
|
||||
const [shouldLoad, setShouldLoad] = useState(false);
|
||||
|
||||
// Parse diff when loaded (for large diffs) or always (for small diffs)
|
||||
// Pre-parse the diff with cacheKey for worker pool caching
|
||||
const fileDiff = useMemo(() => {
|
||||
// For large diffs, only parse if manually triggered
|
||||
if (isLargeDiff && !shouldLoad) return null;
|
||||
|
||||
const cacheKey = getCacheKey(fileName, original, modified);
|
||||
|
||||
// Return cached diff if inputs haven't changed
|
||||
@@ -344,7 +330,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
diffCacheRef.current = { key: cacheKey, fileDiff: diff };
|
||||
|
||||
return diff;
|
||||
}, [fileName, original, modified, language, isLargeDiff, shouldLoad]);
|
||||
}, [fileName, original, modified, language]);
|
||||
|
||||
const options = useMemo(() => ({
|
||||
theme: {
|
||||
@@ -363,45 +349,11 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
onLineSelected: handleSelectionChange,
|
||||
unsafeCSS: WEBKIT_SCROLL_FIX_CSS,
|
||||
}), [isDark, renderSideBySide, wrapLines, handleSelectionChange]);
|
||||
|
||||
// Show placeholder for large diffs
|
||||
if (isLargeDiff && !fileDiff) {
|
||||
const originalLines = (original || '').split('\n').length;
|
||||
const modifiedLines = (modified || '').split('\n').length;
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full min-h-[200px] p-6">
|
||||
<div className="flex flex-col items-center gap-3 text-center">
|
||||
<div className="p-3 rounded-full bg-muted/50">
|
||||
<RiEyeLine className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="typography-ui-header font-medium text-foreground">Large Diff</div>
|
||||
<div className="typography-meta text-muted-foreground mt-1">
|
||||
{originalLines + modifiedLines} lines
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShouldLoad(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 mt-2 rounded-lg bg-primary/10 text-primary hover:bg-primary/20 transition-colors typography-meta font-medium"
|
||||
>
|
||||
<RiArrowDownSLine className="size-4" />
|
||||
Load Diff
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// fileDiff should not be null here (handled by large diff placeholder above)
|
||||
if (!fileDiff) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Extracted Comment Interface Content for reuse in Portal or In-Flow
|
||||
const renderCommentContent = () => {
|
||||
if (!selection) return null;
|
||||
@@ -521,6 +473,8 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
: '16px'
|
||||
}}
|
||||
data-keyboard-avoid="true"
|
||||
data-comment-ui="true"
|
||||
ref={commentContainerRef}
|
||||
>
|
||||
{commentContent}
|
||||
</div>
|
||||
@@ -566,6 +520,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
}}
|
||||
data-keyboard-avoid="true"
|
||||
data-comment-ui="true"
|
||||
ref={commentContainerRef}
|
||||
>
|
||||
{commentContent}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user