Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 126 additions & 97 deletions apps/web/src/components/DiffPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DiffOptionsMenu } from "./diffs/DiffOptionsMenu";
import { RefreshIcon } from "~/components/ui/refresh-icon";
import { useAtomValue } from "@effect/atom-react";
import type { FileDiffContentsLoader, FileDiffMetadata } from "@pierre/diffs";
Expand Down Expand Up @@ -1146,132 +1147,160 @@ export default function DiffPanel({
</TooltipPopup>
</Tooltip>
)}
{diffFileKeys.length > 0 && (
<Tooltip>
<TooltipTrigger
render={
<Button
type="button"
size="icon-sm"
variant="ghost"
aria-label={allDiffFilesCollapsed ? "Expand all files" : "Collapse all files"}
onClick={toggleDiffFileCollapse}
<DiffOptionsMenu
wordWrap={wordWrap}
onWordWrapChange={setWordWrap}
ignoreWhitespace={diffIgnoreWhitespace}
onIgnoreWhitespaceChange={setDiffIgnoreWhitespace}
diffLayout={diffLayout}
onDiffLayoutChange={(value) => updateClientSettings({ diffLayout: value })}
files={
diffFileKeys.length > 0
? {
collapsed: allDiffFilesCollapsed,
onToggleCollapsed: toggleDiffFileCollapse,
treeOpen: fileTreeOpen,
onTreeOpenChange: setFileTreeOpen,
}
: null
}
testFiles={
testFileCount > 0
? {
hidden: hideTestFiles,
onHiddenChange: setHideTestFiles,
}
: null
}
/>
<div className="hidden items-center gap-1 @min-[36rem]/diff-toolbar:flex">
{diffFileKeys.length > 0 && (
<Tooltip>
<TooltipTrigger
render={
<Button
type="button"
size="icon-sm"
variant="ghost"
aria-label={allDiffFilesCollapsed ? "Expand all files" : "Collapse all files"}
onClick={toggleDiffFileCollapse}
/>
}
>
<MorphIcon
className="size-3.5"
icon={allDiffFilesCollapsed ? ChevronsUpDown : ChevronsDownUp}
/>
</TooltipTrigger>
<TooltipPopup side="top">
{allDiffFilesCollapsed ? "Expand all files" : "Collapse all files"}
</TooltipPopup>
</Tooltip>
)}
<ToggleGroup
aria-label="Diff layout"
className="shrink-0"
variant="segmented"
value={[diffLayout]}
onValueChange={(value) => {
const next = value[0];
if (next === "stacked" || next === "split") {
updateClientSettings({ diffLayout: next });
}
>
<MorphIcon
className="size-3.5"
icon={allDiffFilesCollapsed ? ChevronsUpDown : ChevronsDownUp}
/>
</TooltipTrigger>
<TooltipPopup side="top">
{allDiffFilesCollapsed ? "Expand all files" : "Collapse all files"}
</TooltipPopup>
</Tooltip>
)}
<ToggleGroup
aria-label="Diff layout"
className="shrink-0"
variant="segmented"
value={[diffLayout]}
onValueChange={(value) => {
const next = value[0];
if (next === "stacked" || next === "split") {
updateClientSettings({ diffLayout: next });
}
}}
>
<Toggle aria-label="Stacked diff view" value="stacked">
<Rows3Icon className="size-3.5" />
</Toggle>
<Toggle aria-label="Split diff view" value="split">
<Columns2Icon className="size-3.5" />
</Toggle>
</ToggleGroup>
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label={wordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"}
variant="ghost"
size="sm"
pressed={wordWrap}
onPressedChange={(pressed) => {
setWordWrap(Boolean(pressed));
}}
/>
}
>
<TextWrapIcon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">
{wordWrap ? "Disable line wrapping" : "Enable line wrapping"}
</TooltipPopup>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label={
diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"
}
variant="ghost"
size="sm"
pressed={diffIgnoreWhitespace}
onPressedChange={(pressed) => {
setDiffIgnoreWhitespace(Boolean(pressed));
}}
/>
}
}}
>
<PilcrowIcon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">
{diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
</TooltipPopup>
</Tooltip>
{testFileCount > 0 && (
<Toggle aria-label="Stacked diff view" value="stacked">
<Rows3Icon className="size-3.5" />
</Toggle>
<Toggle aria-label="Split diff view" value="split">
<Columns2Icon className="size-3.5" />
</Toggle>
</ToggleGroup>
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label="Hide test files"
aria-label={wordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"}
variant="ghost"
size="sm"
pressed={hideTestFiles}
onPressedChange={(pressed) => setHideTestFiles(Boolean(pressed))}
pressed={wordWrap}
onPressedChange={(pressed) => {
setWordWrap(Boolean(pressed));
}}
/>
}
>
<FlaskConicalOffIcon className="size-3.5" />
<TextWrapIcon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">
{hideTestFiles
? `Show ${testFileCount} hidden test ${testFileCount === 1 ? "file" : "files"}`
: "Hide test files"}
{wordWrap ? "Disable line wrapping" : "Enable line wrapping"}
</TooltipPopup>
</Tooltip>
)}
{diffFileKeys.length > 0 && (
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label={fileTreeOpen ? "Hide file tree" : "Show file tree"}
aria-label={
diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"
}
variant="ghost"
size="sm"
pressed={fileTreeOpen}
onPressedChange={(pressed) => setFileTreeOpen(Boolean(pressed))}
pressed={diffIgnoreWhitespace}
onPressedChange={(pressed) => {
setDiffIgnoreWhitespace(Boolean(pressed));
}}
/>
}
>
<FolderTreeIcon className="size-3.5" />
<PilcrowIcon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">
{fileTreeOpen ? "Hide file tree" : "Show file tree"}
{diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
</TooltipPopup>
</Tooltip>
)}
{testFileCount > 0 && (
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label="Hide test files"
variant="ghost"
size="sm"
pressed={hideTestFiles}
onPressedChange={(pressed) => setHideTestFiles(Boolean(pressed))}
/>
}
>
<FlaskConicalOffIcon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">
{hideTestFiles
? `Show ${testFileCount} hidden test ${testFileCount === 1 ? "file" : "files"}`
: "Hide test files"}
</TooltipPopup>
</Tooltip>
)}
{diffFileKeys.length > 0 && (
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label={fileTreeOpen ? "Hide file tree" : "Show file tree"}
variant="ghost"
size="sm"
pressed={fileTreeOpen}
onPressedChange={(pressed) => setFileTreeOpen(Boolean(pressed))}
/>
}
>
<FolderTreeIcon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">
{fileTreeOpen ? "Hide file tree" : "Show file tree"}
</TooltipPopup>
</Tooltip>
)}
</div>
</div>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/DiffPanelShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type DiffPanelMode = "inline" | "sheet" | "sidebar" | "embedded";
function getDiffPanelHeaderRowClassName(mode: DiffPanelMode) {
const shouldUseDragRegion = isElectron && mode !== "sheet" && mode !== "embedded";
return cn(
"flex items-center justify-between gap-2",
"@container/diff-toolbar flex items-center justify-between gap-2",
mode === "embedded" ? "px-2" : "px-4",
shouldUseDragRegion
? "drag-region h-[var(--workspace-topbar-height)] border-b border-border wco:pr-(--workspace-native-controls-inset)"
Expand Down
97 changes: 97 additions & 0 deletions apps/web/src/components/diffs/DiffOptionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Settings2Icon } from "lucide-react";

import { Button } from "../ui/button";
import {
Menu,
MenuCheckboxItem,
MenuItem,
MenuPopup,
MenuRadioGroup,
MenuRadioItem,
MenuSeparator,
MenuTrigger,
} from "../ui/menu";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";

export function DiffOptionsMenu({
wordWrap,
onWordWrapChange,
ignoreWhitespace,
onIgnoreWhitespaceChange,
diffLayout,
onDiffLayoutChange,
files,
testFiles,
}: {
wordWrap: boolean;
onWordWrapChange: (value: boolean) => void;
ignoreWhitespace: boolean;
onIgnoreWhitespaceChange: (value: boolean) => void;
diffLayout: "stacked" | "split";
onDiffLayoutChange: (value: "stacked" | "split") => void;
files: {
collapsed: boolean;
onToggleCollapsed: () => void;
treeOpen: boolean;
onTreeOpenChange: (value: boolean) => void;
} | null;
testFiles: { hidden: boolean; onHiddenChange: (value: boolean) => void } | null;
}) {
return (
<div className="shrink-0 @min-[36rem]/diff-toolbar:hidden">
<Menu>
<Tooltip>
<TooltipTrigger
render={
<MenuTrigger
render={<Button size="icon-sm" variant="ghost" />}
aria-label="Diff options"
/>
}
>
<Settings2Icon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">Diff options</TooltipPopup>
</Tooltip>
<MenuPopup align="end">
<MenuCheckboxItem checked={wordWrap} onCheckedChange={onWordWrapChange}>
Wrap lines
</MenuCheckboxItem>
<MenuCheckboxItem checked={ignoreWhitespace} onCheckedChange={onIgnoreWhitespaceChange}>
Hide whitespace changes
</MenuCheckboxItem>
<MenuSeparator />
<MenuRadioGroup
value={diffLayout}
onValueChange={(value) => {
if (value === "stacked" || value === "split") onDiffLayoutChange(value);
}}
>
<MenuRadioItem value="stacked" closeOnClick>
Stacked
</MenuRadioItem>
<MenuRadioItem value="split" closeOnClick>
Split
</MenuRadioItem>
</MenuRadioGroup>
{files || testFiles ? <MenuSeparator /> : null}
{files ? (
<MenuItem onClick={files.onToggleCollapsed}>
{files.collapsed ? "Expand all files" : "Collapse all files"}
</MenuItem>
) : null}
{testFiles ? (
<MenuCheckboxItem checked={testFiles.hidden} onCheckedChange={testFiles.onHiddenChange}>
Hide test files
</MenuCheckboxItem>
) : null}
{files ? (
<MenuCheckboxItem checked={files.treeOpen} onCheckedChange={files.onTreeOpenChange}>
Show file tree
</MenuCheckboxItem>
) : null}
</MenuPopup>
</Menu>
</div>
);
}
Loading