diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 215529a616..8c93d082a9 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -715,6 +715,8 @@ "resend": "", "resend_confirmation_email": "", "resending_confirmation_email": "", + "restore_file": "", + "restoring": "", "reverse_x_sort_order": "", "revert_pending_plan_change": "", "review": "", diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare.tsx index 4f341a9e07..332a0d4062 100644 --- a/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare.tsx +++ b/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare.tsx @@ -44,7 +44,6 @@ function Compare({ }, comparing: true, files: [], - pathname: null, }) } diff --git a/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx b/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx index fef76114ae..d73276f296 100644 --- a/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx +++ b/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx @@ -24,7 +24,6 @@ function HistoryVersionDetails({ updateRange: { fromV, toV, fromVTimestamp, toVTimestamp }, comparing: false, files: [], - pathname: null, }) } } diff --git a/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx b/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx index 58b74e2c87..3a07c2fc93 100644 --- a/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx +++ b/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx @@ -1,7 +1,7 @@ -import Toolbar from './toolbar' +import { useEffect, useState } from 'react' +import Toolbar from './toolbar/toolbar' import Main from './main' import { Diff, DocDiffResponse } from '../../services/types/doc' -import { useEffect, useState } from 'react' import { Nullable } from '../../../../../../types/utils' import { useHistoryContext } from '../../context/history-context' import { diffDoc } from '../../services/api' @@ -14,35 +14,37 @@ function DiffView() { const { isLoading, runAsync } = useAsync() - const { updateRange, pathname } = selection + const { updateRange, selectedFile } = selection useEffect(() => { - if (!updateRange || !pathname) { + if (!updateRange || !selectedFile?.pathname) { return } const { fromV, toV } = updateRange // TODO: Error handling - runAsync(diffDoc(projectId, fromV, toV, pathname)).then(data => { - let diff: Diff | undefined + runAsync(diffDoc(projectId, fromV, toV, selectedFile.pathname)).then( + data => { + let diff: Diff | undefined - if (!data?.diff) { - setDiff(null) - } - - if ('binary' in data.diff) { - diff = { binary: true } - } else { - diff = { - binary: false, - docDiff: highlightsFromDiffResponse(data.diff), + if (!data?.diff) { + setDiff(null) } - } - setDiff(diff) - }) - }, [projectId, runAsync, updateRange, pathname]) + if ('binary' in data.diff) { + diff = { binary: true } + } else { + diff = { + binary: false, + docDiff: highlightsFromDiffResponse(data.diff), + } + } + + setDiff(diff) + } + ) + }, [projectId, runAsync, updateRange, selectedFile]) return (
diff --git a/services/web/frontend/js/features/history/components/diff-view/toolbar.tsx b/services/web/frontend/js/features/history/components/diff-view/toolbar.tsx deleted file mode 100644 index b3393d2c28..0000000000 --- a/services/web/frontend/js/features/history/components/diff-view/toolbar.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Trans, useTranslation } from 'react-i18next' -import { formatTime } from '../../../utils/format-date' -import type { Nullable } from '../../../../../../types/utils' -import type { Diff } from '../../services/types/doc' -import type { HistoryContextValue } from '../../context/types/history-context-value' - -type ToolbarProps = { - diff: Nullable - selection: HistoryContextValue['selection'] -} - -function Toolbar({ diff, selection }: ToolbarProps) { - const { t } = useTranslation() - - if (!selection) return null - - return ( -
-
- {selection.comparing ? ( - ]} - values={{ - startTime: formatTime( - selection.updateRange?.fromVTimestamp, - 'Do MMMM · h:mm a' - ), - endTime: formatTime( - selection.updateRange?.toVTimestamp, - 'Do MMMM · h:mm a' - ), - }} - /> - ) : ( - ]} - values={{ - endTime: formatTime( - selection.updateRange?.toVTimestamp, - 'Do MMMM · h:mm a' - ), - }} - /> - )} -
- {selection.pathname ? ( -
- {t('x_changes_in', { - count: diff?.docDiff?.highlights.length ?? 0, - })} -   - {getFileName(selection)} -
- ) : null} -
- ) -} - -function getFileName(selection: HistoryContextValue['selection']) { - const filePathParts = selection?.pathname?.split('/') - let fileName - if (filePathParts) { - fileName = filePathParts[filePathParts.length - 1] - } - - return fileName -} - -export default Toolbar diff --git a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-datetime.tsx b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-datetime.tsx new file mode 100644 index 0000000000..020881cfd2 --- /dev/null +++ b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-datetime.tsx @@ -0,0 +1,43 @@ +import { Trans } from 'react-i18next' +import { formatTime } from '../../../../utils/format-date' +import type { HistoryContextValue } from '../../../context/types/history-context-value' + +type ToolbarDatetimeProps = { + selection: HistoryContextValue['selection'] +} + +export default function ToolbarDatetime({ selection }: ToolbarDatetimeProps) { + return ( +
+ {selection.comparing ? ( + ]} + values={{ + startTime: formatTime( + selection.updateRange?.fromVTimestamp, + 'Do MMMM · h:mm a' + ), + endTime: formatTime( + selection.updateRange?.toVTimestamp, + 'Do MMMM · h:mm a' + ), + }} + /> + ) : ( + ]} + values={{ + endTime: formatTime( + selection.updateRange?.toVTimestamp, + 'Do MMMM · h:mm a' + ), + }} + /> + )} +
+ ) +} diff --git a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-file-info.tsx b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-file-info.tsx new file mode 100644 index 0000000000..3171dcbe81 --- /dev/null +++ b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-file-info.tsx @@ -0,0 +1,36 @@ +import { useTranslation } from 'react-i18next' +import type { HistoryContextValue } from '../../../context/types/history-context-value' +import type { Diff } from '../../../services/types/doc' +import type { Nullable } from '../../../../../../../types/utils' + +type ToolbarFileInfoProps = { + diff: Nullable + selection: HistoryContextValue['selection'] +} + +export default function ToolbarFileInfo({ + diff, + selection, +}: ToolbarFileInfoProps) { + const { t } = useTranslation() + + return ( +
+ {t('x_changes_in', { + count: diff?.docDiff?.highlights?.length ?? 0, + })} +   + {getFileName(selection)} +
+ ) +} + +function getFileName(selection: HistoryContextValue['selection']) { + const filePathParts = selection?.selectedFile?.pathname?.split('/') + let fileName + if (filePathParts) { + fileName = filePathParts[filePathParts.length - 1] + } + + return fileName +} diff --git a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-button.tsx b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-button.tsx new file mode 100644 index 0000000000..ec51e5f340 --- /dev/null +++ b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-button.tsx @@ -0,0 +1,32 @@ +import { Button } from 'react-bootstrap' +import { useTranslation } from 'react-i18next' +import { useHistoryContext } from '../../../context/history-context' +import { useRestoreDeletedFile } from '../../../context/hooks/use-restore-deleted-file' +import type { HistoryContextValue } from '../../../context/types/history-context-value' + +type ToolbarRestoreFileButtonProps = { + selection: HistoryContextValue['selection'] +} + +export default function ToolbarRestoreFileButton({ + selection, +}: ToolbarRestoreFileButtonProps) { + const { t } = useTranslation() + const { loadingState } = useHistoryContext() + + const onRestoreFile = useRestoreDeletedFile() + + return ( + + ) +} diff --git a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar.tsx b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar.tsx new file mode 100644 index 0000000000..9f75b8f449 --- /dev/null +++ b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar.tsx @@ -0,0 +1,29 @@ +import type { Nullable } from '../../../../../../../types/utils' +import type { Diff } from '../../../services/types/doc' +import type { HistoryContextValue } from '../../../context/types/history-context-value' +import ToolbarDatetime from './toolbar-datetime' +import ToolbarFileInfo from './toolbar-file-info' +import ToolbarRestoreFileButton from './toolbar-restore-file-button' +import { isFileRemoved } from '../../../utils/file-diff' + +type ToolbarProps = { + diff: Nullable + selection: HistoryContextValue['selection'] +} + +export default function Toolbar({ diff, selection }: ToolbarProps) { + const showRestoreFileButton = + selection.selectedFile && isFileRemoved(selection.selectedFile) + + return ( +
+ + {selection.selectedFile?.pathname ? ( + + ) : null} + {showRestoreFileButton ? ( + + ) : null} +
+ ) +} diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx index c2e8404a68..a6fe5851d4 100644 --- a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx +++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-doc.tsx @@ -1,22 +1,20 @@ +import classNames from 'classnames' import HistoryFileTreeItem from './history-file-tree-item' import iconTypeFromName from '../../../file-tree/util/icon-type-from-name' import Icon from '../../../../shared/components/icon' import { useFileTreeItemSelection } from '../../context/hooks/use-file-tree-item-selection' -import { DiffOperation } from '../../services/types/diff-operation' -import classNames from 'classnames' +import type { FileDiff } from '../../services/types/file' type HistoryFileTreeDocProps = { + file: FileDiff name: string - pathname: string - operation?: DiffOperation } export default function HistoryFileTreeDoc({ + file, name, - pathname, - operation, }: HistoryFileTreeDocProps) { - const { isSelected, onClick } = useFileTreeItemSelection(pathname) + const { isSelected, onClick } = useFileTreeItemSelection(file) return (
  • { return ( - + ) })} {children} diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx index b8d9509e23..dbcd8d137d 100644 --- a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx +++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-item.tsx @@ -1,10 +1,11 @@ +import classNames from 'classnames' import type { ReactNode } from 'react' -import type { DiffOperation } from '../../services/types/diff-operation' +import type { FileOperation } from '../../services/types/file-operation' import Badge from '../../../../shared/components/badge' type FileTreeItemProps = { name: string - operation?: DiffOperation + operation?: FileOperation icons: ReactNode } @@ -17,7 +18,13 @@ export default function HistoryFileTreeItem({
    {icons}