first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
import { sendMB } from '../../../../infrastructure/event-tracking'
import { useLayoutContext } from '../../../../shared/context/layout-context'
import { restoreFile } from '../../services/api'
import { isFileRemoved } from '../../utils/file-diff'
import { useHistoryContext } from '../history-context'
import type { HistoryContextValue } from '../types/history-context-value'
import { useErrorHandler } from 'react-error-boundary'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { findInTree } from '@/features/file-tree/util/find-in-tree'
import { useCallback, useEffect, useState } from 'react'
import { RestoreFileResponse } from '@/features/history/services/types/restore-file'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
type RestorationState =
| 'idle'
| 'restoring'
| 'waitingForFileTree'
| 'complete'
| 'error'
| 'timedOut'
export function useRestoreDeletedFile() {
const { projectId } = useHistoryContext()
const { setView } = useLayoutContext()
const { openDocWithId, openFileWithId } = useEditorManagerContext()
const handleError = useErrorHandler()
const { fileTreeData } = useFileTreeData()
const [state, setState] = useState<RestorationState>('idle')
const [restoredFileMetadata, setRestoredFileMetadata] =
useState<RestoreFileResponse | null>(null)
const isLoading = state === 'restoring' || state === 'waitingForFileTree'
useEffect(() => {
if (state === 'waitingForFileTree' && restoredFileMetadata) {
const result = findInTree(fileTreeData, restoredFileMetadata.id)
if (result) {
setState('complete')
const { _id: id } = result.entity
setView('editor')
if (restoredFileMetadata.type === 'doc') {
openDocWithId(id)
} else {
openFileWithId(id)
}
}
}
}, [
state,
fileTreeData,
restoredFileMetadata,
openDocWithId,
openFileWithId,
setView,
])
useEffect(() => {
if (state === 'waitingForFileTree') {
const timer = window.setTimeout(() => {
setState('timedOut')
handleError(new Error('timed out'))
}, 3000)
return () => {
window.clearTimeout(timer)
}
}
}, [handleError, state])
const restoreDeletedFile = useCallback(
(selection: HistoryContextValue['selection']) => {
const { selectedFile, files } = selection
if (
selectedFile &&
selectedFile.pathname &&
isFileRemoved(selectedFile)
) {
const file = files.find(file => file.pathname === selectedFile.pathname)
if (file && isFileRemoved(file)) {
sendMB('history-v2-restore-deleted')
setState('restoring')
restoreFile(projectId, {
...selectedFile,
pathname: file.newPathname ?? file.pathname,
}).then(
(data: RestoreFileResponse) => {
setRestoredFileMetadata(data)
setState('waitingForFileTree')
},
error => {
setState('error')
handleError(error)
}
)
}
}
},
[handleError, projectId]
)
return { restoreDeletedFile, isLoading }
}

View File

@@ -0,0 +1,36 @@
import { useCallback, useState } from 'react'
import { useErrorHandler } from 'react-error-boundary'
import { restoreProjectToVersion } from '../../services/api'
import { useLayoutContext } from '@/shared/context/layout-context'
type RestorationState = 'initial' | 'restoring' | 'restored' | 'error'
export const useRestoreProject = () => {
const handleError = useErrorHandler()
const { setView } = useLayoutContext()
const [restorationState, setRestorationState] =
useState<RestorationState>('initial')
const restoreProject = useCallback(
(projectId: string, version: number) => {
setRestorationState('restoring')
restoreProjectToVersion(projectId, version)
.then(() => {
setRestorationState('restored')
setView('editor')
})
.catch(err => {
setRestorationState('error')
handleError(err)
})
},
[handleError, setView]
)
return {
restorationState,
restoreProject,
isRestoring: restorationState === 'restoring',
}
}

View File

@@ -0,0 +1,104 @@
import { useLayoutContext } from '../../../../shared/context/layout-context'
import { restoreFileToVersion } from '../../services/api'
import { isFileRemoved } from '../../utils/file-diff'
import { useHistoryContext } from '../history-context'
import type { HistoryContextValue } from '../types/history-context-value'
import { useErrorHandler } from 'react-error-boundary'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { findInTree } from '@/features/file-tree/util/find-in-tree'
import { useCallback, useEffect, useState } from 'react'
import { RestoreFileResponse } from '../../services/types/restore-file'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
const RESTORE_FILE_TIMEOUT = 3000
type RestoreState =
| 'idle'
| 'restoring'
| 'waitingForFileTree'
| 'complete'
| 'error'
| 'timedOut'
export function useRestoreSelectedFile() {
const { projectId } = useHistoryContext()
const { setView } = useLayoutContext()
const { openDocWithId, openFileWithId } = useEditorManagerContext()
const handleError = useErrorHandler()
const { fileTreeData } = useFileTreeData()
const [state, setState] = useState<RestoreState>('idle')
const [restoredFileMetadata, setRestoredFileMetadata] =
useState<RestoreFileResponse | null>(null)
const isLoading = state === 'restoring' || state === 'waitingForFileTree'
useEffect(() => {
if (state === 'waitingForFileTree' && restoredFileMetadata) {
const result = findInTree(fileTreeData, restoredFileMetadata.id)
if (result) {
setState('complete')
const { _id: id } = result.entity
setView('editor')
if (restoredFileMetadata.type === 'doc') {
openDocWithId(id)
} else {
openFileWithId(id)
}
}
}
}, [
state,
fileTreeData,
restoredFileMetadata,
openDocWithId,
openFileWithId,
setView,
])
useEffect(() => {
if (state === 'waitingForFileTree') {
const timer = window.setTimeout(() => {
setState('timedOut')
handleError(new Error('timed out'))
}, RESTORE_FILE_TIMEOUT)
return () => {
window.clearTimeout(timer)
}
}
}, [handleError, state])
const restoreSelectedFile = useCallback(
(selection: HistoryContextValue['selection']) => {
const { selectedFile, files } = selection
if (selectedFile && selectedFile.pathname) {
const file = files.find(file => file.pathname === selectedFile.pathname)
if (file) {
const deletedAtV = isFileRemoved(file) ? file.deletedAtV : undefined
const toVersion = deletedAtV ?? selection.updateRange?.toV
if (!toVersion) {
return
}
setState('restoring')
restoreFileToVersion(projectId, file.pathname, toVersion).then(
(data: RestoreFileResponse) => {
setRestoredFileMetadata(data)
setState('waitingForFileTree')
},
error => {
setState('error')
handleError(error)
}
)
}
}
},
[handleError, projectId]
)
return { restoreSelectedFile, isLoading }
}