Merge pull request #22836 from overleaf/dp-filetree-typescript

Convert file-tree util files to typescript

GitOrigin-RevId: bdf8d0655a543a216f028bc8477c3ee47aba5566
This commit is contained in:
David
2025-01-21 13:10:39 +00:00
committed by Copybot
parent 11695ca18a
commit 60698d8d37
12 changed files with 85 additions and 41 deletions
@@ -60,7 +60,7 @@ const FileTreeActionableContext = createContext<
canRename: boolean
canCreate: boolean
parentFolderId: string
selectedFileName: string | null
selectedFileName: string | null | undefined
isDuplicate: (parentFolderId: string, name: string) => boolean
startRenaming: any
finishRenaming: any
@@ -320,7 +320,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
// moves entities. Tree is updated immediately and data are sync'd after.
const finishMoving = useCallback(
(toFolderId, draggedEntityIds) => {
(toFolderId: string, draggedEntityIds: Set<string>) => {
dispatch({ type: ACTION_TYPES.MOVING })
// find entities and filter out no-ops and nested files
@@ -624,7 +624,7 @@ function validateCreate(
function validateRename(
fileTreeData: Folder,
found: { parentFolderId: string; path: string; type: string },
found: { parentFolderId: string; path: string[]; type: string },
newName: string
) {
if (!isCleanFilename(newName)) {
@@ -15,6 +15,7 @@ import { useFileTreeActionable } from './file-tree-actionable'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
import { isAcceptableFile } from '@/features/file-tree/util/is-acceptable-file'
import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
const DRAGGABLE_TYPE = 'ENTITY'
export const FileTreeDraggableProvider: FC<{
@@ -171,7 +172,7 @@ function getDraggedTitle(
}
// Get all children folder ids of any of the dragged items.
function getForbiddenFolderIds(draggedItems: Set<any>) {
function getForbiddenFolderIds(draggedItems: Set<FileTreeFindResult>) {
const draggedFoldersArray = Array.from(draggedItems)
.filter(draggedItem => {
return draggedItem.type === 'folder'
@@ -10,7 +10,7 @@ import {
} from 'react'
import classNames from 'classnames'
import _ from 'lodash'
import { findInTree } from '../util/find-in-tree'
import { findInTree, findInTreeOrThrow } from '../util/find-in-tree'
import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
import { useProjectContext } from '../../../shared/context/project-context'
import { useLayoutContext } from '../../../shared/context/layout-context'
@@ -177,7 +177,7 @@ export const FileTreeSelectableProvider: FC<{
}
const _selectedEntities = Array.from(selectedEntityIds)
.map(id => findInTree(fileTreeData, id))
.filter(Boolean)
.filter(entity => entity !== null)
onSelect(_selectedEntities)
setSelectedEntities(_selectedEntities)
}, [
@@ -282,7 +282,7 @@ export function useSelectableEntity(id: string, type: string) {
const chooseView = useCallback(() => {
for (const id of selectedEntityIds) {
const selectedEntity = findInTree(fileTreeData, id)
const selectedEntity = findInTreeOrThrow(fileTreeData, id)
if (selectedEntity.type === 'doc') {
return 'editor'
@@ -1,4 +1,4 @@
import { postJSON } from '../../../infrastructure/fetch-json'
export const refreshProjectMetadata = (projectId, entityId) =>
export const refreshProjectMetadata = (projectId: string, entityId: string) =>
postJSON(`/project/${projectId}/doc/${entityId}/metadata`)
@@ -1,6 +1,15 @@
import getMeta from '@/utils/meta'
import { Folder } from '../../../../../types/folder'
export function countFiles(fileTreeData) {
type FileCountStatus = 'success' | 'warning' | 'error'
type FileCount = {
value: number
status: FileCountStatus
limit: number
}
export function countFiles(fileTreeData: Folder | undefined): 0 | FileCount {
if (!fileTreeData) {
return 0
}
@@ -13,7 +22,11 @@ export function countFiles(fileTreeData) {
return { value, status, limit }
}
function fileCountStatus(value, limit, range) {
function fileCountStatus(
value: number,
limit: number,
range: number
): FileCountStatus {
if (value >= limit) {
return 'error'
}
@@ -26,8 +39,8 @@ function fileCountStatus(value, limit, range) {
}
// Copied and adapted from ProjectEntityMongoUpdateHandler
function _countElements(rootFolder) {
function countFolder(folder) {
function _countElements(rootFolder: Folder): number {
function countFolder(folder: Folder) {
if (folder == null) {
return 0
}
@@ -1,20 +1,25 @@
import OError from '@overleaf/o-error'
import { Folder } from '../../../../../types/folder'
import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
export function findInTreeOrThrow(tree, id) {
export function findInTreeOrThrow(tree: Folder, id: string) {
const found = findInTree(tree, id)
if (found) return found
throw new OError('Entity not found in tree', { entityId: id })
}
export function findAllInTreeOrThrow(tree, ids) {
const list = new Set()
export function findAllInTreeOrThrow(
tree: Folder,
ids: Set<string>
): Set<FileTreeFindResult> {
const list: Set<FileTreeFindResult> = new Set()
ids.forEach(id => {
list.add(findInTreeOrThrow(tree, id))
})
return list
}
export function findAllFolderIdsInFolder(folder) {
export function findAllFolderIdsInFolder(folder: Folder): Set<string> {
const list = new Set([folder._id])
for (const index in folder.folders) {
const subFolder = folder.folders[index]
@@ -25,8 +30,8 @@ export function findAllFolderIdsInFolder(folder) {
return list
}
export function findAllFolderIdsInFolders(folders) {
const list = new Set()
export function findAllFolderIdsInFolders(folders: Set<Folder>): Set<string> {
const list: Set<string> = new Set()
folders.forEach(folder => {
findAllFolderIdsInFolder(folder).forEach(folderId => {
list.add(folderId)
@@ -35,7 +40,11 @@ export function findAllFolderIdsInFolders(folders) {
return list
}
export function findInTree(tree, id, path) {
export function findInTree(
tree: Folder,
id: string,
path?: string[]
): FileTreeFindResult | null {
if (!path) {
path = [tree._id]
}
@@ -48,7 +57,7 @@ export function findInTree(tree, id, path) {
parent: tree.docs,
parentFolderId: tree._id,
path,
index,
index: Number(index),
}
}
}
@@ -62,7 +71,7 @@ export function findInTree(tree, id, path) {
parent: tree.fileRefs,
parentFolderId: tree._id,
path,
index,
index: Number(index),
}
}
}
@@ -76,7 +85,7 @@ export function findInTree(tree, id, path) {
parent: tree.folders,
parentFolderId: tree._id,
path,
index,
index: Number(index),
}
}
const found = findInTree(folder, id, path.concat(folder._id))
@@ -1,16 +1,16 @@
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
export default function iconTypeFromName(name) {
export default function iconTypeFromName(name: string): string {
let ext = name.split('.').pop()
ext = ext ? ext.toLowerCase() : ext
if (['png', 'pdf', 'jpg', 'jpeg', 'gif'].includes(ext)) {
if (ext && ['png', 'pdf', 'jpg', 'jpeg', 'gif'].includes(ext)) {
return 'image'
} else if (['csv', 'xls', 'xlsx'].includes(ext)) {
} else if (ext && ['csv', 'xls', 'xlsx'].includes(ext)) {
return isBootstrap5() ? 'table_chart' : 'table'
} else if (['py', 'r'].includes(ext)) {
} else if (ext && ['py', 'r'].includes(ext)) {
return isBootstrap5() ? 'code' : 'file-text'
} else if (['bib'].includes(ext)) {
} else if (ext && ['bib'].includes(ext)) {
return isBootstrap5() ? 'menu_book' : 'book'
} else {
return isBootstrap5() ? 'description' : 'file'
@@ -1,4 +1,4 @@
import { findInTree } from '../util/find-in-tree'
import { findInTreeOrThrow } from '../util/find-in-tree'
import { Folder } from '../../../../../types/folder'
import { Doc } from '../../../../../types/doc'
import { FileRef } from '../../../../../types/file-ref'
@@ -20,7 +20,7 @@ export function findFileByNameInFolder(
name: string
): Doc | FileRef | undefined {
if (tree._id !== parentFolderId) {
tree = findInTree(tree, parentFolderId).entity
tree = findInTreeOrThrow(tree, parentFolderId).entity as Folder
}
return (
@@ -35,7 +35,7 @@ export function findFolderByNameInFolder(
name: string
): Folder | undefined {
if (tree._id !== parentFolderId) {
tree = findInTree(tree, parentFolderId).entity
tree = findInTreeOrThrow(tree, parentFolderId).entity as Folder
}
return tree.folders.find(entity => entity.name === name)
@@ -58,7 +58,7 @@ prototype\
const MAX_PATH = 1024 // Maximum path length, in characters. This is fairly arbitrary.
export function clean(filename) {
export function clean(filename: string): string {
filename = filename.replace(BADCHAR_RX, '_')
// for BADFILE_RX replace any matches with an equal number of underscores
filename = filename.replace(BADFILE_RX, match =>
@@ -69,7 +69,7 @@ export function clean(filename) {
return filename
}
export function isCleanFilename(filename) {
export function isCleanFilename(filename: string): boolean {
return (
isAllowedLength(filename) &&
!filename.match(BADCHAR_RX) &&
@@ -77,13 +77,13 @@ export function isCleanFilename(filename) {
)
}
export function isBlockedFilename(filename) {
export function isBlockedFilename(filename: string): boolean {
return BLOCKEDFILE_RX.test(filename)
}
// returns whether a full path is 'clean' - e.g. is a full or relative path
// that points to a file, and each element passes the rules in 'isCleanFilename'
export function isCleanPath(path) {
export function isCleanPath(path: string): boolean {
const elements = path.split('/')
const lastElementIsEmpty = elements[elements.length - 1].length === 0
@@ -105,6 +105,6 @@ export function isCleanPath(path) {
return true
}
export function isAllowedLength(pathname) {
export function isAllowedLength(pathname: string): boolean {
return pathname.length > 0 && pathname.length <= MAX_PATH
}
@@ -1,6 +1,11 @@
import { postJSON, deleteJSON } from '../../../infrastructure/fetch-json'
export function syncRename(projectId, entityType, entityId, newName) {
export function syncRename(
projectId: string,
entityType: string,
entityId: string,
newName: string
) {
return postJSON(
`/project/${projectId}/${getEntityPathName(entityType)}/${entityId}/rename`,
{
@@ -11,13 +16,22 @@ export function syncRename(projectId, entityType, entityId, newName) {
)
}
export function syncDelete(projectId, entityType, entityId) {
export function syncDelete(
projectId: string,
entityType: string,
entityId: string
) {
return deleteJSON(
`/project/${projectId}/${getEntityPathName(entityType)}/${entityId}`
)
}
export function syncMove(projectId, entityType, entityId, toFolderId) {
export function syncMove(
projectId: string,
entityType: string,
entityId: string,
toFolderId: string
) {
return postJSON(
`/project/${projectId}/${getEntityPathName(entityType)}/${entityId}/move`,
{
@@ -28,7 +42,14 @@ export function syncMove(projectId, entityType, entityId, toFolderId) {
)
}
export function syncCreateEntity(projectId, parentFolderId, newEntityData) {
export function syncCreateEntity(
projectId: string,
parentFolderId: string,
newEntityData: {
endpoint: 'doc' | 'folder' | 'linked-file'
[key: string]: unknown
}
) {
const { endpoint, ...newEntity } = newEntityData
return postJSON(`/project/${projectId}/${endpoint}`, {
body: {
@@ -38,6 +59,6 @@ export function syncCreateEntity(projectId, parentFolderId, newEntityData) {
})
}
function getEntityPathName(entityType) {
function getEntityPathName(entityType: string) {
return entityType === 'fileRef' ? 'file' : entityType
}
@@ -9,7 +9,7 @@ interface BaseFileTreeFindResult<T> {
entity: T
parent: T[]
parentFolderId: string
path: string
path: string[]
index: number
}