addons/isl/src/stackEdit/ui/ConfirmUnsavedEditsBeforeSplit.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 type {MutableRefObject} from 'react';
b69ab319import type {FieldsBeingEdited} from '../../CommitInfoView/types';
b69ab3110import type {CommitInfo} from '../../types';
b69ab3111
b69ab3112import {Button} from 'isl-components/Button';
b69ab3113import {Divider} from 'isl-components/Divider';
b69ab3114import {Icon} from 'isl-components/Icon';
b69ab3115import {useAtomValue} from 'jotai';
b69ab3116import {useCallback} from 'react';
b69ab3117import {useAutofocusRef} from 'shared/hooks';
b69ab3118import {Commit} from '../../Commit';
b69ab3119import {
b69ab3120 editedCommitMessages,
b69ab3121 getDefaultEditedCommitMessage,
b69ab3122 unsavedFieldsBeingEdited,
b69ab3123} from '../../CommitInfoView/CommitInfoState';
b69ab3124import {commitMessageFieldsSchema} from '../../CommitInfoView/CommitMessageFields';
b69ab3125import {FlexSpacer} from '../../ComponentUtils';
b69ab3126import {T, t} from '../../i18n';
b69ab3127import {readAtom, writeAtom} from '../../jotaiUtils';
b69ab3128import {CommitPreview} from '../../previews';
b69ab3129import {useModal} from '../../useModal';
b69ab3130
b69ab3131import './ConfirmUnsavedEditsBeforeSplit.css';
b69ab3132
b69ab3133type UnsavedEditConfirmKind = 'split' | 'edit_stack';
b69ab3134
b69ab3135export function useConfirmUnsavedEditsBeforeSplit(): (
b69ab3136 commits: Array<CommitInfo>,
b69ab3137 kind: UnsavedEditConfirmKind,
b69ab3138) => Promise<boolean> {
b69ab3139 const showModal = useModal();
b69ab3140 const showConfirmation = useCallback(
b69ab3141 async (commits: Array<CommitInfo>, kind: UnsavedEditConfirmKind): Promise<boolean> => {
b69ab3142 const editedCommits = commits
b69ab3143 .map(commit => [commit, readAtom(unsavedFieldsBeingEdited(commit.hash))])
b69ab3144 .filter(([_, f]) => f != null) as Array<[CommitInfo, FieldsBeingEdited]>;
b69ab3145 if (editedCommits.some(([_, f]) => Object.values(f).some(Boolean))) {
b69ab3146 const continueWithSplit = await showModal<boolean>({
b69ab3147 type: 'custom',
b69ab3148 component: ({returnResultAndDismiss}) => (
b69ab3149 <PreSplitUnsavedEditsConfirmationModal
b69ab3150 kind={kind}
b69ab3151 editedCommits={editedCommits}
b69ab3152 returnResultAndDismiss={returnResultAndDismiss}
b69ab3153 />
b69ab3154 ),
b69ab3155 title:
b69ab3156 kind === 'split'
b69ab3157 ? t('Save edits before splitting?')
b69ab3158 : t('Save edits before editing stack?'),
b69ab3159 });
b69ab3160 return continueWithSplit === true;
b69ab3161 }
b69ab3162 return true;
b69ab3163 },
b69ab3164 [showModal],
b69ab3165 );
b69ab3166
b69ab3167 return (commits: Array<CommitInfo>, kind: UnsavedEditConfirmKind) => {
b69ab3168 return showConfirmation(commits, kind);
b69ab3169 };
b69ab3170}
b69ab3171
b69ab3172function PreSplitUnsavedEditsConfirmationModal({
b69ab3173 kind,
b69ab3174 editedCommits,
b69ab3175 returnResultAndDismiss,
b69ab3176}: {
b69ab3177 kind: UnsavedEditConfirmKind;
b69ab3178 editedCommits: Array<[CommitInfo, FieldsBeingEdited]>;
b69ab3179 returnResultAndDismiss: (continueWithSplit: boolean) => unknown;
b69ab3180}) {
b69ab3181 const schema = useAtomValue(commitMessageFieldsSchema);
b69ab3182
b69ab3183 const resetEditedCommitMessage = useCallback((commit: CommitInfo) => {
b69ab3184 writeAtom(editedCommitMessages(commit.hash), getDefaultEditedCommitMessage());
b69ab3185 }, []);
b69ab3186
b69ab3187 const commitsWithUnsavedEdits = editedCommits.filter(([_, fields]) =>
b69ab3188 Object.values(fields).some(Boolean),
b69ab3189 );
b69ab3190
b69ab3191 const saveButtonRef = useAutofocusRef();
b69ab3192
b69ab3193 return (
b69ab3194 <div className="confirm-unsaved-edits-pre-split" data-testid="confirm-unsaved-edits-pre-split">
b69ab3195 <>
b69ab3196 <div>
b69ab3197 <T count={commitsWithUnsavedEdits.length}>
b69ab3198 {kind === 'split'
b69ab3199 ? 'confirmUnsavedEditsBeforeSplit'
b69ab31100 : 'confirmUnsavedEditsBeforeEditStack'}
b69ab31101 </T>
b69ab31102 </div>
b69ab31103 <div className="commits-with-unsaved-changes">
b69ab31104 {commitsWithUnsavedEdits.map(([commit, fields]) => (
b69ab31105 <div className="commit-row" key={commit.hash}>
b69ab31106 <Commit
b69ab31107 commit={commit}
b69ab31108 hasChildren={false}
b69ab31109 previewType={CommitPreview.NON_ACTIONABLE_COMMIT}
b69ab31110 />
b69ab31111 <span key={`${commit.hash}-fields`} className="byline">
b69ab31112 <T
b69ab31113 replace={{
b69ab31114 $commitTitle: commit.title,
b69ab31115 $fields: (
b69ab31116 <>
b69ab31117 {Object.entries(fields)
b69ab31118 .filter(([, value]) => value)
b69ab31119 .map(([field]) => {
b69ab31120 const icon = schema.find(f => f.key === field)?.icon;
b69ab31121 return (
b69ab31122 <span key={field} className="field-name">
b69ab31123 {icon && <Icon icon={icon} />}
b69ab31124 {field}
b69ab31125 </span>
b69ab31126 );
b69ab31127 })}
b69ab31128 </>
b69ab31129 ),
b69ab31130 }}>
b69ab31131 unsaved changes to $fields
b69ab31132 </T>
b69ab31133 </span>
b69ab31134 </div>
b69ab31135 ))}
b69ab31136 </div>
b69ab31137 <Divider />
b69ab31138 <div className="use-modal-buttons">
b69ab31139 <FlexSpacer />
b69ab31140 <Button onClick={() => returnResultAndDismiss(false)}>
b69ab31141 <T>Cancel</T>
b69ab31142 </Button>
b69ab31143 <Button
b69ab31144 onClick={() => {
b69ab31145 for (const [commit] of editedCommits) {
b69ab31146 resetEditedCommitMessage(commit);
b69ab31147 }
b69ab31148 returnResultAndDismiss(true); // continue with split
b69ab31149 }}>
b69ab31150 <T>Discard Edits</T>
b69ab31151 </Button>
b69ab31152 <Button
b69ab31153 ref={saveButtonRef as MutableRefObject<null>}
b69ab31154 primary
b69ab31155 onClick={() => {
b69ab31156 // Unsaved edits will be automatically loaded by the split as the commits' text
b69ab31157 returnResultAndDismiss(true); // continue with split
b69ab31158 }}>
b69ab31159 <T>Save Edits</T>
b69ab31160 </Button>
b69ab31161 </div>
b69ab31162 </>
b69ab31163 </div>
b69ab31164 );
b69ab31165}