addons/isl/src/stackEdit/ui/EditStackModal.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import * as stylex from '@stylexjs/stylex';
b69ab319import {ErrorNotice} from 'isl-components/ErrorNotice';
b69ab3110import {Icon} from 'isl-components/Icon';
b69ab3111import {Panels} from 'isl-components/Panels';
b69ab3112import {useAtom, useAtomValue} from 'jotai';
b69ab3113import {useState} from 'react';
b69ab3114import {Center, FlexSpacer, Row, ScrollY} from '../../ComponentUtils';
b69ab3115import {Modal} from '../../Modal';
b69ab3116import {tracker} from '../../analytics';
b69ab3117import {t} from '../../i18n';
b69ab3118import {AbsorbStackEditPanel} from './AbsorbStackEditPanel';
b69ab3119import {SplitStackEditPanel, SplitStackToolbar} from './SplitStackEditPanel';
b69ab3120import {StackEditConfirmButtons} from './StackEditConfirmButtons';
b69ab3121import {StackEditSubTree} from './StackEditSubTree';
b69ab3122import {editingStackIntentionHashes, loadingStackState} from './stackEditState';
b69ab3123
b69ab3124const styles = stylex.create({
b69ab3125 container: {
b69ab3126 minWidth: '500px',
b69ab3127 minHeight: '300px',
b69ab3128 },
b69ab3129 loading: {
b69ab3130 paddingBottom: 'calc(24px + 2 * var(--pad))',
b69ab3131 },
b69ab3132 tab: {
b69ab3133 fontSize: '110%',
b69ab3134 padding: 'var(--halfpad) calc(2 * var(--pad))',
b69ab3135 },
b69ab3136});
b69ab3137
b69ab3138/// Show a <Modal /> when editing a stack.
b69ab3139export function MaybeEditStackModal() {
b69ab3140 const loadingState = useAtomValue(loadingStackState);
b69ab3141 const [[stackIntention, stackHashes], setStackIntention] = useAtom(editingStackIntentionHashes);
b69ab3142
b69ab3143 const isEditing = stackHashes.size > 0;
b69ab3144 const isLoaded = isEditing && loadingState.state === 'hasValue';
b69ab3145
b69ab3146 return isLoaded ? (
b69ab3147 {
b69ab3148 split: () => <LoadedSplitModal />,
b69ab3149 general: () => <LoadedEditStackModal />,
b69ab3150 // TODO: implement absorb model.
b69ab3151 absorb: () => <LoadedAbsorbModal />,
b69ab3152 }[stackIntention]()
b69ab3153 ) : isEditing ? (
b69ab3154 <Modal
b69ab3155 dataTestId="edit-stack-loading"
b69ab3156 dismiss={() => {
b69ab3157 // allow dismissing in loading state in case it gets stuck
b69ab3158 setStackIntention(['general', new Set()]);
b69ab3159 }}>
b69ab3160 <Center
b69ab3161 xstyle={[
b69ab3162 (stackIntention === 'general' || stackIntention === 'absorb') && styles.container,
b69ab3163 styles.loading,
b69ab3164 ]}
b69ab3165 className={stackIntention === 'split' ? 'interactive-split' : undefined}>
b69ab3166 {loadingState.state === 'hasError' ? (
b69ab3167 <ErrorNotice error={new Error(loadingState.error)} title={t('Loading stack failed')} />
b69ab3168 ) : (
b69ab3169 <Row>
b69ab3170 <Icon icon="loading" size="M" />
b69ab3171 {(loadingState.state === 'loading' && loadingState.message) ?? null}
b69ab3172 </Row>
b69ab3173 )}
b69ab3174 </Center>
b69ab3175 </Modal>
b69ab3176 ) : null;
b69ab3177}
b69ab3178
b69ab3179/** A Modal for dedicated split UI. Subset of `LoadedEditStackModal`. */
b69ab3180function LoadedSplitModal() {
b69ab3181 return (
b69ab3182 <Modal dataTestId="interactive-split-modal" className="split-single-commit-modal-contents">
b69ab3183 <SplitStackEditPanel />
b69ab3184 <Row style={{padding: 'var(--pad) 0', justifyContent: 'flex-end', zIndex: 1}}>
b69ab3185 <StackEditConfirmButtons />
b69ab3186 </Row>
b69ab3187 </Modal>
b69ab3188 );
b69ab3189}
b69ab3190
b69ab3191/**
b69ab3192 * A Modal for dedicated absorb UI.
b69ab3193 * While absorbing, the other edit stacks features are unavailable.
b69ab3194 * See `StackStateWithOperationProps.absorbChunks` for details.
b69ab3195 */
b69ab3196function LoadedAbsorbModal() {
b69ab3197 return (
b69ab3198 <Modal dataTestId="interactive-absorb-modal" className="absorb-modal-contents">
b69ab3199 <AbsorbStackEditPanel />
b69ab31100 <Row style={{padding: 'var(--pad) 0', justifyContent: 'flex-end', zIndex: 1}}>
b69ab31101 <StackEditConfirmButtons />
b69ab31102 </Row>
b69ab31103 </Modal>
b69ab31104 );
b69ab31105}
b69ab31106
b69ab31107/** A Modal for general stack editing UI. */
b69ab31108function LoadedEditStackModal() {
b69ab31109 const panels = {
b69ab31110 commits: {
b69ab31111 label: t('Commits'),
b69ab31112 render: () => (
b69ab31113 <ScrollY maxSize="calc((100vh / var(--zoom)) - 200px)">
b69ab31114 <StackEditSubTree
b69ab31115 activateSplitTab={() => {
b69ab31116 setActiveTab('split');
b69ab31117 tracker.track('StackEditInlineSplitButton');
b69ab31118 }}
b69ab31119 />
b69ab31120 </ScrollY>
b69ab31121 ),
b69ab31122 },
b69ab31123 split: {
b69ab31124 label: t('Split'),
b69ab31125 render: () => <SplitStackEditPanel />,
b69ab31126 },
b69ab31127 // TODO: re-enable the "files" tab
b69ab31128 // files: {label: t('Files'), render: () => <FileStackEditPanel />},
b69ab31129 } as const;
b69ab31130 type Tab = keyof typeof panels;
b69ab31131 const [activeTab, setActiveTab] = useState<Tab>('commits');
b69ab31132
b69ab31133 return (
b69ab31134 <Modal className="edit-stack-modal-contents">
b69ab31135 <Panels
b69ab31136 active={activeTab}
b69ab31137 panels={panels}
b69ab31138 onSelect={tab => {
b69ab31139 setActiveTab(tab);
b69ab31140 tracker.track('StackEditChangeTab', {extras: {tab}});
b69ab31141 }}
b69ab31142 xstyle={styles.container}
b69ab31143 tabXstyle={styles.tab}
b69ab31144 />
b69ab31145 <Row style={{padding: 'var(--pad) 0', justifyContent: 'flex-end'}}>
b69ab31146 {activeTab === 'split' && <SplitStackToolbar />}
b69ab31147 <FlexSpacer />
b69ab31148 <StackEditConfirmButtons />
b69ab31149 </Row>
b69ab31150 </Modal>
b69ab31151 );
b69ab31152}