Merge pull request #20057 from overleaf/jpa-readonly-tuning

[web] tuning for readonly mirror on client

GitOrigin-RevId: c0ecc8923cdec6c515da17750133632ebc430e8d
This commit is contained in:
Jakob Ackermann 2024-08-22 17:00:07 +02:00 committed by Copybot
parent 4997b7f1ee
commit 1182049d89
14 changed files with 83 additions and 39 deletions

View file

@ -42,6 +42,7 @@ const TrackedChangeList = require('./lib/file_data/tracked_change_list')
const TrackingProps = require('./lib/file_data/tracking_props') const TrackingProps = require('./lib/file_data/tracking_props')
const Range = require('./lib/range') const Range = require('./lib/range')
const CommentList = require('./lib/file_data/comment_list') const CommentList = require('./lib/file_data/comment_list')
const LazyStringFileData = require('./lib/file_data/lazy_string_file_data')
exports.AddCommentOperation = AddCommentOperation exports.AddCommentOperation = AddCommentOperation
exports.Author = Author exports.Author = Author
@ -56,6 +57,7 @@ exports.Comment = Comment
exports.DeleteCommentOperation = DeleteCommentOperation exports.DeleteCommentOperation = DeleteCommentOperation
exports.File = File exports.File = File
exports.FileMap = FileMap exports.FileMap = FileMap
exports.LazyStringFileData = LazyStringFileData
exports.History = History exports.History = History
exports.Label = Label exports.Label = Label
exports.AddFileOperation = AddFileOperation exports.AddFileOperation = AddFileOperation

View file

@ -1,13 +1,13 @@
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Dropdown } from 'react-bootstrap' import { Dropdown } from 'react-bootstrap'
import { useEditorContext } from '../../../shared/context/editor-context' import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useFileTreeMainContext } from '../contexts/file-tree-main' import { useFileTreeMainContext } from '../contexts/file-tree-main'
import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items' import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
function FileTreeContextMenu() { function FileTreeContextMenu() {
const { permissionsLevel } = useEditorContext() const { fileTreeReadOnly } = useFileTreeData()
const { contextMenuCoords, setContextMenuCoords } = useFileTreeMainContext() const { contextMenuCoords, setContextMenuCoords } = useFileTreeMainContext()
const toggleButtonRef = useRef<HTMLButtonElement | null>(null) const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
@ -20,7 +20,7 @@ function FileTreeContextMenu() {
} }
}, [contextMenuCoords]) }, [contextMenuCoords])
if (!contextMenuCoords || permissionsLevel === 'readOnly') return null if (!contextMenuCoords || fileTreeReadOnly) return null
// A11y - Move the focus to the context menu when it opens // A11y - Move the focus to the context menu when it opens
function focusContextMenu() { function focusContextMenu() {

View file

@ -2,7 +2,7 @@ import { ReactNode, useEffect, useRef } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed' import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
import { useEditorContext } from '../../../../shared/context/editor-context' import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useFileTreeMainContext } from '../../contexts/file-tree-main' import { useFileTreeMainContext } from '../../contexts/file-tree-main'
import { useDraggable } from '../../contexts/file-tree-draggable' import { useDraggable } from '../../contexts/file-tree-draggable'
@ -25,16 +25,14 @@ function FileTreeItemInner({
isSelected: boolean isSelected: boolean
icons?: ReactNode icons?: ReactNode
}) { }) {
const { permissionsLevel } = useEditorContext() const { fileTreeReadOnly } = useFileTreeData()
const { setContextMenuCoords } = useFileTreeMainContext() const { setContextMenuCoords } = useFileTreeMainContext()
const { isRenaming } = useFileTreeActionable() const { isRenaming } = useFileTreeActionable()
const { selectedEntityIds } = useFileTreeSelectable() const { selectedEntityIds } = useFileTreeSelectable()
const hasMenu = const hasMenu =
permissionsLevel !== 'readOnly' && !fileTreeReadOnly && isSelected && selectedEntityIds.size === 1
isSelected &&
selectedEntityIds.size === 1
const { dragRef, setIsDraggable } = useDraggable(id) const { dragRef, setIsDraggable } = useDraggable(id)

View file

@ -47,7 +47,11 @@ function FileTreeItemMenuItems() {
<MenuItem onClick={startRenaming}>{t('rename')}</MenuItem> <MenuItem onClick={startRenaming}>{t('rename')}</MenuItem>
) : null} ) : null}
{downloadPath ? ( {downloadPath ? (
<MenuItem href={downloadPath} onClick={downloadWithAnalytics} download> <MenuItem
href={downloadPath}
onClick={downloadWithAnalytics}
download={selectedFileName}
>
{t('download')} {t('download')}
</MenuItem> </MenuItem>
) : null} ) : null}

