addons/isl/src/ShelvedChanges.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 {Button} from 'isl-components/Button';
b69ab319import {ErrorNotice} from 'isl-components/ErrorNotice';
b69ab3110import {Icon} from 'isl-components/Icon';
b69ab3111import {Kbd} from 'isl-components/Kbd';
b69ab3112import {KeyCode, Modifier} from 'isl-components/KeyboardShortcuts';
b69ab3113import {Subtle} from 'isl-components/Subtle';
b69ab3114import {Tooltip} from 'isl-components/Tooltip';
b69ab3115import {useAtom} from 'jotai';
b69ab3116import {useEffect} from 'react';
b69ab3117import {ComparisonType} from 'shared/Comparison';
b69ab3118import serverAPI from './ClientToServerAPI';
b69ab3119import {OpenComparisonViewButton} from './ComparisonView/OpenComparisonViewButton';
b69ab3120import {FlexSpacer, Row} from './ComponentUtils';
b69ab3121import {DropdownFields} from './DropdownFields';
b69ab3122import {EmptyState} from './EmptyState';
b69ab3123import {useCommandEvent} from './ISLShortcuts';
b69ab3124import {OperationDisabledButton} from './OperationDisabledButton';
b69ab3125import {ChangedFiles} from './UncommittedChanges';
b69ab3126import {T, t} from './i18n';
b69ab3127import {atomLoadableWithRefresh} from './jotaiUtils';
b69ab3128import {DeleteShelveOperation} from './operations/DeleteShelveOperation';
b69ab3129import {UnshelveOperation} from './operations/UnshelveOperation';
b69ab3130import {RelativeDate} from './relativeDate';
b69ab3131
b69ab3132import './ShelvedChanges.css';
b69ab3133
b69ab3134const shelvedChangesState = atomLoadableWithRefresh(async _get => {
b69ab3135 serverAPI.postMessage({
b69ab3136 type: 'fetchShelvedChanges',
b69ab3137 });
b69ab3138
b69ab3139 const result = await serverAPI.nextMessageMatching('fetchedShelvedChanges', () => true);
b69ab3140 if (result.shelvedChanges.error != null) {
b69ab3141 throw new Error(result.shelvedChanges.error.toString());
b69ab3142 }
b69ab3143 return result.shelvedChanges.value;
b69ab3144});
b69ab3145
b69ab3146export function ShelvedChangesMenu() {
b69ab3147 const additionalToggles = useCommandEvent('ToggleShelvedChangesDropdown');
b69ab3148 return (
b69ab3149 <Tooltip
b69ab3150 component={dismiss => <ShelvedChangesList dismiss={dismiss} />}
b69ab3151 trigger="click"
b69ab3152 placement="bottom"
b69ab3153 additionalToggles={additionalToggles.asEventTarget()}
b69ab3154 group="topbar"
b69ab3155 title={
b69ab3156 <T replace={{$shortcut: <Kbd keycode={KeyCode.S} modifiers={[Modifier.ALT]} />}}>
b69ab3157 Shelved Changes ($shortcut)
b69ab3158 </T>
b69ab3159 }>
b69ab3160 <Button icon data-testid="shelved-changes-button">
b69ab3161 <Icon icon="archive" />
b69ab3162 </Button>
b69ab3163 </Tooltip>
b69ab3164 );
b69ab3165}
b69ab3166
b69ab3167function ShelvedChangesList({dismiss}: {dismiss: () => void}) {
b69ab3168 const [shelvedChanges, refresh] = useAtom(shelvedChangesState);
b69ab3169 useEffect(() => {
b69ab3170 // make sure we fetch whenever loading the shelved changes list
b69ab3171 refresh();
b69ab3172 }, [refresh]);
b69ab3173 return (
b69ab3174 <DropdownFields
b69ab3175 title={
b69ab3176 <Row>
b69ab3177 <T>Shelved Changes</T>{' '}
b69ab3178 <Tooltip
b69ab3179 title={t(
b69ab3180 'You can Shelve a set of uncommitted changes to save them for later, via the Shelve button in the list of uncommitted changes.\n\nHere you can view and Unshelve previously shelved changes.',
b69ab3181 )}>
b69ab3182 <Icon icon="info" />
b69ab3183 </Tooltip>
b69ab3184 </Row>
b69ab3185 }
b69ab3186 icon="archive"
b69ab3187 className="shelved-changes-dropdown"
b69ab3188 data-testid="shelved-changes-dropdown">
b69ab3189 {shelvedChanges.state === 'loading' ? (
b69ab3190 <Icon icon="loading" />
b69ab3191 ) : shelvedChanges.state === 'hasError' ? (
b69ab3192 <ErrorNotice
b69ab3193 title="Could not fetch shelved changes"
b69ab3194 error={shelvedChanges.error as Error}
b69ab3195 />
b69ab3196 ) : shelvedChanges.data.length === 0 ? (
b69ab3197 <EmptyState small>
b69ab3198 <T>No shelved changes</T>
b69ab3199 </EmptyState>
b69ab31100 ) : (
b69ab31101 <div className="shelved-changes-list">
b69ab31102 {shelvedChanges.data.map(change => {
b69ab31103 const comparison = {
b69ab31104 type: ComparisonType.Committed,
b69ab31105 hash: change.hash,
b69ab31106 };
b69ab31107 return (
b69ab31108 <div key={change.hash} className="shelved-changes-item">
b69ab31109 <div className="shelved-changes-item-row">
b69ab31110 <span className="shelve-name">{change.name}</span>
b69ab31111 <Subtle>
b69ab31112 <RelativeDate date={change.date} useShortVariant />
b69ab31113 </Subtle>
b69ab31114 <FlexSpacer />
b69ab31115 <Tooltip title={t('Remove from the list of shelved changes')}>
b69ab31116 <OperationDisabledButton
b69ab31117 kind="icon"
b69ab31118 contextKey={`delete-shelve-${change.hash}`}
b69ab31119 data-testid={`delete-shelve-${change.hash}`}
b69ab31120 className="unshelve-button"
b69ab31121 runOperation={() => {
b69ab31122 dismiss();
b69ab31123 return new DeleteShelveOperation(change);
b69ab31124 }}
b69ab31125 icon={<Icon icon="trash" />}></OperationDisabledButton>
b69ab31126 </Tooltip>
b69ab31127 <Tooltip
b69ab31128 title={t(
b69ab31129 'Apply these changes without removing this from your list of shelved changes',
b69ab31130 )}>
b69ab31131 <OperationDisabledButton
b69ab31132 kind="icon"
b69ab31133 contextKey={`unshelve-keep-${change.hash}`}
b69ab31134 className="unshelve-button"
b69ab31135 runOperation={() => {
b69ab31136 dismiss();
b69ab31137 return new UnshelveOperation(change, true);
b69ab31138 }}
b69ab31139 icon={<Icon icon="layers-active" slot="start" />}>
b69ab31140 <T>Apply</T>
b69ab31141 </OperationDisabledButton>
b69ab31142 </Tooltip>
b69ab31143 <Tooltip
b69ab31144 title={t(
b69ab31145 'Apply these changes and remove this from your list of shelved changes',
b69ab31146 )}>
b69ab31147 <OperationDisabledButton
b69ab31148 contextKey={`unshelve-${change.hash}`}
b69ab31149 className="unshelve-button"
b69ab31150 runOperation={() => {
b69ab31151 dismiss();
b69ab31152 return new UnshelveOperation(change, false);
b69ab31153 }}
b69ab31154 icon={<Icon icon="layers-active" slot="start" />}>
b69ab31155 <T>Unshelve</T>
b69ab31156 </OperationDisabledButton>
b69ab31157 </Tooltip>
b69ab31158 </div>
b69ab31159 <OpenComparisonViewButton
b69ab31160 comparison={comparison}
b69ab31161 buttonText={<T>View Changes</T>}
b69ab31162 onClick={dismiss}
b69ab31163 />
b69ab31164 <div className="shelved-changes-item-row">
b69ab31165 <ChangedFiles
b69ab31166 filesSubset={change.filesSample}
b69ab31167 totalFiles={change.totalFileCount}
b69ab31168 comparison={comparison}
b69ab31169 />
b69ab31170 </div>
b69ab31171 </div>
b69ab31172 );
b69ab31173 })}
b69ab31174 </div>
b69ab31175 )}
b69ab31176 </DropdownFields>
b69ab31177 );
b69ab31178}