mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
Improve file viewer browsing layout
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
ce22ad7632
commit
f2b869ecdc
7 files changed
+74
-27
No files matched your search
@@ -50,7 +50,7 @@ export function ArtifactFileChip({
|
||||
const content = (
|
||||
<>
|
||||
{showIcon ? <FileCode2 aria-hidden="true" className="h-3 w-3 shrink-0 opacity-70" /> : null}
|
||||
<span className="truncate max-w-[38ch]">{display}</span>
|
||||
<span className="max-w-full whitespace-normal break-all text-left">{display}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -75,6 +75,21 @@ describe("FileContentViewer", () => {
|
||||
expect(markup).toContain("break-words");
|
||||
});
|
||||
|
||||
it("reserves code gutter space for at least four digit line numbers", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<FileContentViewer
|
||||
content={content({
|
||||
content: {
|
||||
encoding: "utf8",
|
||||
data: "one\ntwo\nthree",
|
||||
},
|
||||
})}
|
||||
highlightedLine={null}
|
||||
/>,
|
||||
);
|
||||
expect(markup).toContain("calc(4ch + 2rem)");
|
||||
});
|
||||
|
||||
it("renders video previews with native controls", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<FileContentViewer
|
||||
|
||||
@@ -94,6 +94,11 @@ function basename(path: string): string {
|
||||
return idx < 0 ? path : path.slice(idx + 1);
|
||||
}
|
||||
|
||||
function dirname(path: string): string | null {
|
||||
const idx = path.lastIndexOf("/");
|
||||
return idx < 0 ? null : path.slice(0, idx);
|
||||
}
|
||||
|
||||
function middleTruncatePath(path: string, maxLen = 80): string {
|
||||
if (path.length <= maxLen) return path;
|
||||
const head = path.slice(0, Math.floor(maxLen / 2) - 1);
|
||||
@@ -267,7 +272,7 @@ export function FileContentViewer({ content, highlightedLine, onLoaded }: FileCo
|
||||
);
|
||||
}
|
||||
|
||||
const gutterWidth = `${Math.max(2, String(lines.length).length)}ch`;
|
||||
const gutterWidth = `calc(${Math.max(4, String(lines.length).length)}ch + 2rem)`;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -299,7 +304,7 @@ export function FileContentViewer({ content, highlightedLine, onLoaded }: FileCo
|
||||
isHighlighted &&
|
||||
"opacity-100 bg-[var(--paperclip-code-highlight-bg,rgba(250,204,21,0.12))] border-l-2 border-[var(--paperclip-code-highlight-border,rgb(234,179,8))]",
|
||||
)}
|
||||
style={{ width: gutterWidth }}
|
||||
style={{ width: gutterWidth, minWidth: gutterWidth }}
|
||||
>
|
||||
{lineNumber}
|
||||
</span>
|
||||
@@ -626,28 +631,45 @@ export function FileViewerSheet({
|
||||
{announcement}
|
||||
</div>
|
||||
{state ? (
|
||||
<FileViewerBody
|
||||
resolveQuery={resolveQuery}
|
||||
contentQuery={contentQuery}
|
||||
elapsedMs={elapsedMs}
|
||||
canPreview={canPreview}
|
||||
highlightedLine={state.line ?? null}
|
||||
onRetry={handleRetry}
|
||||
onSetAnnouncement={setAnnouncement}
|
||||
onFallbackToProject={
|
||||
state.workspace !== "project" && !state.projectId && !state.workspaceId
|
||||
? () =>
|
||||
viewer.open({
|
||||
path: state.path,
|
||||
line: state.line,
|
||||
column: state.column,
|
||||
workspace: "project",
|
||||
projectId: null,
|
||||
workspaceId: null,
|
||||
})
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<div className="flex min-h-0 flex-1">
|
||||
<aside className="hidden min-h-0 w-80 shrink-0 border-r border-border bg-muted/20 sm:flex">
|
||||
<WorkspaceFileBrowser
|
||||
key={`${state.projectId ?? ""}:${state.workspaceId ?? ""}:${dirname(state.path) ?? ""}`}
|
||||
issueId={issueId}
|
||||
companyId={companyId}
|
||||
onOpen={handleBrowseOpen}
|
||||
initialFolderPath={dirname(state.path)}
|
||||
initialProjectId={state.projectId}
|
||||
initialWorkspaceId={state.workspaceId}
|
||||
autoFocusSearch={false}
|
||||
className="min-h-0 flex-1 p-3"
|
||||
/>
|
||||
</aside>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<FileViewerBody
|
||||
resolveQuery={resolveQuery}
|
||||
contentQuery={contentQuery}
|
||||
elapsedMs={elapsedMs}
|
||||
canPreview={canPreview}
|
||||
highlightedLine={state.line ?? null}
|
||||
onRetry={handleRetry}
|
||||
onSetAnnouncement={setAnnouncement}
|
||||
onFallbackToProject={
|
||||
state.workspace !== "project" && !state.projectId && !state.workspaceId
|
||||
? () =>
|
||||
viewer.open({
|
||||
path: state.path,
|
||||
line: state.line,
|
||||
column: state.column,
|
||||
workspace: "project",
|
||||
projectId: null,
|
||||
workspaceId: null,
|
||||
})
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : browseMode ? (
|
||||
<WorkspaceFileBrowser
|
||||
issueId={issueId}
|
||||
|
||||
@@ -282,6 +282,8 @@ describe("MarkdownBody", () => {
|
||||
|
||||
expect(html).toContain('data-workspace-file-link="true"');
|
||||
expect(html).toContain('data-workspace-file-path="videos/90-days-paperclip/out/90-days-paperclip-1x1.mp4"');
|
||||
expect(html).toContain("videos/90-days-paperclip/out/90-days-paperclip-1x1.mp4");
|
||||
expect(html).not.toContain("max-w-[38ch]");
|
||||
expect(html).not.toContain("paperclip-markdown-issue-ref");
|
||||
expect(html).not.toContain('href="/issues/PAP-10306"');
|
||||
});
|
||||
|
||||
@@ -198,6 +198,12 @@ describe("WorkspaceFileBrowser", () => {
|
||||
expect(onOpen).toHaveBeenCalledWith({ path: "ui/src/pages/IssueDetail.tsx", workspace: "auto" });
|
||||
});
|
||||
|
||||
it("can render without autofocus when embedded beside a preview", () => {
|
||||
useQueryMock.mockReturnValue(ok(availableResponse([createItem()])));
|
||||
renderBrowser(vi.fn(), { autoFocusSearch: false });
|
||||
expect(container.querySelector("input")?.hasAttribute("autofocus")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not render a Recent/All toggle", () => {
|
||||
useQueryMock.mockReturnValue(ok(availableResponse([createItem()])));
|
||||
renderBrowser();
|
||||
|
||||
@@ -219,6 +219,7 @@ export interface WorkspaceFileBrowserProps {
|
||||
initialFolderPath?: string | null;
|
||||
initialProjectId?: string | null;
|
||||
initialWorkspaceId?: string | null;
|
||||
autoFocusSearch?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -230,6 +231,7 @@ export function WorkspaceFileBrowser({
|
||||
initialFolderPath,
|
||||
initialProjectId,
|
||||
initialWorkspaceId,
|
||||
autoFocusSearch = true,
|
||||
className,
|
||||
}: WorkspaceFileBrowserProps) {
|
||||
const [source, setSource] = useState<BrowserSource>(
|
||||
@@ -512,7 +514,7 @@ export function WorkspaceFileBrowser({
|
||||
aria-expanded={items.length > 0}
|
||||
aria-controls={items.length > 0 ? listboxId : undefined}
|
||||
aria-activedescendant={activeOptionId}
|
||||
autoFocus
|
||||
autoFocus={autoFocusSearch}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
className="h-8 pl-8 font-mono text-xs"
|
||||
|
||||
@@ -93,7 +93,7 @@ export function WorkspaceFileLink({
|
||||
? <FolderOpen aria-hidden="true" className="h-3 w-3 shrink-0 opacity-70" />
|
||||
: <FileCode2 aria-hidden="true" className="h-3 w-3 shrink-0 opacity-70" />
|
||||
) : null}
|
||||
<span className="truncate max-w-[38ch]">{display}</span>
|
||||
<span className="max-w-full whitespace-normal break-all text-left">{display}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user