@reviewflow/ui 6.25.0 → 6.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -2
- package/src/board/pull-request-row.tsx +57 -18
- package/src/components/branch-diff/diff-change-mark.tsx +86 -0
- package/src/components/branch-diff/use-collapsed-files.ts +3 -0
- package/src/components/checkout-sync-buttons.tsx +76 -0
- package/src/components/checkout-view.tsx +789 -0
- package/src/components/commit-list.tsx +172 -0
- package/src/components/diff-viewer.tsx +1 -0
- package/src/components/omitted-files.tsx +32 -0
- package/src/components/pull-request-commits.tsx +4 -91
- package/src/components/pull-request-diff-view.tsx +1 -25
- package/src/components/pull-request-diff-viewer.tsx +145 -37
- package/src/components/pull-request-panel.tsx +46 -23
- package/src/components/pull-request-repo-name.tsx +27 -0
- package/src/components/pull-request-screen.tsx +34 -26
- package/src/components/remove-worktree-button.tsx +233 -0
- package/src/components/repo-overview.tsx +615 -0
- package/src/index.ts +44 -0
- package/src/prStatus.ts +8 -1
- package/src/services/checkoutSummary.ts +32 -0
- package/src/slots.tsx +8 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { PullRequestCommit } from "@reviewflow/sdk";
|
|
2
|
+
import { Button, Code, PressableListItem, Text, View } from "alouette";
|
|
3
|
+
import { ArrowLeftRegularIcon } from "alouette-icons/phosphor-icons/ArrowLeft";
|
|
4
|
+
import { CaretDownRegularIcon } from "alouette-icons/phosphor-icons/CaretDown";
|
|
5
|
+
import { CaretUpRegularIcon } from "alouette-icons/phosphor-icons/CaretUp";
|
|
6
|
+
import type { ReactNode } from "react";
|
|
7
|
+
import { formatDataAge } from "../services/formatDataAge.ts";
|
|
8
|
+
import { PersonAvatar } from "./pull-request-conversation-person.tsx";
|
|
9
|
+
import { TooltipIconButton } from "./tooltip-icon-button.tsx";
|
|
10
|
+
|
|
11
|
+
const shortSha = (oid: string): string => oid.slice(0, 7);
|
|
12
|
+
|
|
13
|
+
const dayOf = (commit: PullRequestCommit): string =>
|
|
14
|
+
new Date(commit.committedAt).toLocaleDateString([], {
|
|
15
|
+
year: "numeric",
|
|
16
|
+
month: "long",
|
|
17
|
+
day: "numeric",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
interface CommitRowProps<Commit extends PullRequestCommit> {
|
|
21
|
+
commit: Commit;
|
|
22
|
+
onOpen: () => void;
|
|
23
|
+
extra: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A link, as a board row is: it opens the commit's own diff. */
|
|
27
|
+
function CommitRow<Commit extends PullRequestCommit>({
|
|
28
|
+
commit,
|
|
29
|
+
onOpen,
|
|
30
|
+
extra,
|
|
31
|
+
}: CommitRowProps<Commit>): ReactNode {
|
|
32
|
+
const login = commit.author?.login ?? "unknown";
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<PressableListItem
|
|
36
|
+
role="link"
|
|
37
|
+
aria-label={`Show the changes of ${commit.headline}`}
|
|
38
|
+
onPress={onOpen}
|
|
39
|
+
>
|
|
40
|
+
<View className="flex-row items-center gap-sm">
|
|
41
|
+
<PersonAvatar person={commit.author} />
|
|
42
|
+
<View className="min-w-0 flex-1">
|
|
43
|
+
<Text className="text-sm font-body-bold" numberOfLines={1}>
|
|
44
|
+
{commit.headline}
|
|
45
|
+
</Text>
|
|
46
|
+
<Text className="text-xs text-muted" numberOfLines={1}>
|
|
47
|
+
{`${login} committed ${formatDataAge(commit.committedAt)}`}
|
|
48
|
+
</Text>
|
|
49
|
+
</View>
|
|
50
|
+
{extra}
|
|
51
|
+
<Code className="text-xs">{shortSha(commit.oid)}</Code>
|
|
52
|
+
</View>
|
|
53
|
+
</PressableListItem>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CommitListProps<Commit extends PullRequestCommit> {
|
|
58
|
+
/** in the order to show them; a day heads each run of commits made that day */
|
|
59
|
+
commits: Commit[];
|
|
60
|
+
onOpen: (oid: string) => void;
|
|
61
|
+
/** beside the commit's short sha, e.g. a badge */
|
|
62
|
+
renderExtra?: (commit: Commit) => ReactNode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Commits grouped by day as on GitHub; pressing one opens its own diff. */
|
|
66
|
+
export function CommitList<Commit extends PullRequestCommit>({
|
|
67
|
+
commits,
|
|
68
|
+
onOpen,
|
|
69
|
+
renderExtra,
|
|
70
|
+
}: CommitListProps<Commit>): ReactNode {
|
|
71
|
+
const days = new Map<string, Commit[]>();
|
|
72
|
+
for (const commit of commits) {
|
|
73
|
+
const day = dayOf(commit);
|
|
74
|
+
const ofDay = days.get(day);
|
|
75
|
+
if (ofDay === undefined) days.set(day, [commit]);
|
|
76
|
+
else ofDay.push(commit);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<View className="gap-m">
|
|
81
|
+
{[...days].map(([day, ofDay]) => (
|
|
82
|
+
<View key={day} className="gap-xs">
|
|
83
|
+
<Text className="text-xs font-body-bold text-muted">{day}</Text>
|
|
84
|
+
{ofDay.map((commit) => (
|
|
85
|
+
<CommitRow
|
|
86
|
+
key={commit.oid}
|
|
87
|
+
commit={commit}
|
|
88
|
+
extra={renderExtra?.(commit)}
|
|
89
|
+
onOpen={() => {
|
|
90
|
+
onOpen(commit.oid);
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
))}
|
|
94
|
+
</View>
|
|
95
|
+
))}
|
|
96
|
+
</View>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Steps to the neighbouring commits of the list; null at its end. */
|
|
101
|
+
export interface CommitSteps {
|
|
102
|
+
onNewer: (() => void) | null;
|
|
103
|
+
onOlder: (() => void) | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function CommitStepButtons({ onNewer, onOlder }: CommitSteps): ReactNode {
|
|
107
|
+
return (
|
|
108
|
+
<View className="flex-row items-center gap-xxs">
|
|
109
|
+
<TooltipIconButton
|
|
110
|
+
icon={<CaretUpRegularIcon />}
|
|
111
|
+
size="sm"
|
|
112
|
+
aria-label="Newer commit"
|
|
113
|
+
disabledReason={onNewer === null ? "This is the newest commit" : null}
|
|
114
|
+
onPress={() => {
|
|
115
|
+
onNewer?.();
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
<TooltipIconButton
|
|
119
|
+
icon={<CaretDownRegularIcon />}
|
|
120
|
+
size="sm"
|
|
121
|
+
aria-label="Older commit"
|
|
122
|
+
disabledReason={onOlder === null ? "This is the oldest commit" : null}
|
|
123
|
+
onPress={() => {
|
|
124
|
+
onOlder?.();
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
</View>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface CommitHeaderProps {
|
|
132
|
+
commit: PullRequestCommit;
|
|
133
|
+
onBack: () => void;
|
|
134
|
+
/** absent: the commit cannot be stepped from */
|
|
135
|
+
steps?: CommitSteps;
|
|
136
|
+
/** beside the commit's short sha, e.g. a badge */
|
|
137
|
+
extra?: ReactNode;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Above a commit's own diff: the way back to the list, its message and its sha. */
|
|
141
|
+
export function CommitHeader({
|
|
142
|
+
commit,
|
|
143
|
+
onBack,
|
|
144
|
+
steps,
|
|
145
|
+
extra,
|
|
146
|
+
}: CommitHeaderProps): ReactNode {
|
|
147
|
+
return (
|
|
148
|
+
<View className="gap-m">
|
|
149
|
+
<View className="flex-row flex-wrap items-center gap-sm">
|
|
150
|
+
<Button
|
|
151
|
+
text="Back to commits"
|
|
152
|
+
icon={<ArrowLeftRegularIcon />}
|
|
153
|
+
accent="neutral"
|
|
154
|
+
variant="tonal"
|
|
155
|
+
size="sm"
|
|
156
|
+
onPress={onBack}
|
|
157
|
+
/>
|
|
158
|
+
<Text className="min-w-0 flex-1 text-sm font-body-bold">
|
|
159
|
+
{commit.headline}
|
|
160
|
+
</Text>
|
|
161
|
+
{extra}
|
|
162
|
+
<Code className="text-xs">{shortSha(commit.oid)}</Code>
|
|
163
|
+
{steps === undefined ? null : <CommitStepButtons {...steps} />}
|
|
164
|
+
</View>
|
|
165
|
+
{commit.body.trim() === "" ? null : (
|
|
166
|
+
<Text className="select-auto font-mono text-xs text-muted">
|
|
167
|
+
{commit.body}
|
|
168
|
+
</Text>
|
|
169
|
+
)}
|
|
170
|
+
</View>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DiffViewer as default } from "./pull-request-diff-viewer.tsx";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DiffOmittedFile } from "@reviewflow/sdk";
|
|
2
|
+
import { Code, Text, View } from "alouette";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
const omissionLabel = (reason: DiffOmittedFile["reason"]): string => {
|
|
6
|
+
if (reason === "binary") return "binary";
|
|
7
|
+
if (reason === "tooLarge") return "too large";
|
|
8
|
+
if (reason === "whitespace") return "only whitespace changed";
|
|
9
|
+
return "over the diff budget";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface OmittedFilesProps {
|
|
13
|
+
omitted: DiffOmittedFile[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The files a diff left out and why; nothing when it left none out. */
|
|
17
|
+
export function OmittedFiles({ omitted }: OmittedFilesProps): ReactNode {
|
|
18
|
+
if (omitted.length === 0) return null;
|
|
19
|
+
return (
|
|
20
|
+
<View className="gap-xxs">
|
|
21
|
+
<Text className="text-xs font-body-bold text-muted">
|
|
22
|
+
{`Left out (${String(omitted.length)})`}
|
|
23
|
+
</Text>
|
|
24
|
+
{omitted.map(({ path, reason }) => (
|
|
25
|
+
<View key={path} className="flex-row flex-wrap items-center gap-xs">
|
|
26
|
+
<Code className="text-xs">{path}</Code>
|
|
27
|
+
<Text className="text-xs text-muted">{omissionLabel(reason)}</Text>
|
|
28
|
+
</View>
|
|
29
|
+
))}
|
|
30
|
+
</View>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -4,20 +4,11 @@ import type {
|
|
|
4
4
|
PullRequestEdit,
|
|
5
5
|
} from "@reviewflow/sdk";
|
|
6
6
|
import { pluralOf } from "@reviewflow/sdk";
|
|
7
|
-
import {
|
|
8
|
-
Button,
|
|
9
|
-
Code,
|
|
10
|
-
InfoMessage,
|
|
11
|
-
PressableListItem,
|
|
12
|
-
Text,
|
|
13
|
-
View,
|
|
14
|
-
} from "alouette";
|
|
15
|
-
import { ArrowLeftRegularIcon } from "alouette-icons/phosphor-icons/ArrowLeft";
|
|
7
|
+
import { InfoMessage, Text, View } from "alouette";
|
|
16
8
|
import type { ReactNode } from "react";
|
|
17
9
|
import { useState } from "react";
|
|
18
10
|
import { usePullRequestDataSource } from "../dataSource.ts";
|
|
19
|
-
import {
|
|
20
|
-
import { PersonAvatar } from "./pull-request-conversation-person.tsx";
|
|
11
|
+
import { CommitHeader, CommitList } from "./commit-list.tsx";
|
|
21
12
|
import { PullRequestDiffView } from "./pull-request-diff-view.tsx";
|
|
22
13
|
import {
|
|
23
14
|
PullRequestPartError,
|
|
@@ -37,46 +28,6 @@ export interface PullRequestCommitsProps {
|
|
|
37
28
|
onOpenCommitChange?: (oid: string | null) => void;
|
|
38
29
|
}
|
|
39
30
|
|
|
40
|
-
const shortSha = (oid: string): string => oid.slice(0, 7);
|
|
41
|
-
|
|
42
|
-
const dayOf = (commit: PullRequestCommit): string =>
|
|
43
|
-
new Date(commit.committedAt).toLocaleDateString([], {
|
|
44
|
-
year: "numeric",
|
|
45
|
-
month: "long",
|
|
46
|
-
day: "numeric",
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
interface CommitRowProps {
|
|
50
|
-
commit: PullRequestCommit;
|
|
51
|
-
onOpen: () => void;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** A link, as a board row is: it opens the commit's own page, which a url keeps. */
|
|
55
|
-
function CommitRow({ commit, onOpen }: CommitRowProps): ReactNode {
|
|
56
|
-
const login = commit.author?.login ?? "unknown";
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<PressableListItem
|
|
60
|
-
role="link"
|
|
61
|
-
aria-label={`Show the changes of ${commit.headline}`}
|
|
62
|
-
onPress={onOpen}
|
|
63
|
-
>
|
|
64
|
-
<View className="flex-row items-center gap-sm">
|
|
65
|
-
<PersonAvatar person={commit.author} />
|
|
66
|
-
<View className="min-w-0 flex-1">
|
|
67
|
-
<Text className="text-sm font-body-bold" numberOfLines={1}>
|
|
68
|
-
{commit.headline}
|
|
69
|
-
</Text>
|
|
70
|
-
<Text className="text-xs text-muted" numberOfLines={1}>
|
|
71
|
-
{`${login} committed ${formatDataAge(commit.committedAt)}`}
|
|
72
|
-
</Text>
|
|
73
|
-
</View>
|
|
74
|
-
<Code className="text-xs">{shortSha(commit.oid)}</Code>
|
|
75
|
-
</View>
|
|
76
|
-
</PressableListItem>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
31
|
interface CommitDiffProps {
|
|
81
32
|
detail: PullRequestDetailResponse;
|
|
82
33
|
commit: PullRequestCommit;
|
|
@@ -99,25 +50,7 @@ function CommitDiff({
|
|
|
99
50
|
const dataSource = usePullRequestDataSource();
|
|
100
51
|
return (
|
|
101
52
|
<View className="gap-m">
|
|
102
|
-
<
|
|
103
|
-
<Button
|
|
104
|
-
text="Back to commits"
|
|
105
|
-
icon={<ArrowLeftRegularIcon />}
|
|
106
|
-
accent="neutral"
|
|
107
|
-
variant="tonal"
|
|
108
|
-
size="sm"
|
|
109
|
-
onPress={onBack}
|
|
110
|
-
/>
|
|
111
|
-
<Text className="min-w-0 flex-1 text-sm font-body-bold">
|
|
112
|
-
{commit.headline}
|
|
113
|
-
</Text>
|
|
114
|
-
<Code className="text-xs">{shortSha(commit.oid)}</Code>
|
|
115
|
-
</View>
|
|
116
|
-
{commit.body.trim() === "" ? null : (
|
|
117
|
-
<Text className="select-auto font-mono text-xs text-muted">
|
|
118
|
-
{commit.body}
|
|
119
|
-
</Text>
|
|
120
|
-
)}
|
|
53
|
+
<CommitHeader commit={commit} onBack={onBack} />
|
|
121
54
|
{commit.parentOid === null ? (
|
|
122
55
|
<InfoMessage>
|
|
123
56
|
This is a root commit with no parent: its changes cannot be shown.
|
|
@@ -185,33 +118,13 @@ export function PullRequestCommits({
|
|
|
185
118
|
);
|
|
186
119
|
}
|
|
187
120
|
|
|
188
|
-
const days = new Map<string, PullRequestCommit[]>();
|
|
189
|
-
for (const commit of listed) {
|
|
190
|
-
const day = dayOf(commit);
|
|
191
|
-
const ofDay = days.get(day);
|
|
192
|
-
if (ofDay === undefined) days.set(day, [commit]);
|
|
193
|
-
else ofDay.push(commit);
|
|
194
|
-
}
|
|
195
121
|
const unlisted = pullRequest.commitsTotal - listed.length;
|
|
196
122
|
|
|
197
123
|
return (
|
|
198
124
|
<View className="gap-m">
|
|
199
125
|
<PullRequestPartReading reading={reading} />
|
|
200
126
|
<PullRequestPartError error={error} />
|
|
201
|
-
|
|
202
|
-
<View key={day} className="gap-xs">
|
|
203
|
-
<Text className="text-xs font-body-bold text-muted">{day}</Text>
|
|
204
|
-
{commits.map((commit) => (
|
|
205
|
-
<CommitRow
|
|
206
|
-
key={commit.oid}
|
|
207
|
-
commit={commit}
|
|
208
|
-
onOpen={() => {
|
|
209
|
-
setOpenOid(commit.oid);
|
|
210
|
-
}}
|
|
211
|
-
/>
|
|
212
|
-
))}
|
|
213
|
-
</View>
|
|
214
|
-
))}
|
|
127
|
+
<CommitList commits={listed} onOpen={setOpenOid} />
|
|
215
128
|
{unlisted > 0 ? (
|
|
216
129
|
<Text className="text-xs text-muted">
|
|
217
130
|
{`${pluralOf(unlisted, "more commit")} not listed`}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
DiffOmittedFile,
|
|
3
2
|
DiffStep,
|
|
4
3
|
DiffWhitespace,
|
|
5
4
|
PullRequestDiffResponse,
|
|
@@ -41,6 +40,7 @@ import {
|
|
|
41
40
|
useWhitespaceToggle,
|
|
42
41
|
} from "./branch-diff/use-diff-settings.tsx";
|
|
43
42
|
import { WhitespaceButton } from "./branch-diff/whitespace-button.tsx";
|
|
43
|
+
import { OmittedFiles } from "./omitted-files.tsx";
|
|
44
44
|
import { usePullRequestCollapse } from "./pull-request-diff-collapse.ts";
|
|
45
45
|
import type { PullRequestDiffTarget } from "./pull-request-diff-data.ts";
|
|
46
46
|
import {
|
|
@@ -77,30 +77,6 @@ const diffTreeAvailable = Platform.OS === "web";
|
|
|
77
77
|
|
|
78
78
|
type AsideView = "list" | "tree";
|
|
79
79
|
|
|
80
|
-
const omissionLabel = (reason: DiffOmittedFile["reason"]): string => {
|
|
81
|
-
if (reason === "binary") return "binary";
|
|
82
|
-
if (reason === "tooLarge") return "too large";
|
|
83
|
-
if (reason === "whitespace") return "only whitespace changed";
|
|
84
|
-
return "over the diff budget";
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
function OmittedFiles({ omitted }: { omitted: DiffOmittedFile[] }): ReactNode {
|
|
88
|
-
if (omitted.length === 0) return null;
|
|
89
|
-
return (
|
|
90
|
-
<View className="gap-xxs">
|
|
91
|
-
<Text className="text-xs font-body-bold text-muted">
|
|
92
|
-
{`Left out (${String(omitted.length)})`}
|
|
93
|
-
</Text>
|
|
94
|
-
{omitted.map(({ path, reason }) => (
|
|
95
|
-
<View key={path} className="flex-row flex-wrap items-center gap-xs">
|
|
96
|
-
<Code className="text-xs">{path}</Code>
|
|
97
|
-
<Text className="text-xs text-muted">{omissionLabel(reason)}</Text>
|
|
98
|
-
</View>
|
|
99
|
-
))}
|
|
100
|
-
</View>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
80
|
interface DiffSourceProps {
|
|
105
81
|
diff: PullRequestDiffResponse;
|
|
106
82
|
asked: DiffWhitespace;
|
|
@@ -49,6 +49,10 @@ import type { PullRequestDataSource } from "../dataSource.ts";
|
|
|
49
49
|
import { usePullRequestDataSource } from "../dataSource.ts";
|
|
50
50
|
import type { PendingComment } from "../services/PullRequestReviewDraftProvider.tsx";
|
|
51
51
|
import { usePullRequestReviewDraft } from "../services/PullRequestReviewDraftProvider.tsx";
|
|
52
|
+
import {
|
|
53
|
+
DiffChangeMark,
|
|
54
|
+
hidePierreChangeIcon,
|
|
55
|
+
} from "./branch-diff/diff-change-mark.tsx";
|
|
52
56
|
import { diffFontCSS } from "./branch-diff/diff-font.ts";
|
|
53
57
|
import type { DiffTheme } from "./branch-diff/diff-themes.ts";
|
|
54
58
|
import {
|
|
@@ -736,26 +740,31 @@ function DiffFile({
|
|
|
736
740
|
overflow: wordWrap ? "wrap" : "scroll",
|
|
737
741
|
collapsed,
|
|
738
742
|
stickyHeader: true,
|
|
739
|
-
unsafeCSS: `${stickyHeaderBelow(stickyTop)} ${headerInset} ${roundedFile} ${pierreScrollbars} ${diffFontCSS(fontFamily)} ${selectionCSS(theme, themeType)} ${changeBackgroundCSS(theme)} ${headerHighlight(highlight, collapsed)}`,
|
|
743
|
+
unsafeCSS: `${stickyHeaderBelow(stickyTop)} ${headerInset} ${roundedFile} ${pierreScrollbars} ${hidePierreChangeIcon} ${diffFontCSS(fontFamily)} ${selectionCSS(theme, themeType)} ${changeBackgroundCSS(theme)} ${headerHighlight(highlight, collapsed)}`,
|
|
740
744
|
loadDiffFiles: loadFiles,
|
|
741
745
|
enableGutterUtility: commentable,
|
|
742
746
|
onGutterUtilityClick: commentable ? onGutterUtilityClick : undefined,
|
|
743
747
|
}}
|
|
744
748
|
renderHeaderPrefix={() => (
|
|
745
|
-
<
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
749
|
+
<View className="flex-row items-center gap-xxs">
|
|
750
|
+
<IconButton
|
|
751
|
+
icon={
|
|
752
|
+
collapsed ? <CaretRightRegularIcon /> : <CaretDownRegularIcon />
|
|
753
|
+
}
|
|
754
|
+
size="sm"
|
|
755
|
+
variant="soft"
|
|
756
|
+
accent="neutral"
|
|
757
|
+
aria-label={
|
|
758
|
+
collapsed ? `Open ${file.name}` : `Close ${file.name}`
|
|
759
|
+
}
|
|
760
|
+
aria-expanded={!collapsed}
|
|
761
|
+
onPress={() => {
|
|
762
|
+
revealBeforeClosing();
|
|
763
|
+
onToggleCollapsed();
|
|
764
|
+
}}
|
|
765
|
+
/>
|
|
766
|
+
<DiffChangeMark type={file.type} />
|
|
767
|
+
</View>
|
|
759
768
|
)}
|
|
760
769
|
renderHeaderMetadata={
|
|
761
770
|
!entry &&
|
|
@@ -964,6 +973,56 @@ function FileGroups({ groups, renderFile }: FileGroupsProps): ReactNode {
|
|
|
964
973
|
);
|
|
965
974
|
}
|
|
966
975
|
|
|
976
|
+
interface ReadOnlyFilesProps {
|
|
977
|
+
groups: FileGroup[];
|
|
978
|
+
diffStyle: DiffStyle;
|
|
979
|
+
theme: DiffTheme;
|
|
980
|
+
themeType: "dark" | "light";
|
|
981
|
+
fontFamily: string | null;
|
|
982
|
+
wordWrap: boolean;
|
|
983
|
+
stickyTop: number;
|
|
984
|
+
collapse: PullRequestCollapse;
|
|
985
|
+
loadFiles: FileDiffContentsLoader | undefined;
|
|
986
|
+
impactsOf?: (path: string) => DiffFileImpacts | undefined;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function ReadOnlyFiles({
|
|
990
|
+
groups,
|
|
991
|
+
diffStyle,
|
|
992
|
+
theme,
|
|
993
|
+
themeType,
|
|
994
|
+
fontFamily,
|
|
995
|
+
wordWrap,
|
|
996
|
+
stickyTop,
|
|
997
|
+
collapse,
|
|
998
|
+
loadFiles,
|
|
999
|
+
impactsOf,
|
|
1000
|
+
}: ReadOnlyFilesProps): ReactNode {
|
|
1001
|
+
return (
|
|
1002
|
+
<FileGroups
|
|
1003
|
+
groups={groups}
|
|
1004
|
+
renderFile={(file, { entry, attached }) => (
|
|
1005
|
+
<DiffFile
|
|
1006
|
+
file={file}
|
|
1007
|
+
diffStyle={diffStyle}
|
|
1008
|
+
theme={theme}
|
|
1009
|
+
themeType={themeType}
|
|
1010
|
+
fontFamily={fontFamily}
|
|
1011
|
+
wordWrap={wordWrap}
|
|
1012
|
+
stickyTop={stickyTop}
|
|
1013
|
+
loadFiles={loadFiles}
|
|
1014
|
+
entry={entry}
|
|
1015
|
+
impacts={impactsOf?.(file.name)}
|
|
1016
|
+
collapsed={collapse.isCollapsed(file.name, attached)}
|
|
1017
|
+
onToggleCollapsed={() => {
|
|
1018
|
+
collapse.set(file.name, !collapse.isCollapsed(file.name, attached));
|
|
1019
|
+
}}
|
|
1020
|
+
/>
|
|
1021
|
+
)}
|
|
1022
|
+
/>
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
967
1026
|
export interface PullRequestDiffReview {
|
|
968
1027
|
/** the head a comment is anchored on: the diff's, not the header's last read */
|
|
969
1028
|
headSha: string;
|
|
@@ -1345,29 +1404,17 @@ function PullRequestDiffViewer({
|
|
|
1345
1404
|
return (
|
|
1346
1405
|
<DiffWorkerPool theme={theme}>
|
|
1347
1406
|
{review === undefined ? (
|
|
1348
|
-
<
|
|
1407
|
+
<ReadOnlyFiles
|
|
1349
1408
|
groups={fileGroups}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
loadFiles={loadFiles}
|
|
1360
|
-
entry={entry}
|
|
1361
|
-
impacts={impactsOf(file.name)}
|
|
1362
|
-
collapsed={collapse.isCollapsed(file.name, attached)}
|
|
1363
|
-
onToggleCollapsed={() => {
|
|
1364
|
-
collapse.set(
|
|
1365
|
-
file.name,
|
|
1366
|
-
!collapse.isCollapsed(file.name, attached),
|
|
1367
|
-
);
|
|
1368
|
-
}}
|
|
1369
|
-
/>
|
|
1370
|
-
)}
|
|
1409
|
+
diffStyle={diffStyle}
|
|
1410
|
+
theme={theme}
|
|
1411
|
+
themeType={themeType}
|
|
1412
|
+
fontFamily={fontFamily}
|
|
1413
|
+
wordWrap={wordWrap}
|
|
1414
|
+
stickyTop={stickyTop}
|
|
1415
|
+
collapse={collapse}
|
|
1416
|
+
loadFiles={loadFiles}
|
|
1417
|
+
impactsOf={impactsOf}
|
|
1371
1418
|
/>
|
|
1372
1419
|
) : (
|
|
1373
1420
|
<ReviewedFiles
|
|
@@ -1392,3 +1439,64 @@ function PullRequestDiffViewer({
|
|
|
1392
1439
|
}
|
|
1393
1440
|
|
|
1394
1441
|
export default memo(PullRequestDiffViewer);
|
|
1442
|
+
|
|
1443
|
+
export interface DiffViewerProps {
|
|
1444
|
+
patch: string;
|
|
1445
|
+
/** the files by package, in this order; a file no group names goes under the root */
|
|
1446
|
+
groups: DiffGroup[];
|
|
1447
|
+
diffStyle: DiffStyle;
|
|
1448
|
+
theme: DiffTheme;
|
|
1449
|
+
themeType: "dark" | "light";
|
|
1450
|
+
fontFamily: string | null;
|
|
1451
|
+
wordWrap: boolean;
|
|
1452
|
+
/** where a file header sticks: the height of the toolbar stuck above it */
|
|
1453
|
+
stickyTop: number;
|
|
1454
|
+
collapse: PullRequestCollapse;
|
|
1455
|
+
/** absent: the unchanged lines between hunks cannot be unfolded */
|
|
1456
|
+
loadFiles?: FileDiffContentsLoader;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* A read-only diff of any patch, with the pull request's file chrome and no
|
|
1461
|
+
* pull request: a branch against its base, a working tree. Default export of
|
|
1462
|
+
* `@reviewflow/ui/diff-viewer`, to be loaded lazily like the pull request's.
|
|
1463
|
+
*/
|
|
1464
|
+
function DiffViewer({
|
|
1465
|
+
patch,
|
|
1466
|
+
groups,
|
|
1467
|
+
diffStyle,
|
|
1468
|
+
theme,
|
|
1469
|
+
themeType,
|
|
1470
|
+
fontFamily,
|
|
1471
|
+
wordWrap,
|
|
1472
|
+
stickyTop,
|
|
1473
|
+
collapse,
|
|
1474
|
+
loadFiles,
|
|
1475
|
+
}: DiffViewerProps): ReactNode {
|
|
1476
|
+
const fileGroups = useMemo(
|
|
1477
|
+
() =>
|
|
1478
|
+
groupFiles(
|
|
1479
|
+
parsePatchFiles(patch).flatMap((parsed) => parsed.files),
|
|
1480
|
+
groups,
|
|
1481
|
+
),
|
|
1482
|
+
[patch, groups],
|
|
1483
|
+
);
|
|
1484
|
+
return (
|
|
1485
|
+
<DiffWorkerPool theme={theme}>
|
|
1486
|
+
<ReadOnlyFiles
|
|
1487
|
+
groups={fileGroups}
|
|
1488
|
+
diffStyle={diffStyle}
|
|
1489
|
+
theme={theme}
|
|
1490
|
+
themeType={themeType}
|
|
1491
|
+
fontFamily={fontFamily}
|
|
1492
|
+
wordWrap={wordWrap}
|
|
1493
|
+
stickyTop={stickyTop}
|
|
1494
|
+
collapse={collapse}
|
|
1495
|
+
loadFiles={loadFiles}
|
|
1496
|
+
/>
|
|
1497
|
+
</DiffWorkerPool>
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const MemoizedDiffViewer = memo(DiffViewer);
|
|
1502
|
+
export { MemoizedDiffViewer as DiffViewer };
|