import _ from 'lodash' import type { Doc } from '../../../../../types/doc' import type { FileDiff, FileRenamed } from '../services/types/file' // `Partial` because the `reducePathsToTree` function was copied directly // from a javascript file without proper type system and the logic is not typescript-friendly. // TODO: refactor the function to have a proper type system type FileTreeEntity = Partial<{ name: string type: 'file' | 'folder' oldPathname: string newPathname: string pathname: string children: FileTreeEntity[] operation: 'edited' | 'added' | 'renamed' | 'removed' }> export function reducePathsToTree( currentFileTree: FileTreeEntity[], fileObject: FileTreeEntity ) { const filePathParts = fileObject?.pathname?.split('/') ?? '' let currentFileTreeLocation = currentFileTree for (let index = 0; index < filePathParts.length; index++) { let fileTreeEntity: FileTreeEntity | null = {} const pathPart = filePathParts[index] const isFile = index === filePathParts.length - 1 if (isFile) { fileTreeEntity = _.clone(fileObject) fileTreeEntity.name = pathPart fileTreeEntity.type = 'file' currentFileTreeLocation.push(fileTreeEntity) } else { fileTreeEntity = _.find(currentFileTreeLocation, entity => entity.name === pathPart) ?? null if (fileTreeEntity == null) { fileTreeEntity = { name: pathPart, type: 'folder', children: [], } currentFileTreeLocation.push(fileTreeEntity) } currentFileTreeLocation = fileTreeEntity.children ?? [] } } return currentFileTree } type HistoryFileTree = { docs?: Doc[] folders: HistoryFileTree[] name: string // `id` and `fileRefs` are both required from react file tree. // TODO: update react file tree to make the data optional so we can delete these keys id: '' fileRefs: [] } export function fileTreeDiffToFileTreeData( fileTreeDiff: FileTreeEntity[], currentFolderName = 'rootFolder' // default value from angular version ): HistoryFileTree { const folders: HistoryFileTree[] = [] const docs: Doc[] = [] for (const file of fileTreeDiff) { if (file.type === 'file') { docs.push({ _id: '', name: file.name ?? '', }) } else if (file.type === 'folder') { if (file.children) { const folder = fileTreeDiffToFileTreeData(file.children, file.name) folders.push(folder) } } } return { docs, folders, name: currentFolderName, id: '', fileRefs: [], } } // TODO: refactor the oldPathname/newPathname data // It's an artifact from the angular version. // Our API returns `pathname` and `newPathname` for `renamed` operation // In the angular version, we change the key of the data: // 1. `pathname` -> `oldPathname` // 2. `newPathname` -> `pathname` // 3. Delete the `newPathname` key from the object // This is because the angular version wants to generalize the API usage // In the operation other than the `renamed` operation, the diff API (/project/:id/diff) consumes the `pathname` // But the `renamed` operation consumes the `newPathname` instead of the `pathname` data // // This behaviour can be refactored by introducing a conditional when calling the API // i.e if `renamed` -> use `newPathname`, else -> use `pathname` export function renamePathnameKey(file: FileRenamed): FileRenamed { return { oldPathname: file.pathname, pathname: file.newPathname as string, operation: file.operation, } } export function isFileRenamed(fileDiff: FileDiff): fileDiff is FileRenamed { return (fileDiff as FileRenamed).operation === 'renamed' }