4.7 KB153 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 * as stylex from '@stylexjs/stylex';
9import {ErrorNotice} from 'isl-components/ErrorNotice';
10import {Icon} from 'isl-components/Icon';
11import {Panels} from 'isl-components/Panels';
12import {useAtom, useAtomValue} from 'jotai';
13import {useState} from 'react';
14import {Center, FlexSpacer, Row, ScrollY} from '../../ComponentUtils';
15import {Modal} from '../../Modal';
16import {tracker} from '../../analytics';
17import {t} from '../../i18n';
18import {AbsorbStackEditPanel} from './AbsorbStackEditPanel';
19import {SplitStackEditPanel, SplitStackToolbar} from './SplitStackEditPanel';
20import {StackEditConfirmButtons} from './StackEditConfirmButtons';
21import {StackEditSubTree} from './StackEditSubTree';
22import {editingStackIntentionHashes, loadingStackState} from './stackEditState';
23
24const styles = stylex.create({
25 container: {
26 minWidth: '500px',
27 minHeight: '300px',
28 },
29 loading: {
30 paddingBottom: 'calc(24px + 2 * var(--pad))',
31 },
32 tab: {
33 fontSize: '110%',
34 padding: 'var(--halfpad) calc(2 * var(--pad))',
35 },
36});
37
38/// Show a <Modal /> when editing a stack.
39export function MaybeEditStackModal() {
40 const loadingState = useAtomValue(loadingStackState);
41 const [[stackIntention, stackHashes], setStackIntention] = useAtom(editingStackIntentionHashes);
42
43 const isEditing = stackHashes.size > 0;
44 const isLoaded = isEditing && loadingState.state === 'hasValue';
45
46 return isLoaded ? (
47 {
48 split: () => <LoadedSplitModal />,
49 general: () => <LoadedEditStackModal />,
50 // TODO: implement absorb model.
51 absorb: () => <LoadedAbsorbModal />,
52 }[stackIntention]()
53 ) : isEditing ? (
54 <Modal
55 dataTestId="edit-stack-loading"
56 dismiss={() => {
57 // allow dismissing in loading state in case it gets stuck
58 setStackIntention(['general', new Set()]);
59 }}>
60 <Center
61 xstyle={[
62 (stackIntention === 'general' || stackIntention === 'absorb') && styles.container,
63 styles.loading,
64 ]}
65 className={stackIntention === 'split' ? 'interactive-split' : undefined}>
66 {loadingState.state === 'hasError' ? (
67 <ErrorNotice error={new Error(loadingState.error)} title={t('Loading stack failed')} />
68 ) : (
69 <Row>
70 <Icon icon="loading" size="M" />
71 {(loadingState.state === 'loading' && loadingState.message) ?? null}
72 </Row>
73 )}
74 </Center>
75 </Modal>
76 ) : null;
77}
78
79/** A Modal for dedicated split UI. Subset of `LoadedEditStackModal`. */
80function LoadedSplitModal() {
81 return (
82 <Modal dataTestId="interactive-split-modal" className="split-single-commit-modal-contents">
83 <SplitStackEditPanel />
84 <Row style={{padding: 'var(--pad) 0', justifyContent: 'flex-end', zIndex: 1}}>
85 <StackEditConfirmButtons />
86 </Row>
87 </Modal>
88 );
89}
90
91/**
92 * A Modal for dedicated absorb UI.
93 * While absorbing, the other edit stacks features are unavailable.
94 * See `StackStateWithOperationProps.absorbChunks` for details.
95 */
96function LoadedAbsorbModal() {
97 return (
98 <Modal dataTestId="interactive-absorb-modal" className="absorb-modal-contents">
99 <AbsorbStackEditPanel />
100 <Row style={{padding: 'var(--pad) 0', justifyContent: 'flex-end', zIndex: 1}}>
101 <StackEditConfirmButtons />
102 </Row>
103 </Modal>
104 );
105}
106
107/** A Modal for general stack editing UI. */
108function LoadedEditStackModal() {
109 const panels = {
110 commits: {
111 label: t('Commits'),
112 render: () => (
113 <ScrollY maxSize="calc((100vh / var(--zoom)) - 200px)">
114 <StackEditSubTree
115 activateSplitTab={() => {
116 setActiveTab('split');
117 tracker.track('StackEditInlineSplitButton');
118 }}
119 />
120 </ScrollY>
121 ),
122 },
123 split: {
124 label: t('Split'),
125 render: () => <SplitStackEditPanel />,
126 },
127 // TODO: re-enable the "files" tab
128 // files: {label: t('Files'), render: () => <FileStackEditPanel />},
129 } as const;
130 type Tab = keyof typeof panels;
131 const [activeTab, setActiveTab] = useState<Tab>('commits');
132
133 return (
134 <Modal className="edit-stack-modal-contents">
135 <Panels
136 active={activeTab}
137 panels={panels}
138 onSelect={tab => {
139 setActiveTab(tab);
140 tracker.track('StackEditChangeTab', {extras: {tab}});
141 }}
142 xstyle={styles.container}
143 tabXstyle={styles.tab}
144 />
145 <Row style={{padding: 'var(--pad) 0', justifyContent: 'flex-end'}}>
146 {activeTab === 'split' && <SplitStackToolbar />}
147 <FlexSpacer />
148 <StackEditConfirmButtons />
149 </Row>
150 </Modal>
151 );
152}
153