@reviewflow/ui 6.26.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 +39 -20
- 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/services/checkoutSummary.ts +32 -0
|
@@ -7,12 +7,7 @@ import type {
|
|
|
7
7
|
PullRequestReviewState,
|
|
8
8
|
PullRequestSummary,
|
|
9
9
|
} from "@reviewflow/sdk";
|
|
10
|
-
import {
|
|
11
|
-
pluralOf,
|
|
12
|
-
prStatus,
|
|
13
|
-
prStatusLabels,
|
|
14
|
-
reviewflowPrStatus,
|
|
15
|
-
} from "@reviewflow/sdk";
|
|
10
|
+
import { pluralOf, prStatusLabels, reviewflowPrStatus } from "@reviewflow/sdk";
|
|
16
11
|
import type { Accent } from "alouette";
|
|
17
12
|
import {
|
|
18
13
|
AccentScope,
|
|
@@ -59,6 +54,7 @@ import {
|
|
|
59
54
|
PullRequestPartPending,
|
|
60
55
|
PullRequestPartReading,
|
|
61
56
|
} from "./pull-request-part-notice.tsx";
|
|
57
|
+
import { RepoName } from "./pull-request-repo-name.tsx";
|
|
62
58
|
import { PullRequestReview } from "./pull-request-review.tsx";
|
|
63
59
|
import { PullRequestTimeline } from "./pull-request-timeline.tsx";
|
|
64
60
|
|
|
@@ -156,9 +152,16 @@ interface MergeLineProps {
|
|
|
156
152
|
children?: ReactNode;
|
|
157
153
|
}
|
|
158
154
|
|
|
155
|
+
/** Badge aligns itself to the start: in a row, that is the top, not the center. */
|
|
156
|
+
function CenteredBadge({ children }: { children: ReactNode }): ReactNode {
|
|
157
|
+
return <View className="justify-center">{children}</View>;
|
|
158
|
+
}
|
|
159
|
+
|
|
159
160
|
function StatusBadge({ status }: { status: PrStatus }): ReactNode {
|
|
160
161
|
return (
|
|
161
|
-
<
|
|
162
|
+
<CenteredBadge>
|
|
163
|
+
<Badge accent={usePrStatusAccent(status)}>{prStatusLabels[status]}</Badge>
|
|
164
|
+
</CenteredBadge>
|
|
162
165
|
);
|
|
163
166
|
}
|
|
164
167
|
|
|
@@ -201,6 +204,7 @@ export function PullRequestSummaryHeader({
|
|
|
201
204
|
}): ReactNode {
|
|
202
205
|
return (
|
|
203
206
|
<View className="gap-sm">
|
|
207
|
+
<RepoName ownerRepo={summary.ownerRepo} />
|
|
204
208
|
<TitleText number={summary.number} title={summary.title} />
|
|
205
209
|
<MergeLine
|
|
206
210
|
status={reviewflowPrStatus(summary)}
|
|
@@ -212,16 +216,23 @@ export function PullRequestSummaryHeader({
|
|
|
212
216
|
);
|
|
213
217
|
}
|
|
214
218
|
|
|
219
|
+
interface HeaderProps extends EditableProps {
|
|
220
|
+
summary: PullRequestSummary | null;
|
|
221
|
+
}
|
|
222
|
+
|
|
215
223
|
/**
|
|
216
224
|
* The header: the title and its number, Edit and the way out beside
|
|
217
|
-
* them, and one line saying whose branch goes where.
|
|
225
|
+
* them, and one line saying whose branch goes where. The review status is
|
|
226
|
+
* reviewflow's, never recomputed from GitHub's: without reviewflow's
|
|
227
|
+
* summary, an open pull request shows none.
|
|
218
228
|
*/
|
|
219
|
-
function Header({ detail, onEdited }:
|
|
229
|
+
function Header({ detail, summary, onEdited }: HeaderProps): ReactNode {
|
|
220
230
|
const { pullRequest } = detail;
|
|
221
231
|
const [editing, setEditing] = useState(false);
|
|
222
232
|
|
|
223
233
|
return (
|
|
224
234
|
<View className="gap-sm">
|
|
235
|
+
<RepoName ownerRepo={pullRequest.ownerRepo} />
|
|
225
236
|
{editing ? (
|
|
226
237
|
<TitleEditor
|
|
227
238
|
detail={detail}
|
|
@@ -251,7 +262,10 @@ function Header({ detail, onEdited }: EditableProps): ReactNode {
|
|
|
251
262
|
</View>
|
|
252
263
|
)}
|
|
253
264
|
<MergeLine
|
|
254
|
-
status={
|
|
265
|
+
status={reviewflowPrStatus({
|
|
266
|
+
state: pullRequest.state,
|
|
267
|
+
reviewState: summary?.reviewState ?? null,
|
|
268
|
+
})}
|
|
255
269
|
author={pullRequest.author?.login ?? null}
|
|
256
270
|
baseRef={pullRequest.baseRef}
|
|
257
271
|
headRef={pullRequest.headRef}
|
|
@@ -588,15 +602,17 @@ function ReviewerLine({
|
|
|
588
602
|
<Text className="min-w-0 flex-1 text-sm" numberOfLines={1}>
|
|
589
603
|
{name}
|
|
590
604
|
</Text>
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
{
|
|
598
|
-
|
|
599
|
-
|
|
605
|
+
<CenteredBadge>
|
|
606
|
+
{state === null ? (
|
|
607
|
+
<Badge accent="warning" size="sm">
|
|
608
|
+
awaiting
|
|
609
|
+
</Badge>
|
|
610
|
+
) : (
|
|
611
|
+
<Badge accent={reviewStateAccent(state)} size="sm">
|
|
612
|
+
{reviewStateLabel(state)}
|
|
613
|
+
</Badge>
|
|
614
|
+
)}
|
|
615
|
+
</CenteredBadge>
|
|
600
616
|
{onRerequest === undefined ? null : (
|
|
601
617
|
<RerequestButton name={name} onRerequest={onRerequest} />
|
|
602
618
|
)}
|
|
@@ -1165,6 +1181,8 @@ function PullRequestActions({ detail, onEdited }: EditableProps): ReactNode {
|
|
|
1165
1181
|
}
|
|
1166
1182
|
|
|
1167
1183
|
export interface PullRequestPanelProps extends CandidatesProps {
|
|
1184
|
+
/** reviewflow's copy, where the review status comes from; null without reviewflow */
|
|
1185
|
+
summary: PullRequestSummary | null;
|
|
1168
1186
|
/** why the editors stay read-only, when GitHub refused what they offer */
|
|
1169
1187
|
candidatesError: string | null;
|
|
1170
1188
|
/** absent: the first tab, Review for a reviewer and Conversation for the author */
|
|
@@ -1185,6 +1203,7 @@ export interface PullRequestPanelProps extends CandidatesProps {
|
|
|
1185
1203
|
*/
|
|
1186
1204
|
export function PullRequestPanel({
|
|
1187
1205
|
detail,
|
|
1206
|
+
summary,
|
|
1188
1207
|
candidates,
|
|
1189
1208
|
candidatesError,
|
|
1190
1209
|
onEdited,
|
|
@@ -1216,7 +1235,7 @@ export function PullRequestPanel({
|
|
|
1216
1235
|
{`Reviewers, assignees and labels cannot be edited: ${candidatesError}`}
|
|
1217
1236
|
</WarningMessage>
|
|
1218
1237
|
)}
|
|
1219
|
-
<Header detail={detail} onEdited={onEdited} />
|
|
1238
|
+
<Header detail={detail} summary={summary} onEdited={onEdited} />
|
|
1220
1239
|
<ConflictWarning pullRequest={pullRequest} />
|
|
1221
1240
|
<View className="flex-row flex-wrap items-center justify-between gap-sm">
|
|
1222
1241
|
<Tabs
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Text, View } from "alouette";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { createContext, use } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How an app links a repository's name to its own screen of it, the router
|
|
7
|
+
* being the app's; null keeps the name as plain text, e.g. for a repository
|
|
8
|
+
* the app has no screen of.
|
|
9
|
+
*/
|
|
10
|
+
export type RenderRepoLink = (ownerRepo: string) => ReactNode;
|
|
11
|
+
|
|
12
|
+
export const RepoLinkContext = createContext<RenderRepoLink | null>(null);
|
|
13
|
+
|
|
14
|
+
export interface RepoNameProps {
|
|
15
|
+
ownerRepo: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The repository above a pull request's title, a link when the app gives one. */
|
|
19
|
+
export function RepoName({ ownerRepo }: RepoNameProps): ReactNode {
|
|
20
|
+
const renderRepoLink = use(RepoLinkContext);
|
|
21
|
+
const link = renderRepoLink?.(ownerRepo) ?? null;
|
|
22
|
+
if (link === null) {
|
|
23
|
+
return <Text className="text-sm text-muted">{ownerRepo}</Text>;
|
|
24
|
+
}
|
|
25
|
+
// a link stretches in a column: the row keeps it as wide as its name
|
|
26
|
+
return <View className="flex-row">{link}</View>;
|
|
27
|
+
}
|
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
PullRequestPartPending,
|
|
41
41
|
usePartReadingCount,
|
|
42
42
|
} from "./pull-request-part-notice.tsx";
|
|
43
|
+
import type { RenderRepoLink } from "./pull-request-repo-name.tsx";
|
|
44
|
+
import { RepoLinkContext } from "./pull-request-repo-name.tsx";
|
|
43
45
|
import { RepositoryAttachmentBanner } from "./repository-attachment-banner.tsx";
|
|
44
46
|
|
|
45
47
|
/** How old what the header shows is: GitHub's answer, or the last read shown meanwhile. */
|
|
@@ -184,6 +186,7 @@ function PullRequestBody({
|
|
|
184
186
|
<PartReadingScope value={partsReadInTabs.report}>
|
|
185
187
|
<PullRequestPanel
|
|
186
188
|
detail={detail}
|
|
189
|
+
summary={summary}
|
|
187
190
|
candidates={candidates}
|
|
188
191
|
candidatesError={candidatesError}
|
|
189
192
|
tab={tab}
|
|
@@ -236,6 +239,8 @@ export interface PullRequestScreenProps {
|
|
|
236
239
|
ref: PullRequestRef,
|
|
237
240
|
detail: PullRequestDetailResponse | null,
|
|
238
241
|
) => ReactNode;
|
|
242
|
+
/** links the repository's name above the title; absent or null: plain text */
|
|
243
|
+
renderRepoLink?: RenderRepoLink;
|
|
239
244
|
/** what the app replaces, see {@link PullRequestSlots} */
|
|
240
245
|
slots?: PullRequestSlots;
|
|
241
246
|
/**
|
|
@@ -285,35 +290,38 @@ export function PullRequestScreen({
|
|
|
285
290
|
initialWhitespace,
|
|
286
291
|
diffFileOrder,
|
|
287
292
|
onDiffFileOrderChange,
|
|
293
|
+
renderRepoLink,
|
|
288
294
|
...body
|
|
289
295
|
}: PullRequestScreenProps): ReactNode {
|
|
290
296
|
return (
|
|
291
|
-
<
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
<View className="
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
297
|
+
<RepoLinkContext value={renderRepoLink ?? null}>
|
|
298
|
+
<PullRequestSlotsProvider value={slots}>
|
|
299
|
+
<DiffSettingsProvider
|
|
300
|
+
theme={diffTheme}
|
|
301
|
+
font={diffFont}
|
|
302
|
+
customFont={diffCustomFont}
|
|
303
|
+
wordWrap={diffWordWrap}
|
|
304
|
+
initialWhitespace={initialWhitespace}
|
|
305
|
+
fileOrder={diffFileOrder}
|
|
306
|
+
onFileOrderChange={onDiffFileOrderChange}
|
|
307
|
+
>
|
|
308
|
+
<View className="w-full px-l pt-l pb-xl web:@container">
|
|
309
|
+
<View className="mx-auto w-full max-w-[1200px] gap-m">
|
|
310
|
+
<PullRequestDraftsProvider
|
|
311
|
+
store={draftStore}
|
|
312
|
+
pullRequest={{ ownerRepo, number }}
|
|
313
|
+
>
|
|
314
|
+
<PullRequestBody
|
|
315
|
+
key={`${ownerRepo}#${String(number)}`}
|
|
316
|
+
ownerRepo={ownerRepo}
|
|
317
|
+
number={number}
|
|
318
|
+
{...body}
|
|
319
|
+
/>
|
|
320
|
+
</PullRequestDraftsProvider>
|
|
321
|
+
</View>
|
|
314
322
|
</View>
|
|
315
|
-
</
|
|
316
|
-
</
|
|
317
|
-
</
|
|
323
|
+
</DiffSettingsProvider>
|
|
324
|
+
</PullRequestSlotsProvider>
|
|
325
|
+
</RepoLinkContext>
|
|
318
326
|
);
|
|
319
327
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WorktreeRemovalResult,
|
|
3
|
+
WorktreeRemovalStatus,
|
|
4
|
+
} from "@reviewflow/sdk";
|
|
5
|
+
import { worktreeRemovalRefusals } from "@reviewflow/sdk";
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
Checkbox,
|
|
9
|
+
Code,
|
|
10
|
+
ConfirmationMessage,
|
|
11
|
+
Text,
|
|
12
|
+
View,
|
|
13
|
+
WarningAlertDialog,
|
|
14
|
+
WarningMessage,
|
|
15
|
+
} from "alouette";
|
|
16
|
+
import { TrashRegularIcon } from "alouette-icons/phosphor-icons/Trash";
|
|
17
|
+
import type { ReactNode } from "react";
|
|
18
|
+
import { useRef, useState } from "react";
|
|
19
|
+
import { errorToMessage } from "../services/errorToMessage.ts";
|
|
20
|
+
|
|
21
|
+
const visibleIgnored = 10;
|
|
22
|
+
|
|
23
|
+
export interface IgnoredFilesProps {
|
|
24
|
+
paths: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Not a guard: an ignored file is usually disposable, so the reader decides.
|
|
29
|
+
* Each path is prefixed `./` because a bare `~/…` (an agent writing to an
|
|
30
|
+
* unexpanded home path) reads as the home directory.
|
|
31
|
+
*/
|
|
32
|
+
export function IgnoredFiles({ paths }: IgnoredFilesProps): ReactNode {
|
|
33
|
+
if (paths.length === 0) return null;
|
|
34
|
+
const hidden = paths.length - visibleIgnored;
|
|
35
|
+
return (
|
|
36
|
+
<View className="gap-xxs">
|
|
37
|
+
<WarningMessage variant="flat">
|
|
38
|
+
Lost with the worktree: ignored by git, so not counted as uncommitted,
|
|
39
|
+
real files and not symlinks, and the main checkout has no copy. Move
|
|
40
|
+
them out first if you need them.
|
|
41
|
+
</WarningMessage>
|
|
42
|
+
{paths.slice(0, visibleIgnored).map((path) => (
|
|
43
|
+
<Code key={path}>{`./${path}`}</Code>
|
|
44
|
+
))}
|
|
45
|
+
{hidden > 0 ? (
|
|
46
|
+
<Text className="text-sm text-muted">{`and ${String(hidden)} more`}</Text>
|
|
47
|
+
) : null}
|
|
48
|
+
</View>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** What a confirmation reads before it offers the removal. */
|
|
53
|
+
export interface WorktreeRemovalCheck {
|
|
54
|
+
status: WorktreeRemovalStatus;
|
|
55
|
+
/** the branch's work is demonstrably spent (a merged pull request): the branch box starts checked */
|
|
56
|
+
branchSpent: boolean;
|
|
57
|
+
/** under the refusals, e.g. why no pull request could prove a commit pushed */
|
|
58
|
+
notice: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WorktreeRemovalMessagesProps {
|
|
62
|
+
check: WorktreeRemovalCheck;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Every refusal, or the all-clear; the notice; then the ignored files the removal takes. */
|
|
66
|
+
export function WorktreeRemovalMessages({
|
|
67
|
+
check,
|
|
68
|
+
}: WorktreeRemovalMessagesProps): ReactNode {
|
|
69
|
+
const refusals = worktreeRemovalRefusals(check.status);
|
|
70
|
+
return (
|
|
71
|
+
<>
|
|
72
|
+
{refusals.map((refusal) => (
|
|
73
|
+
<WarningMessage key={refusal} variant="flat">
|
|
74
|
+
{refusal}
|
|
75
|
+
</WarningMessage>
|
|
76
|
+
))}
|
|
77
|
+
{refusals.length === 0 ? (
|
|
78
|
+
<ConfirmationMessage variant="flat">
|
|
79
|
+
No uncommitted file, every commit is on a remote branch or in a pull
|
|
80
|
+
request, and nothing runs inside.
|
|
81
|
+
</ConfirmationMessage>
|
|
82
|
+
) : null}
|
|
83
|
+
{check.notice === null ? null : (
|
|
84
|
+
<Text className="text-sm text-muted">{check.notice}</Text>
|
|
85
|
+
)}
|
|
86
|
+
<IgnoredFiles paths={check.status.localOnlyIgnored} />
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface CheckState {
|
|
92
|
+
check: WorktreeRemovalCheck | null;
|
|
93
|
+
error: string | null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const unread: CheckState = { check: null, error: null };
|
|
97
|
+
|
|
98
|
+
const branchDetails =
|
|
99
|
+
"Only the local branch: git refuses while it is unmerged, and the remote is never touched.";
|
|
100
|
+
|
|
101
|
+
export interface RemoveWorktreeButtonProps {
|
|
102
|
+
/** named before git answers anything */
|
|
103
|
+
path: string;
|
|
104
|
+
/** read on every opening: an answer kept from the last one could let a changed worktree through */
|
|
105
|
+
readCheck: () => Promise<WorktreeRemovalCheck>;
|
|
106
|
+
/** runs the removal; the server reads every refusal again first */
|
|
107
|
+
onRemove: (deleteBranch: boolean) => Promise<WorktreeRemovalResult>;
|
|
108
|
+
/** after the removal, the branch's refusal included */
|
|
109
|
+
onRemoved: (result: WorktreeRemovalResult) => void;
|
|
110
|
+
/** why the button cannot be pressed at all, e.g. a removal already running */
|
|
111
|
+
disabledReason?: string | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Removes a worktree after a confirmation that reads what it would lose.
|
|
116
|
+
* Remove stays disabled until that read proves no uncommitted file, no commit
|
|
117
|
+
* only here, no lock and no process inside; an unread or failed read disables
|
|
118
|
+
* it too, since nothing is proven. A `WarningAlertDialog`: one of the buttons
|
|
119
|
+
* deletes files, and the icon says so before the copy is read.
|
|
120
|
+
*/
|
|
121
|
+
export function RemoveWorktreeButton({
|
|
122
|
+
path,
|
|
123
|
+
readCheck,
|
|
124
|
+
onRemove,
|
|
125
|
+
onRemoved,
|
|
126
|
+
disabledReason = null,
|
|
127
|
+
}: RemoveWorktreeButtonProps): ReactNode {
|
|
128
|
+
const [confirming, setConfirming] = useState(false);
|
|
129
|
+
const [read, setRead] = useState<CheckState>(unread);
|
|
130
|
+
const [pickedDeleteBranch, setPickedDeleteBranch] = useState<boolean | null>(
|
|
131
|
+
null,
|
|
132
|
+
);
|
|
133
|
+
// an answer to an earlier opening must not land in this one
|
|
134
|
+
const opening = useRef(0);
|
|
135
|
+
const { check } = read;
|
|
136
|
+
const branch = check?.status.branch ?? null;
|
|
137
|
+
const deleteBranch =
|
|
138
|
+
pickedDeleteBranch ?? (branch !== null && check?.branchSpent === true);
|
|
139
|
+
const removable =
|
|
140
|
+
check !== null && worktreeRemovalRefusals(check.status).length === 0;
|
|
141
|
+
|
|
142
|
+
const open = (): void => {
|
|
143
|
+
opening.current += 1;
|
|
144
|
+
const current = opening.current;
|
|
145
|
+
setConfirming(true);
|
|
146
|
+
setRead(unread);
|
|
147
|
+
setPickedDeleteBranch(null);
|
|
148
|
+
readCheck().then(
|
|
149
|
+
(answer) => {
|
|
150
|
+
if (opening.current === current) {
|
|
151
|
+
setRead({ check: answer, error: null });
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
(error: unknown) => {
|
|
155
|
+
if (opening.current === current) {
|
|
156
|
+
setRead({ check: null, error: errorToMessage(error) });
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const close = (): void => {
|
|
163
|
+
opening.current += 1;
|
|
164
|
+
setConfirming(false);
|
|
165
|
+
setRead(unread);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const body = ((): ReactNode => {
|
|
169
|
+
if (read.error !== null) {
|
|
170
|
+
return (
|
|
171
|
+
<WarningMessage variant="flat">
|
|
172
|
+
{`Could not read the worktree: ${read.error}. Remove stays disabled until it can be.`}
|
|
173
|
+
</WarningMessage>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (check === null) {
|
|
177
|
+
return (
|
|
178
|
+
<Text className="text-sm text-muted">
|
|
179
|
+
Checking for uncommitted files, unpushed commits and processes inside…
|
|
180
|
+
</Text>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return <WorktreeRemovalMessages check={check} />;
|
|
184
|
+
})();
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<>
|
|
188
|
+
<Button
|
|
189
|
+
text="Remove"
|
|
190
|
+
icon={<TrashRegularIcon />}
|
|
191
|
+
size="sm"
|
|
192
|
+
variant="tonal"
|
|
193
|
+
accent="danger"
|
|
194
|
+
aria-label={`Remove the worktree at ${path}`}
|
|
195
|
+
disabledReason={disabledReason}
|
|
196
|
+
onPress={open}
|
|
197
|
+
/>
|
|
198
|
+
<WarningAlertDialog
|
|
199
|
+
visible={confirming}
|
|
200
|
+
title="Remove this worktree?"
|
|
201
|
+
confirmText="Remove"
|
|
202
|
+
primaryAction="cancel"
|
|
203
|
+
confirmDisabled={!removable}
|
|
204
|
+
errorToMessage={errorToMessage}
|
|
205
|
+
content={
|
|
206
|
+
<View className="gap-sm">
|
|
207
|
+
<Code>{path}</Code>
|
|
208
|
+
{body}
|
|
209
|
+
{branch === null || check === null ? null : (
|
|
210
|
+
<View className="gap-xxs">
|
|
211
|
+
<Checkbox
|
|
212
|
+
label={`Also delete the branch ${branch}`}
|
|
213
|
+
checked={deleteBranch}
|
|
214
|
+
onValueChange={setPickedDeleteBranch}
|
|
215
|
+
/>
|
|
216
|
+
<Text className="text-sm text-muted">{branchDetails}</Text>
|
|
217
|
+
</View>
|
|
218
|
+
)}
|
|
219
|
+
</View>
|
|
220
|
+
}
|
|
221
|
+
onConfirm={async () => {
|
|
222
|
+
const result = await onRemove(deleteBranch);
|
|
223
|
+
close();
|
|
224
|
+
onRemoved(result);
|
|
225
|
+
}}
|
|
226
|
+
onCancel={close}
|
|
227
|
+
>
|
|
228
|
+
Its directory is deleted from disk. git refuses on its own if a file
|
|
229
|
+
changed since this check.
|
|
230
|
+
</WarningAlertDialog>
|
|
231
|
+
</>
|
|
232
|
+
);
|
|
233
|
+
}
|