| ... | @@ -19,6 +19,7 @@ import { | ... | @@ -19,6 +19,7 @@ import { |
| 19 | MessageSquareOffIcon, | 19 | MessageSquareOffIcon, |
| 20 | PilcrowIcon, | 20 | PilcrowIcon, |
| 21 | Rows3Icon, | 21 | Rows3Icon, |
| | 22 | Settings2Icon, |
| 22 | TextWrapIcon, | 23 | TextWrapIcon, |
| 23 | TriangleAlertIcon, | 24 | TriangleAlertIcon, |
| 24 | } from "lucide-react"; | 25 | } from "lucide-react"; |
| ... | @@ -69,10 +70,12 @@ import { Checkbox } from "../ui/checkbox"; | ... | @@ -69,10 +70,12 @@ import { Checkbox } from "../ui/checkbox"; |
| 69 | import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible"; | 70 | import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible"; |
| 70 | import { | 71 | import { |
| 71 | DropdownMenu, | 72 | DropdownMenu, |
| | 73 | DropdownMenuCheckboxItem, |
| 72 | DropdownMenuContent, | 74 | DropdownMenuContent, |
| 73 | DropdownMenuItem, | 75 | DropdownMenuItem, |
| 74 | DropdownMenuRadioGroup, | 76 | DropdownMenuRadioGroup, |
| 75 | DropdownMenuRadioItem, | 77 | DropdownMenuRadioItem, |
| | 78 | DropdownMenuSeparator, |
| 76 | DropdownMenuTrigger, | 79 | DropdownMenuTrigger, |
| 77 | } from "../ui/menu"; | 80 | } from "../ui/menu"; |
| 78 | import { toastManager } from "../ui/toast"; | 81 | import { toastManager } from "../ui/toast"; |
| ... | @@ -1075,8 +1078,13 @@ function PullRequestCodeTab({ | ... | @@ -1075,8 +1078,13 @@ function PullRequestCodeTab({ |
| 1075 | } | 1078 | } |
| 1076 | }, [commit, onSelectedCommitChange, selectedCommit]); | 1079 | }, [commit, onSelectedCommitChange, selectedCommit]); |
| 1077 | const scopeLabel = selectedCommit ? selectedCommit.messageHeadline : "All commits"; | 1080 | const scopeLabel = selectedCommit ? selectedCommit.messageHeadline : "All commits"; |
| | 1081 | const changeIgnoreWhitespace = (next: boolean) => { |
| | 1082 | setIgnoreWhitespace(next); |
| | 1083 | setDraft(null); |
| | 1084 | setSelectedLines(null); |
| | 1085 | }; |
| 1078 | const toolbar = ( | 1086 | const toolbar = ( |
| 1079 | <div className="flex h-10 min-h-10 shrink-0 items-center justify-between gap-2 border-b border-border/60 bg-background px-4 text-xs text-muted-foreground"> | 1087 | <div className="@container flex h-10 min-h-10 shrink-0 items-center justify-between gap-2 border-b border-border/60 bg-background px-4 text-xs text-muted-foreground"> |
| 1080 | <div className="flex min-w-0 flex-1 items-center gap-3"> | 1088 | <div className="flex min-w-0 flex-1 items-center gap-3"> |
| 1081 | {/* A host that reports no commits has nothing to scope by, and a dropdown whose only | 1089 | {/* A host that reports no commits has nothing to scope by, and a dropdown whose only |
| 1082 | entry is the scope already showing is a control that does nothing. */} | 1090 | entry is the scope already showing is a control that does nothing. */} |
| ... | @@ -1133,7 +1141,7 @@ function PullRequestCodeTab({ | ... | @@ -1133,7 +1141,7 @@ function PullRequestCodeTab({ |
| 1133 | ) : null} | 1141 | ) : null} |
| 1134 | {/* One count, and the caveats as icons that carry their own words. Spelled out they | 1142 | {/* One count, and the caveats as icons that carry their own words. Spelled out they |
| 1135 | competed for a strip this narrow and every one of them truncated to nothing. */} | 1143 | competed for a strip this narrow and every one of them truncated to nothing. */} |
| 1136 | <PullRequestMetaLine className="shrink-0"> | 1144 | <PullRequestMetaLine> |
| 1137 | <span className="shrink-0 tabular-nums"> | 1145 | <span className="shrink-0 tabular-nums"> |
| 1138 | {files.length} {files.length === 1 ? "file" : "files"} | 1146 | {files.length} {files.length === 1 ? "file" : "files"} |
| 1139 | {nextCursor === null ? "" : "+"} | 1147 | {nextCursor === null ? "" : "+"} |
| ... | @@ -1224,30 +1232,113 @@ function PullRequestCodeTab({ | ... | @@ -1224,30 +1232,113 @@ function PullRequestCodeTab({ |
| 1224 | </PullRequestMetaLine> | 1232 | </PullRequestMetaLine> |
| 1225 | </div> | 1233 | </div> |
| 1226 | <div className="flex shrink-0 items-center gap-1"> | 1234 | <div className="flex shrink-0 items-center gap-1"> |
| 1227 | <Tooltip> | 1235 | <DropdownMenu> |
| 1228 | <TooltipTrigger | 1236 | <Tooltip> |
| 1229 | render={ | 1237 | <TooltipTrigger |
| 1230 | <Toggle | 1238 | render={ |
| 1231 | aria-label={ | 1239 | <DropdownMenuTrigger |
| 1232 | ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes" | 1240 | render={<Button size="icon-sm" variant="ghost" />} |
| | 1241 | className="@lg:hidden" |
| | 1242 | aria-label="Diff options" |
| | 1243 | /> |
| | 1244 | } |
| | 1245 | > |
| | 1246 | <Settings2Icon className="size-3.5" /> |
| | 1247 | </TooltipTrigger> |
| | 1248 | <TooltipPopup side="top">Diff options</TooltipPopup> |
| | 1249 | </Tooltip> |
| | 1250 | <DropdownMenuContent align="end"> |
| | 1251 | <DropdownMenuCheckboxItem |
| | 1252 | checked={ignoreWhitespace} |
| | 1253 | onCheckedChange={(checked) => changeIgnoreWhitespace(checked)} |
| | 1254 | > |
| | 1255 | Hide whitespace changes |
| | 1256 | </DropdownMenuCheckboxItem> |
| | 1257 | <DropdownMenuCheckboxItem |
| | 1258 | checked={wordWrap} |
| | 1259 | onCheckedChange={(checked) => setWordWrap(checked)} |
| | 1260 | > |
| | 1261 | Wrap lines |
| | 1262 | </DropdownMenuCheckboxItem> |
| | 1263 | <DropdownMenuSeparator /> |
| | 1264 | <DropdownMenuRadioGroup |
| | 1265 | value={diffLayout} |
| | 1266 | onValueChange={(value) => { |
| | 1267 | if (value === "stacked" || value === "split") { |
| | 1268 | updateClientSettings({ diffLayout: value }); |
| 1233 | } | 1269 | } |
| 1234 | variant="ghost" | 1270 | }} |
| 1235 | size="sm" | 1271 | > |
| 1236 | pressed={ignoreWhitespace} | 1272 | <DropdownMenuRadioItem value="stacked" closeOnClick> |
| 1237 | onPressedChange={(pressed) => { | 1273 | Stacked |
| 1238 | setIgnoreWhitespace(Boolean(pressed)); | 1274 | </DropdownMenuRadioItem> |
| 1239 | setDraft(null); | 1275 | <DropdownMenuRadioItem value="split" closeOnClick> |
| 1240 | setSelectedLines(null); | 1276 | Split |
| 1241 | }} | 1277 | </DropdownMenuRadioItem> |
| 1242 | /> | 1278 | </DropdownMenuRadioGroup> |
| 1243 | } | 1279 | </DropdownMenuContent> |
| | 1280 | </DropdownMenu> |
| | 1281 | <div className="hidden items-center gap-1 @lg:flex"> |
| | 1282 | <Tooltip> |
| | 1283 | <TooltipTrigger |
| | 1284 | render={ |
| | 1285 | <Toggle |
| | 1286 | aria-label={ |
| | 1287 | ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes" |
| | 1288 | } |
| | 1289 | variant="ghost" |
| | 1290 | size="sm" |
| | 1291 | pressed={ignoreWhitespace} |
| | 1292 | onPressedChange={(pressed) => changeIgnoreWhitespace(Boolean(pressed))} |
| | 1293 | /> |
| | 1294 | } |
| | 1295 | > |
| | 1296 | <PilcrowIcon className="size-3.5" /> |
| | 1297 | </TooltipTrigger> |
| | 1298 | <TooltipPopup side="top"> |
| | 1299 | {ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"} |
| | 1300 | </TooltipPopup> |
| | 1301 | </Tooltip> |
| | 1302 | <ToggleGroup |
| | 1303 | aria-label="Diff layout" |
| | 1304 | className="shrink-0" |
| | 1305 | variant="segmented" |
| | 1306 | value={[diffLayout]} |
| | 1307 | onValueChange={(value) => { |
| | 1308 | const next = value[0]; |
| | 1309 | if (next === "stacked" || next === "split") { |
| | 1310 | updateClientSettings({ diffLayout: next }); |
| | 1311 | } |
| | 1312 | }} |
| 1244 | > | 1313 | > |
| 1245 | <PilcrowIcon className="size-3.5" /> | 1314 | <Toggle aria-label="Stacked diff view" value="stacked"> |
| 1246 | </TooltipTrigger> | 1315 | <Rows3Icon className="size-3.5" /> |
| 1247 | <TooltipPopup side="top"> | 1316 | </Toggle> |
| 1248 | {ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"} | 1317 | <Toggle aria-label="Split diff view" value="split"> |
| 1249 | </TooltipPopup> | 1318 | <Columns2Icon className="size-3.5" /> |
| 1250 | </Tooltip> | 1319 | </Toggle> |
| | 1320 | </ToggleGroup> |
| | 1321 | <Tooltip> |
| | 1322 | <TooltipTrigger |
| | 1323 | render={ |
| | 1324 | <Toggle |
| | 1325 | aria-label={wordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"} |
| | 1326 | variant="ghost" |
| | 1327 | size="sm" |
| | 1328 | pressed={wordWrap} |
| | 1329 | onPressedChange={(pressed) => { |
| | 1330 | setWordWrap(Boolean(pressed)); |
| | 1331 | }} |
| | 1332 | /> |
| | 1333 | } |
| | 1334 | > |
| | 1335 | <TextWrapIcon className="size-3.5" /> |
| | 1336 | </TooltipTrigger> |
| | 1337 | <TooltipPopup side="top"> |
| | 1338 | {wordWrap ? "Disable line wrapping" : "Enable line wrapping"} |
| | 1339 | </TooltipPopup> |
| | 1340 | </Tooltip> |
| | 1341 | </div> |
| 1251 | {fileKeys.length > 0 ? ( | 1342 | {fileKeys.length > 0 ? ( |
| 1252 | <Tooltip> | 1343 | <Tooltip> |
| 1253 | <TooltipTrigger | 1344 | <TooltipTrigger |
| ... | @@ -1271,45 +1362,6 @@ function PullRequestCodeTab({ | ... | @@ -1271,45 +1362,6 @@ function PullRequestCodeTab({ |
| 1271 | </TooltipPopup> | 1362 | </TooltipPopup> |
| 1272 | </Tooltip> | 1363 | </Tooltip> |
| 1273 | ) : null} | 1364 | ) : null} |
| 1274 | <ToggleGroup | | |
| 1275 | aria-label="Diff layout" | | |
| 1276 | className="shrink-0" | | |
| 1277 | variant="segmented" | | |
| 1278 | value={[diffLayout]} | | |
| 1279 | onValueChange={(value) => { | | |
| 1280 | const next = value[0]; | | |
| 1281 | if (next === "stacked" || next === "split") { | | |
| 1282 | updateClientSettings({ diffLayout: next }); | | |
| 1283 | } | | |
| 1284 | }} | | |
| 1285 | > | | |
| 1286 | <Toggle aria-label="Stacked diff view" value="stacked"> | | |
| 1287 | <Rows3Icon className="size-3.5" /> | | |
| 1288 | </Toggle> | | |
| 1289 | <Toggle aria-label="Split diff view" value="split"> | | |
| 1290 | <Columns2Icon className="size-3.5" /> | | |
| 1291 | </Toggle> | | |
| 1292 | </ToggleGroup> | | |
| 1293 | <Tooltip> | | |
| 1294 | <TooltipTrigger | | |
| 1295 | render={ | | |
| 1296 | <Toggle | | |
| 1297 | aria-label={wordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"} | | |
| 1298 | variant="ghost" | | |
| 1299 | size="sm" | | |
| 1300 | pressed={wordWrap} | | |
| 1301 | onPressedChange={(pressed) => { | | |
| 1302 | setWordWrap(Boolean(pressed)); | | |
| 1303 | }} | | |
| 1304 | /> | | |
| 1305 | } | | |
| 1306 | > | | |
| 1307 | <TextWrapIcon className="size-3.5" /> | | |
| 1308 | </TooltipTrigger> | | |
| 1309 | <TooltipPopup side="top"> | | |
| 1310 | {wordWrap ? "Disable line wrapping" : "Enable line wrapping"} | | |
| 1311 | </TooltipPopup> | | |
| 1312 | </Tooltip> | | |
| 1313 | {fileKeys.length > 0 ? ( | 1365 | {fileKeys.length > 0 ? ( |
| 1314 | <Tooltip> | 1366 | <Tooltip> |
| 1315 | <TooltipTrigger | 1367 | <TooltipTrigger |