Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
70 changes: 66 additions & 4 deletions apps/web/src/components/pullRequest/PullRequestCodeTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ChevronDownIcon,
ChevronRightIcon,
Columns2Icon,
EyeOffIcon,
FolderTreeIcon,
InfoIcon,
MessageSquareOffIcon,
Expand Down Expand Up @@ -107,6 +108,7 @@ type ReviewAnnotation = DiffLineAnnotation<ReviewAnnotationGroup>;
const COMMIT_PAGE_SIZE = 10;

const PULL_REQUEST_FILE_TREE_STORAGE_KEY = "t3code.pullRequestFileTreeOpen";
const PULL_REQUEST_HIDE_VIEWED_FILES_STORAGE_KEY = "t3code.pullRequestHideViewedFiles";

/** One answer from the host: a whole number of files, and where the next one carries on. */
interface DiffSlice {
Expand Down Expand Up @@ -239,6 +241,11 @@ function PullRequestCodeTab({
false,
Schema.Boolean,
);
const [hideViewedFiles, setHideViewedFiles] = useLocalStorage(
PULL_REQUEST_HIDE_VIEWED_FILES_STORAGE_KEY,
false,
Schema.Boolean,
);
const [selectedLines, setSelectedLines] = useState<{
id: string;
range: SelectedLineRange;
Expand Down Expand Up @@ -426,6 +433,15 @@ function PullRequestCodeTab({
isViewed: isFileViewed,
isStale: isFileViewedStale,
} = filesViewed;
// Only where the host keeps viewed marks; anywhere else every file stays listed. A file pushed
// to since it was ticked no longer reads as viewed, so it comes back on its own.
const hidingViewedFiles = hideViewedFiles && filesViewedEnabled;
const visibleFiles = useMemo(
() =>
hidingViewedFiles ? files.filter((file) => !isFileViewed(resolveFileDiffPath(file))) : files,
[files, hidingViewedFiles, isFileViewed],
);
const hiddenViewedFileCount = files.length - visibleFiles.length;
// The button goes around the host's cache, so everything the tab reads from it starts over:
// the diff from its first page, and with it the ticks, which a push since the last read can
// have marked as standing against an older version of the file.
Expand Down Expand Up @@ -558,9 +574,16 @@ function PullRequestCodeTab({
[commit, detail.reviewThreads, draft, files, pendingComments, placedThreadIds],
);

// Filtered after the annotations rather than before, so a tick never recomputes them.
const shownAnnotatedFiles = useMemo(
() =>
hidingViewedFiles ? annotatedFiles.filter(({ path }) => !isFileViewed(path)) : annotatedFiles,
Comment thread
alexito4 marked this conversation as resolved.
Outdated
[annotatedFiles, hidingViewedFiles, isFileViewed],
);

const items = useMemo<CodeViewDiffItem<ReviewAnnotationGroup>[]>(
() =>
annotatedFiles.map(({ fileKey, path, fileDiff, annotations, annotationsVersion }) => {
shownAnnotatedFiles.map(({ fileKey, path, fileDiff, annotations, annotationsVersion }) => {
const collapsed = isFileDiffCollapsed(fileKey, effectiveFoldOverride, toggledFiles);
// Ticking a file that is already folded changes no fold, so without this the box on
// screen would keep saying the opposite of what the count says.
Expand All @@ -577,7 +600,7 @@ function PullRequestCodeTab({
};
}),
[
annotatedFiles,
shownAnnotatedFiles,
filesViewedEnabled,
effectiveFoldOverride,
isFileViewed,
Expand All @@ -600,7 +623,7 @@ function PullRequestCodeTab({
[items],
);
const allFilesCollapsed = areAllDiffFilesCollapsed(fileKeys, collapsedFileKeys);
const fileTreeEntries = useMemo(() => diffFileTreeEntries(files), [files]);
const fileTreeEntries = useMemo(() => diffFileTreeEntries(visibleFiles), [visibleFiles]);

// A failed slice must not be asked for again on its own. The files already loaded keep the
// sentinel on screen, so re-arming it after a failure would request the same slice forever.
Expand Down Expand Up @@ -1119,7 +1142,8 @@ function PullRequestCodeTab({
competed for a strip this narrow and every one of them truncated to nothing. */}
<PullRequestMetaLine className="shrink-0">
<span className="shrink-0 tabular-nums">
{files.length} {files.length === 1 ? "file" : "files"}
{hiddenViewedFileCount > 0 ? `${visibleFiles.length} of ${files.length}` : files.length}{" "}
{files.length === 1 ? "file" : "files"}
{nextCursor === null ? "" : "+"}
</span>
{filesViewed.enabled && files.length > 0 ? (
Expand Down Expand Up @@ -1232,6 +1256,28 @@ function PullRequestCodeTab({
{ignoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
</TooltipPopup>
</Tooltip>
{/* Keyed on every file rather than the ones on screen, so the way back stays put once the
last unread file has been ticked and the list is empty. */}
{filesViewedEnabled && files.length > 0 ? (
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label={hideViewedFiles ? "Show viewed files" : "Hide viewed files"}
variant="ghost"
size="sm"
pressed={hideViewedFiles}
onPressedChange={(pressed) => setHideViewedFiles(Boolean(pressed))}
/>
}
>
<EyeOffIcon className="size-3.5" />
</TooltipTrigger>
<TooltipPopup side="top">
{hideViewedFiles ? "Show viewed files" : "Hide viewed files"}
</TooltipPopup>
</Tooltip>
) : null}
{fileKeys.length > 0 ? (
<Tooltip>
<TooltipTrigger
Expand Down Expand Up @@ -1361,6 +1407,22 @@ function PullRequestCodeTab({
);
}

// Nothing on screen because everything is read, which is not the same as nothing changed.
if (items.length === 0 && nextCursor === null && hiddenViewedFileCount > 0) {
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Outdated
return withToolbar(
<div className="flex flex-col items-start gap-3 px-4 py-5">
<p className="text-sm text-muted-foreground">
{hiddenViewedFileCount === 1
? "The only file is viewed."
: `All ${hiddenViewedFileCount} files are viewed.`}
</p>
<Button type="button" size="sm" variant="outline" onClick={() => setHideViewedFiles(false)}>
Show viewed files
</Button>
</div>,
);
}

if (items.length === 0 && nextCursor === null) {
return withToolbar(
<p className="px-4 py-5 text-sm text-muted-foreground">
Expand Down
4 changes: 4 additions & 0 deletions docs/user/source-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ Tick a file off in the **Code** tab once you have read it and it collapses; the
running count. A tick belongs to the pull request rather than to a commit, so scoping the tab to a
single commit keeps them. A file pushed to after you cleared it comes back marked **Changed**.

Turn on **Hide viewed files** (the crossed-out eye in the toolbar) to list only the files you still
have to read, in the diff and in the file tree. Ticking a file then takes it out of the list, and a
file that changes after you viewed it comes back on its own. The setting is remembered.

On GitHub these are GitHub's own viewed marks, so a review carries between T3 Code and github.com
in either direction. Forgejo, GitLab, Bitbucket, and Azure DevOps expose no record T3 Code can read, so the
server you are connected to keeps them instead: they follow you across the apps connected to that
Expand Down
Loading