| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | import type {FileRev, FileStackState} from '../fileStackState'; |
| b69ab31 | | | 9 | import type {Mode} from './FileStackEditorLines'; |
| b69ab31 | | | 10 | |
| b69ab31 | | | 11 | import {Checkbox} from 'isl-components/Checkbox'; |
| b69ab31 | | | 12 | import {Dropdown} from 'isl-components/Dropdown'; |
| b69ab31 | | | 13 | import {RadioGroup} from 'isl-components/Radio'; |
| b69ab31 | | | 14 | import {atom, useAtom} from 'jotai'; |
| b69ab31 | | | 15 | import {useState} from 'react'; |
| b69ab31 | | | 16 | import {nullthrows} from 'shared/utils'; |
| b69ab31 | | | 17 | import {Row} from '../../ComponentUtils'; |
| b69ab31 | | | 18 | import {EmptyState} from '../../EmptyState'; |
| b69ab31 | | | 19 | import {t, T} from '../../i18n'; |
| b69ab31 | | | 20 | import {FileStackEditorRow} from './FileStackEditor'; |
| b69ab31 | | | 21 | import {bumpStackEditMetric, useStackEditState} from './stackEditState'; |
| b69ab31 | | | 22 | |
| b69ab31 | | | 23 | const editModeAtom = atom<Mode>('unified-diff'); |
| b69ab31 | | | 24 | |
| b69ab31 | | | 25 | export default function FileStackEditPanel() { |
| b69ab31 | | | 26 | const [fileIdx, setFileIdx] = useState<null | number>(null); |
| b69ab31 | | | 27 | const [mode, setMode] = useAtom(editModeAtom); |
| b69ab31 | | | 28 | const [textEdit, setTextEdit] = useState(false); |
| b69ab31 | | | 29 | const stackEdit = useStackEditState(); |
| b69ab31 | | | 30 | |
| b69ab31 | | | 31 | // File list dropdown. |
| b69ab31 | | | 32 | const commitStack = stackEdit.commitStack.useFileStack(); |
| b69ab31 | | | 33 | const pathFileIdxList: Array<[string, number]> = commitStack.fileStacks |
| b69ab31 | | | 34 | .map((_f, i): [string, number] => { |
| b69ab31 | | | 35 | const label = commitStack.getFileStackDescription(i); |
| b69ab31 | | | 36 | return [label, i]; |
| b69ab31 | | | 37 | }) |
| b69ab31 | | | 38 | .toArray() |
| b69ab31 | | | 39 | .sort(); |
| b69ab31 | | | 40 | const fileSelector = ( |
| b69ab31 | | | 41 | <div |
| b69ab31 | | | 42 | className="dropdown-container" |
| b69ab31 | | | 43 | style={{ |
| b69ab31 | | | 44 | marginBottom: 'var(--pad)', |
| b69ab31 | | | 45 | width: '100%', |
| b69ab31 | | | 46 | minWidth: '500px', |
| b69ab31 | | | 47 | zIndex: 3, |
| b69ab31 | | | 48 | }}> |
| b69ab31 | | | 49 | <label htmlFor="stack-file-dropdown">File to edit</label> |
| b69ab31 | | | 50 | <Dropdown |
| b69ab31 | | | 51 | id="stack-file-dropdown" |
| b69ab31 | | | 52 | value={fileIdx == null ? 'none' : String(fileIdx)} |
| b69ab31 | | | 53 | style={{width: '100%', zIndex: 3}} |
| b69ab31 | | | 54 | onChange={e => { |
| b69ab31 | | | 55 | const idx = e.currentTarget.value; |
| b69ab31 | | | 56 | setFileIdx(idx === 'none' ? null : parseInt(idx)); |
| b69ab31 | | | 57 | }} |
| b69ab31 | | | 58 | options={ |
| b69ab31 | | | 59 | [ |
| b69ab31 | | | 60 | {value: 'none', name: t('Select a file to edit')}, |
| b69ab31 | | | 61 | ...pathFileIdxList.map(([path, idx]) => ({value: String(idx), name: path})), |
| b69ab31 | | | 62 | ] as Array<{value: string; name: string}> |
| b69ab31 | | | 63 | } |
| b69ab31 | | | 64 | /> |
| b69ab31 | | | 65 | </div> |
| b69ab31 | | | 66 | ); |
| b69ab31 | | | 67 | |
| b69ab31 | | | 68 | if (fileIdx == null) { |
| b69ab31 | | | 69 | return ( |
| b69ab31 | | | 70 | <div> |
| b69ab31 | | | 71 | {fileSelector} |
| b69ab31 | | | 72 | <EmptyState small> |
| b69ab31 | | | 73 | <T>Select a file to see all changes in a row.</T> |
| b69ab31 | | | 74 | </EmptyState> |
| b69ab31 | | | 75 | </div> |
| b69ab31 | | | 76 | ); |
| b69ab31 | | | 77 | } |
| b69ab31 | | | 78 | |
| b69ab31 | | | 79 | // Properties for file stack editing. |
| b69ab31 | | | 80 | const stack = nullthrows(commitStack.fileStacks.get(fileIdx)); |
| b69ab31 | | | 81 | const getTitle = (rev: FileRev) => |
| b69ab31 | | | 82 | commitStack.getCommitFromFileStackRev(fileIdx, rev)?.text ?? |
| b69ab31 | | | 83 | t( |
| b69ab31 | | | 84 | '(Base version)\n\n' + |
| b69ab31 | | | 85 | 'Not part of the stack being edited. ' + |
| b69ab31 | | | 86 | 'Cannot be edited here.\n\n' + |
| b69ab31 | | | 87 | 'Provided to show diff against changes in the stack.', |
| b69ab31 | | | 88 | ); |
| b69ab31 | | | 89 | const skip = (rev: FileRev) => commitStack.isAbsentFromFileStackRev(fileIdx, rev); |
| b69ab31 | | | 90 | const setStack = (newStack: FileStackState) => { |
| b69ab31 | | | 91 | const fileDesc = commitStack.getFileStackDescription(fileIdx); |
| b69ab31 | | | 92 | const newCommitStack = commitStack.setFileStack(fileIdx, newStack); |
| b69ab31 | | | 93 | stackEdit.push(newCommitStack, {name: 'fileStack', fileDesc}); |
| b69ab31 | | | 94 | bumpStackEditMetric('fileStackEdit'); |
| b69ab31 | | | 95 | }; |
| b69ab31 | | | 96 | |
| b69ab31 | | | 97 | const editorRow = ( |
| b69ab31 | | | 98 | <FileStackEditorRow |
| b69ab31 | | | 99 | stack={stack} |
| b69ab31 | | | 100 | setStack={setStack} |
| b69ab31 | | | 101 | getTitle={getTitle} |
| b69ab31 | | | 102 | skip={skip} |
| b69ab31 | | | 103 | mode={mode} |
| b69ab31 | | | 104 | textEdit={textEdit || mode === 'side-by-side-diff'} |
| b69ab31 | | | 105 | /> |
| b69ab31 | | | 106 | ); |
| b69ab31 | | | 107 | |
| b69ab31 | | | 108 | return ( |
| b69ab31 | | | 109 | <div> |
| b69ab31 | | | 110 | {fileSelector} |
| b69ab31 | | | 111 | <div |
| b69ab31 | | | 112 | style={{ |
| b69ab31 | | | 113 | marginLeft: 'calc(0px - var(--pad))', |
| b69ab31 | | | 114 | marginRight: 'calc(0px - var(--pad))', |
| b69ab31 | | | 115 | minWidth: 'calc((100vw / var(--zoom)) - 81px)', |
| b69ab31 | | | 116 | minHeight: 'calc((100vh / var(--zoom)) - 265px)', |
| b69ab31 | | | 117 | }}> |
| b69ab31 | | | 118 | {editorRow} |
| b69ab31 | | | 119 | </div> |
| b69ab31 | | | 120 | <Row> |
| b69ab31 | | | 121 | <RadioGroup |
| b69ab31 | | | 122 | choices={[ |
| b69ab31 | | | 123 | {value: 'unified-diff', title: t('Unified diff')}, |
| b69ab31 | | | 124 | {value: 'side-by-side-diff', title: t('Side by side diff')}, |
| b69ab31 | | | 125 | {value: 'unified-stack', title: t('Unified stack (advanced)')}, |
| b69ab31 | | | 126 | ]} |
| b69ab31 | | | 127 | current={mode} |
| b69ab31 | | | 128 | onChange={setMode} |
| b69ab31 | | | 129 | /> |
| b69ab31 | | | 130 | <Checkbox |
| b69ab31 | | | 131 | accessKey="t" |
| b69ab31 | | | 132 | checked={textEdit || mode === 'side-by-side-diff'} |
| b69ab31 | | | 133 | disabled={mode === 'side-by-side-diff'} |
| b69ab31 | | | 134 | onChange={() => { |
| b69ab31 | | | 135 | setTextEdit(c => !c); |
| b69ab31 | | | 136 | }}> |
| b69ab31 | | | 137 | <T>Edit text</T> |
| b69ab31 | | | 138 | </Checkbox> |
| b69ab31 | | | 139 | </Row> |
| b69ab31 | | | 140 | </div> |
| b69ab31 | | | 141 | ); |
| b69ab31 | | | 142 | } |