4.5 KB150 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {Operation} from '../../operations/Operation';
9import type {CommitInfo} from '../../types';
10import type {GithubUICodeReviewProvider} from './github';
11
12import * as stylex from '@stylexjs/stylex';
13import {Badge} from 'isl-components/Badge';
14import {Button} from 'isl-components/Button';
15import {Checkbox} from 'isl-components/Checkbox';
16import {Dropdown} from 'isl-components/Dropdown';
17import {HorizontallyGrowingTextField} from 'isl-components/HorizontallyGrowingTextField';
18import {useAtomValue} from 'jotai';
19import {useState} from 'react';
20import {Avatar} from '../../Avatar';
21import {Commit} from '../../Commit';
22import {Column, FlexSpacer, Row} from '../../ComponentUtils';
23import {T} from '../../i18n';
24import {PushOperation} from '../../operations/PushOperation';
25import {CommitPreview, dagWithPreviews} from '../../previews';
26import {latestSuccessorUnlessExplicitlyObsolete} from '../../successionUtils';
27
28const styles = stylex.create({
29 sectionTitle: {
30 fontWeight: 'bold',
31 },
32});
33
34function getPushChoices(provider: GithubUICodeReviewProvider) {
35 const system = provider.system;
36 return [
37 `${system.hostname === 'github.com' ? '' : system.hostname + '/'}${system.owner}/${
38 system.repo
39 }`,
40 ];
41}
42
43function recommendNewBranchName(stack: Array<CommitInfo>) {
44 // TODO: this format should be configurable
45 return `feature/${stack[0].title
46 .trim()
47 .replace(/\s/g, '-')
48 .replace(/[^a-zA-Z0-9\-_.]/g, '.')
49 .replace(/^\.+/, '') // no leading dots
50 .replace(/\/$/, '') // no trailing slashes
51 .toLowerCase()}`;
52}
53
54export default function BranchingPrModalContent({
55 topOfStack,
56 provider,
57 returnResultAndDismiss,
58}: {
59 topOfStack: CommitInfo;
60 provider: GithubUICodeReviewProvider;
61 returnResultAndDismiss: (result: Array<Operation> | undefined) => unknown;
62}) {
63 const [createPr, setCreatePr] = useState(false);
64
65 const dag = useAtomValue(dagWithPreviews);
66 // If passed the optimistic isDot commit, we may need to resolve it to a real commit
67 // once the optimistic commit is no longer in the dag.
68 const top =
69 topOfStack.isDot && topOfStack.optimisticRevset != null
70 ? (dag.resolve('.') ?? topOfStack)
71 : topOfStack;
72 const stack = dag.getBatch(dag.ancestors(top.hash, {within: dag.draft()}).toArray());
73
74 const pushChoices = getPushChoices(provider);
75 const [pushChoice, setPushChoice] = useState(pushChoices[0]);
76
77 const [branchName, setBranchName] = useState(recommendNewBranchName(stack));
78
79 return (
80 <Column alignStart style={{height: '100%'}}>
81 <div>
82 <Row {...stylex.props(styles.sectionTitle)}>
83 <span>
84 <T>Commits</T>
85 </span>
86 <Badge>{stack.length}</Badge>
87 </Row>
88 <Column alignStart style={{padding: 'var(--pad)'}}>
89 {stack.map((commit, i) => (
90 <Row key={i}>
91 <Avatar username={commit.author} />
92 <Commit
93 commit={commit}
94 previewType={CommitPreview.NON_ACTIONABLE_COMMIT}
95 hasChildren={false}
96 />
97 </Row>
98 ))}
99 </Column>
100 </div>
101 <Row>
102 <span>
103 <T>Push to repo</T>
104 </span>
105 <Dropdown
106 options={pushChoices}
107 value={pushChoice}
108 onChange={e => setPushChoice(e.currentTarget.value)}
109 />
110 </Row>
111 <Row>
112 <span>
113 <T>to branch named</T>
114 </span>
115 <HorizontallyGrowingTextField
116 value={branchName}
117 onChange={e => setBranchName(e.currentTarget.value)}
118 />
119 {/* TODO: validate the branch name */}
120 </Row>
121 <Row>
122 <Checkbox checked={createPr} onChange={setCreatePr} disabled /* not implemented yet */>
123 <T>Create a Pull Request</T>
124 </Checkbox>
125 </Row>
126
127 <FlexSpacer />
128 <Row style={{width: '100%'}}>
129 <FlexSpacer />
130 <Button onClick={() => returnResultAndDismiss(undefined)}>
131 <T>Cancel</T>
132 </Button>
133 <Button
134 primary
135 onClick={() => {
136 if (createPr) {
137 throw new Error('not implemented');
138 }
139
140 returnResultAndDismiss([
141 new PushOperation(latestSuccessorUnlessExplicitlyObsolete(stack[0]), branchName),
142 ]);
143 }}>
144 {createPr ? <T>Push & Create PR</T> : <T>Push</T>}
145 </Button>
146 </Row>
147 </Column>
148 );
149}
150