View file

@ -5,13 +5,13 @@ import { Button } from 'react-bootstrap'
import Tooltip from '../../../shared/components/tooltip' import Tooltip from '../../../shared/components/tooltip'
import Icon from '../../../shared/components/icon' import Icon from '../../../shared/components/icon'
import { useEditorContext } from '../../../shared/context/editor-context'
import { useFileTreeActionable } from '../contexts/file-tree-actionable' import { useFileTreeActionable } from '../contexts/file-tree-actionable'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
function FileTreeToolbar() { function FileTreeToolbar() {
const { permissionsLevel } = useEditorContext() const { fileTreeReadOnly } = useFileTreeData()
if (permissionsLevel === 'readOnly') return null if (fileTreeReadOnly) return null
return ( return (
<div className="toolbar toolbar-filetree"> <div className="toolbar toolbar-filetree">

View file

@ -22,7 +22,6 @@ import { isNameUniqueInFolder } from '../util/is-name-unique-in-folder'
import { isBlockedFilename, isCleanFilename } from '../util/safe-path' import { isBlockedFilename, isCleanFilename } from '../util/safe-path'
import { useProjectContext } from '../../../shared/context/project-context' import { useProjectContext } from '../../../shared/context/project-context'
import { useEditorContext } from '../../../shared/context/editor-context'
import { useFileTreeData } from '../../../shared/context/file-tree-data-context' import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
import { useFileTreeSelectable } from './file-tree-selectable' import { useFileTreeSelectable } from './file-tree-selectable'
@ -34,6 +33,7 @@ import {
} from '../errors' } from '../errors'
import { Folder } from '../../../../../types/folder' import { Folder } from '../../../../../types/folder'
import { useReferencesContext } from '@/features/ide-react/context/references-context' import { useReferencesContext } from '@/features/ide-react/context/references-context'
import { useSnapshotContext } from '@/features/ide-react/context/snapshot-context'
type DroppedFile = File & { type DroppedFile = File & {
relativePath?: string relativePath?: string
@ -219,11 +219,12 @@ function fileTreeActionableReducer(state: State, action: Action) {
export const FileTreeActionableProvider: FC = ({ children }) => { export const FileTreeActionableProvider: FC = ({ children }) => {
const { _id: projectId } = useProjectContext() const { _id: projectId } = useProjectContext()
const { permissionsLevel } = useEditorContext() const { fileTreeReadOnly } = useFileTreeData()
const { indexAllReferences } = useReferencesContext() const { indexAllReferences } = useReferencesContext()
const { fileTreeFromHistory } = useSnapshotContext()
const [state, dispatch] = useReducer( const [state, dispatch] = useReducer(
permissionsLevel === 'readOnly' fileTreeReadOnly
? fileTreeActionableReadOnlyReducer ? fileTreeActionableReadOnlyReducer
: fileTreeActionableReducer, : fileTreeActionableReducer,
defaultState defaultState
@ -493,6 +494,9 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
const selectedEntity = findInTree(fileTreeData, selectedEntityId) const selectedEntity = findInTree(fileTreeData, selectedEntityId)
if (selectedEntity?.type === 'fileRef') { if (selectedEntity?.type === 'fileRef') {
if (fileTreeFromHistory) {
return `/project/${projectId}/blob/${selectedEntity.entity.hash}`
}
return `/project/${projectId}/file/${selectedEntityId}` return `/project/${projectId}/file/${selectedEntityId}`
} }
@ -500,7 +504,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
return `/project/${projectId}/doc/${selectedEntityId}/download` return `/project/${projectId}/doc/${selectedEntityId}/download`
} }
} }
}, [fileTreeData, projectId, selectedEntityIds]) }, [fileTreeData, projectId, selectedEntityIds, fileTreeFromHistory])
// TODO: wrap in useMemo // TODO: wrap in useMemo
const value = { const value = {

View file

@ -14,7 +14,6 @@ import {
import { useFileTreeActionable } from './file-tree-actionable' import { useFileTreeActionable } from './file-tree-actionable'
import { useFileTreeData } from '@/shared/context/file-tree-data-context' import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useFileTreeSelectable } from '../contexts/file-tree-selectable' import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
import { useEditorContext } from '@/shared/context/editor-context'
import { isAcceptableFile } from '@/features/file-tree/util/is-acceptable-file' import { isAcceptableFile } from '@/features/file-tree/util/is-acceptable-file'
const DRAGGABLE_TYPE = 'ENTITY' const DRAGGABLE_TYPE = 'ENTITY'
@ -48,8 +47,7 @@ type DropResult = {
export function useDraggable(draggedEntityId: string) { export function useDraggable(draggedEntityId: string) {
const { t } = useTranslation() const { t } = useTranslation()
const { permissionsLevel } = useEditorContext() const { fileTreeData, fileTreeReadOnly } = useFileTreeData()
const { fileTreeData } = useFileTreeData()
const { selectedEntityIds, isRootFolderSelected } = useFileTreeSelectable() const { selectedEntityIds, isRootFolderSelected } = useFileTreeSelectable()
const { finishMoving } = useFileTreeActionable() const { finishMoving } = useFileTreeActionable()
@ -73,7 +71,7 @@ export function useDraggable(draggedEntityId: string) {
} }
}, },
canDrag() { canDrag() {
return permissionsLevel !== 'readOnly' && isDraggable return !fileTreeReadOnly && isDraggable
}, },
end(item: DragObject, monitor: DragSourceMonitor<DragObject, DropResult>) { end(item: DragObject, monitor: DragSourceMonitor<DragObject, DropResult>) {
if (monitor.didDrop()) { if (monitor.didDrop()) {

View file

@ -10,6 +10,7 @@ import {
pathInFolder, pathInFolder,
} from '@/features/file-tree/util/path' } from '@/features/file-tree/util/path'
import { PreviewPath } from '../../../../../types/preview-path' import { PreviewPath } from '../../../../../types/preview-path'
import { useSnapshotContext } from '@/features/ide-react/context/snapshot-context'
type FileTreePathContextValue = { type FileTreePathContextValue = {
pathInFolder: (id: string) => string | null pathInFolder: (id: string) => string | null
@ -24,6 +25,7 @@ export const FileTreePathContext = createContext<
export const FileTreePathProvider: FC = ({ children }) => { export const FileTreePathProvider: FC = ({ children }) => {
const { fileTreeData }: { fileTreeData: Folder } = useFileTreeData() const { fileTreeData }: { fileTreeData: Folder } = useFileTreeData()
const { fileTreeFromHistory } = useSnapshotContext()
const projectId = getMeta('ol-project_id') const projectId = getMeta('ol-project_id')
const pathInFileTree = useCallback( const pathInFileTree = useCallback(
@ -37,8 +39,9 @@ export const FileTreePathProvider: FC = ({ children }) => {
) )
const previewByPathInFileTree = useCallback( const previewByPathInFileTree = useCallback(
(path: string) => previewByPath(fileTreeData, projectId, path), (path: string) =>
[fileTreeData, projectId] previewByPath(fileTreeData, projectId, path, fileTreeFromHistory),
[fileTreeData, projectId, fileTreeFromHistory]
) )
const dirnameInFileTree = useCallback( const dirnameInFileTree = useCallback(

View file

@ -13,7 +13,6 @@ import _ from 'lodash'
import { findInTree } from '../util/find-in-tree' import { findInTree } from '../util/find-in-tree'
import { useFileTreeData } from '../../../shared/context/file-tree-data-context' import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
import { useProjectContext } from '../../../shared/context/project-context' import { useProjectContext } from '../../../shared/context/project-context'
import { useEditorContext } from '../../../shared/context/editor-context'
import { useLayoutContext } from '../../../shared/context/layout-context' import { useLayoutContext } from '../../../shared/context/layout-context'
import usePersistedState from '../../../shared/hooks/use-persisted-state' import usePersistedState from '../../../shared/hooks/use-persisted-state'
import usePreviousValue from '../../../shared/hooks/use-previous-value' import usePreviousValue from '../../../shared/hooks/use-previous-value'
@ -123,19 +122,19 @@ export const FileTreeSelectableProvider: FC<{
onSelect: (value: FindResult[]) => void onSelect: (value: FindResult[]) => void
}> = ({ onSelect, children }) => { }> = ({ onSelect, children }) => {
const { _id: projectId, rootDocId } = useProjectContext() const { _id: projectId, rootDocId } = useProjectContext()
const { permissionsLevel } = useEditorContext()
const [initialSelectedEntityId] = usePersistedState( const [initialSelectedEntityId] = usePersistedState(
`doc.open_id.${projectId}`, `doc.open_id.${projectId}`,
rootDocId rootDocId
) )
const { fileTreeData, setSelectedEntities } = useFileTreeData() const { fileTreeData, setSelectedEntities, fileTreeReadOnly } =
useFileTreeData()
const [isRootFolderSelected, setIsRootFolderSelected] = useState(false) const [isRootFolderSelected, setIsRootFolderSelected] = useState(false)
const [selectedEntityIds, dispatch] = useReducer( const [selectedEntityIds, dispatch] = useReducer(
permissionsLevel === 'readOnly' fileTreeReadOnly
? fileTreeSelectableReadOnlyReducer ? fileTreeSelectableReadOnlyReducer
: fileTreeSelectableReadWriteReducer, : fileTreeSelectableReadWriteReducer,
null, null,

View file

@ -5,6 +5,7 @@ import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
import { useFileTreeSelectable } from '../contexts/file-tree-selectable' import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
import { findInTree, findInTreeOrThrow } from '../util/find-in-tree' import { findInTree, findInTreeOrThrow } from '../util/find-in-tree'
import { useIdeContext } from '@/shared/context/ide-context' import { useIdeContext } from '@/shared/context/ide-context'
import { useSnapshotContext } from '@/features/ide-react/context/snapshot-context'
export function useFileTreeSocketListener(onDelete: (entity: any) => void) { export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
const user = useUserContext() const user = useUserContext()
@ -20,6 +21,7 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
const { selectedEntityIds, selectedEntityParentIds, select, unselect } = const { selectedEntityIds, selectedEntityParentIds, select, unselect } =
useFileTreeSelectable() useFileTreeSelectable()
const { socket } = useIdeContext() const { socket } = useIdeContext()
const { fileTreeFromHistory } = useSnapshotContext()
const selectEntityIfCreatedByUser = useCallback( const selectEntityIfCreatedByUser = useCallback(
// hack to automatically re-open refreshed linked files // hack to automatically re-open refreshed linked files
@ -38,6 +40,7 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
) )
useEffect(() => { useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchRename(entityId: string, name: string) { function handleDispatchRename(entityId: string, name: string) {
dispatchRename(entityId, name) dispatchRename(entityId, name)
} }
@ -46,9 +49,10 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
if (socket) if (socket)
socket.removeListener('reciveEntityRename', handleDispatchRename) socket.removeListener('reciveEntityRename', handleDispatchRename)
} }
}, [socket, dispatchRename]) }, [socket, dispatchRename, fileTreeFromHistory])
useEffect(() => { useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchDelete(entityId: string) { function handleDispatchDelete(entityId: string) {
const entity = findInTree(fileTreeData, entityId) const entity = findInTree(fileTreeData, entityId)
unselect(entityId) unselect(entityId)
@ -82,9 +86,11 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
selectedEntityIds, selectedEntityIds,
selectedEntityParentIds, selectedEntityParentIds,
onDelete, onDelete,
fileTreeFromHistory,
]) ])
useEffect(() => { useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchMove(entityId: string, toFolderId: string) { function handleDispatchMove(entityId: string, toFolderId: string) {
dispatchMove(entityId, toFolderId) dispatchMove(entityId, toFolderId)
} }
@ -92,9 +98,10 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
return () => { return () => {
if (socket) socket.removeListener('reciveEntityMove', handleDispatchMove) if (socket) socket.removeListener('reciveEntityMove', handleDispatchMove)
} }
}, [socket, dispatchMove]) }, [socket, dispatchMove, fileTreeFromHistory])
useEffect(() => { useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchCreateFolder(parentFolderId: string, folder: any) { function handleDispatchCreateFolder(parentFolderId: string, folder: any) {
dispatchCreateFolder(parentFolderId, folder) dispatchCreateFolder(parentFolderId, folder)
} }
@ -103,9 +110,10 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
if (socket) if (socket)
socket.removeListener('reciveNewFolder', handleDispatchCreateFolder) socket.removeListener('reciveNewFolder', handleDispatchCreateFolder)
} }
}, [socket, dispatchCreateFolder]) }, [socket, dispatchCreateFolder, fileTreeFromHistory])
useEffect(() => { useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchCreateDoc( function handleDispatchCreateDoc(
parentFolderId: string, parentFolderId: string,
doc: any, doc: any,
@ -117,9 +125,10 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
return () => { return () => {
if (socket) socket.removeListener('reciveNewDoc', handleDispatchCreateDoc) if (socket) socket.removeListener('reciveNewDoc', handleDispatchCreateDoc)
} }
}, [socket, dispatchCreateDoc]) }, [socket, dispatchCreateDoc, fileTreeFromHistory])
useEffect(() => { useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchCreateFile( function handleDispatchCreateFile(
parentFolderId: string, parentFolderId: string,
file: any, file: any,
@ -137,5 +146,10 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
if (socket) if (socket)
socket.removeListener('reciveNewFile', handleDispatchCreateFile) socket.removeListener('reciveNewFile', handleDispatchCreateFile)
} }
}, [socket, dispatchCreateFile, selectEntityIfCreatedByUser]) }, [
socket,
dispatchCreateFile,
selectEntityIfCreatedByUser,
fileTreeFromHistory,
])
} }

View file

@ -106,7 +106,8 @@ export function findEntityByPath(
export function previewByPath( export function previewByPath(
folder: Folder, folder: Folder,
projectId: string, projectId: string,
path: string path: string,
fileTreeFromHistory: boolean
): PreviewPath | null { ): PreviewPath | null {
for (const suffix of [ for (const suffix of [
'', '',
@ -121,10 +122,12 @@ export function previewByPath(
]) { ]) {
const result = findEntityByPath(folder, path + suffix) const result = findEntityByPath(folder, path + suffix)
if (result) { if (result?.type === 'fileRef') {
const { name, _id: id } = result.entity const { name, _id: id, hash } = result.entity
return { return {
url: `/project/${projectId}/file/${id}`, url: fileTreeFromHistory
? `/project/${projectId}/blob/${hash}`
: `/project/${projectId}/file/${id}`,
extension: name.slice(name.lastIndexOf('.') + 1), extension: name.slice(name.lastIndexOf('.') + 1),
} }
} }

View file

@ -3,7 +3,7 @@ import { Trans, useTranslation } from 'react-i18next'
import Icon from '../../../shared/components/icon' import Icon from '../../../shared/components/icon'
import { formatTime, relativeDate } from '../../utils/format-date' import { formatTime, relativeDate } from '../../utils/format-date'
import { useEditorContext } from '../../../shared/context/editor-context' import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useProjectContext } from '../../../shared/context/project-context' import { useProjectContext } from '../../../shared/context/project-context'
import { Nullable } from '../../../../../types/utils' import { Nullable } from '../../../../../types/utils'
@ -49,7 +49,7 @@ type FileViewHeaderProps = {
export default function FileViewHeader({ file }: FileViewHeaderProps) { export default function FileViewHeader({ file }: FileViewHeaderProps) {
const { _id: projectId } = useProjectContext() const { _id: projectId } = useProjectContext()
const { permissionsLevel } = useEditorContext() const { fileTreeReadOnly } = useFileTreeData()
const { fileTreeFromHistory } = useSnapshotContext() const { fileTreeFromHistory } = useSnapshotContext()
const { t } = useTranslation() const { t } = useTranslation()
@ -85,7 +85,7 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) {
tprFileViewInfo.map(({ import: { TPRFileViewInfo }, path }) => ( tprFileViewInfo.map(({ import: { TPRFileViewInfo }, path }) => (
<TPRFileViewInfo key={path} file={file} /> <TPRFileViewInfo key={path} file={file} />
))} ))}
{file.linkedFileData && permissionsLevel !== 'readOnly' && ( {file.linkedFileData && !fileTreeReadOnly && (
<FileViewRefreshButton file={file} setRefreshError={setRefreshError} /> <FileViewRefreshButton file={file} setRefreshError={setRefreshError} />
)} )}
&nbsp; &nbsp;

View file

@ -38,6 +38,7 @@ const FileTreeDataContext = createContext<
// by the file tree // by the file tree
fileTreeData: Folder fileTreeData: Folder
fileCount: { value: number; status: string; limit: number } | number fileCount: { value: number; status: string; limit: number } | number
fileTreeReadOnly: boolean
hasFolders: boolean hasFolders: boolean
selectedEntities: FindResult[] selectedEntities: FindResult[]
setSelectedEntities: (selectedEntities: FindResult[]) => void setSelectedEntities: (selectedEntities: FindResult[]) => void
@ -179,8 +180,11 @@ export const FileTreeDataProvider: FC = ({ children }) => {
const [project] = useScopeValue<Project>('project') const [project] = useScopeValue<Project>('project')
const [openDocId] = useScopeValue('editor.open_doc_id') const [openDocId] = useScopeValue('editor.open_doc_id')
const [, setOpenDocName] = useScopeValueSetterOnly('editor.open_doc_name') const [, setOpenDocName] = useScopeValueSetterOnly('editor.open_doc_name')
const [permissionsLevel] = useScopeValue('permissionsLevel')
const { fileTreeFromHistory, snapshot, snapshotVersion } = const { fileTreeFromHistory, snapshot, snapshotVersion } =
useSnapshotContext() useSnapshotContext()
const fileTreeReadOnly =
permissionsLevel === 'readOnly' || fileTreeFromHistory
const [rootFolder, setRootFolder] = useState(project?.rootFolder) const [rootFolder, setRootFolder] = useState(project?.rootFolder)
@ -288,6 +292,7 @@ export const FileTreeDataProvider: FC = ({ children }) => {
dispatchRename, dispatchRename,
fileCount, fileCount,
fileTreeData, fileTreeData,
fileTreeReadOnly,
hasFolders: fileTreeData?.folders.length > 0, hasFolders: fileTreeData?.folders.length > 0,
selectedEntities, selectedEntities,
setSelectedEntities, setSelectedEntities,
@ -302,6 +307,7 @@ export const FileTreeDataProvider: FC = ({ children }) => {
dispatchRename, dispatchRename,
fileCount, fileCount,
fileTreeData, fileTreeData,
fileTreeReadOnly,
selectedEntities, selectedEntities,
setSelectedEntities, setSelectedEntities,
docs, docs,

View file

@ -151,12 +151,25 @@ describe('Path utils', function () {
const preview = previewByPath( const preview = previewByPath(
rootFolder, rootFolder,
'test-project-id', 'test-project-id',
'test-folder/example.png' 'test-folder/example.png',
false
) )
expect(preview).to.deep.equal({ expect(preview).to.deep.equal({
url: '/project/test-project-id/file/test-file-in-folder', url: '/project/test-project-id/file/test-file-in-folder',
extension: 'png', extension: 'png',
}) })
}) })
it('returns handles history file-tree', function () {
const preview = previewByPath(
rootFolder,
'test-project-id',
'test-folder/example.png',
true
)
expect(preview).to.deep.equal({
url: '/project/test-project-id/blob/42',
extension: 'png',
})
})
}) })
}) })