diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-doc.js b/services/web/frontend/js/features/file-tree/components/file-tree-doc.js
index 1c7bd54d53..252c5d1e21 100644
--- a/services/web/frontend/js/features/file-tree/components/file-tree-doc.js
+++ b/services/web/frontend/js/features/file-tree/components/file-tree-doc.js
@@ -8,9 +8,16 @@ import Icon from '../../../shared/components/icon'
import iconTypeFromName from '../util/icon-type-from-name'
import classnames from 'classnames'
-function FileTreeDoc({ name, id, isFile, isLinkedFile }) {
+function FileTreeDoc({
+ name,
+ id,
+ isFile,
+ isLinkedFile,
+ shouldShowVisualSelection,
+}) {
const { isSelected, props: selectableEntityProps } = useSelectableEntity(
id,
+ shouldShowVisualSelection,
isFile
)
@@ -38,6 +45,7 @@ FileTreeDoc.propTypes = {
id: PropTypes.string.isRequired,
isFile: PropTypes.bool,
isLinkedFile: PropTypes.bool,
+ shouldShowVisualSelection: PropTypes.bool,
}
export const FileTreeIcon = ({ isLinkedFile, name }) => {
diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-folder-list.js b/services/web/frontend/js/features/file-tree/components/file-tree-folder-list.js
index 0795fa3650..7f8c81ec07 100644
--- a/services/web/frontend/js/features/file-tree/components/file-tree-folder-list.js
+++ b/services/web/frontend/js/features/file-tree/components/file-tree-folder-list.js
@@ -12,6 +12,7 @@ function FileTreeFolderList({
classes = {},
dropRef = null,
children,
+ shouldShowVisualSelection,
}) {
files = files.map(file => ({ ...file, isFile: true }))
const docsAndFiles = [...docs, ...files]
@@ -32,6 +33,7 @@ function FileTreeFolderList({
folders={folder.folders}
docs={folder.docs}
files={folder.fileRefs}
+ shouldShowVisualSelection={shouldShowVisualSelection}
/>
)
})}
@@ -43,6 +45,7 @@ function FileTreeFolderList({
id={doc._id}
isFile={doc.isFile}
isLinkedFile={doc.linkedFileData && !!doc.linkedFileData.provider}
+ shouldShowVisualSelection={shouldShowVisualSelection}
/>
)
})}
@@ -60,6 +63,7 @@ FileTreeFolderList.propTypes = {
}),
dropRef: PropTypes.func,
children: PropTypes.node,
+ shouldShowVisualSelection: PropTypes.bool,
}
function compareFunction(one, two) {
diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-folder.js b/services/web/frontend/js/features/file-tree/components/file-tree-folder.js
index 0f319db6f8..3514b4bc96 100644
--- a/services/web/frontend/js/features/file-tree/components/file-tree-folder.js
+++ b/services/web/frontend/js/features/file-tree/components/file-tree-folder.js
@@ -14,10 +14,20 @@ import FileTreeItemInner from './file-tree-item/file-tree-item-inner'
import FileTreeFolderList from './file-tree-folder-list'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
-function FileTreeFolder({ name, id, folders, docs, files }) {
+function FileTreeFolder({
+ name,
+ id,
+ folders,
+ docs,
+ files,
+ shouldShowVisualSelection,
+}) {
const { t } = useTranslation()
- const { isSelected, props: selectableEntityProps } = useSelectableEntity(id)
+ const { isSelected, props: selectableEntityProps } = useSelectableEntity(
+ id,
+ shouldShowVisualSelection
+ )
const { selectedEntityParentIds } = useFileTreeSelectable(id)
@@ -87,6 +97,7 @@ function FileTreeFolder({ name, id, folders, docs, files }) {
docs={docs}
files={files}
dropRef={dropRefList}
+ shouldShowVisualSelection={shouldShowVisualSelection}
/>
) : null}
>
@@ -99,6 +110,7 @@ FileTreeFolder.propTypes = {
folders: PropTypes.array.isRequired,
docs: PropTypes.array.isRequired,
files: PropTypes.array.isRequired,
+ shouldShowVisualSelection: PropTypes.bool,
}
export default FileTreeFolder
diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-root.js b/services/web/frontend/js/features/file-tree/components/file-tree-root.js
index 2bf901d6cb..cf631801a9 100644
--- a/services/web/frontend/js/features/file-tree/components/file-tree-root.js
+++ b/services/web/frontend/js/features/file-tree/components/file-tree-root.js
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react'
+import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import withErrorBoundary from '../../../infrastructure/error-boundary'
@@ -31,6 +31,17 @@ const FileTreeRoot = React.memo(function FileTreeRoot({
const { _id: projectId } = useProjectContext(projectContextPropTypes)
const { fileTreeData } = useFileTreeData()
const isReady = projectId && fileTreeData
+ const [shouldShowVisualSelection, setShouldShowVisualSelection] =
+ useState(true)
+
+ const handleFileTreeClick = e => {
+ if (e.target.classList.contains('bottom-buffer')) {
+ setShouldShowVisualSelection(false)
+ return
+ }
+
+ setShouldShowVisualSelection(e.target !== e.currentTarget)
+ }
useEffect(() => {
if (isReady) onInit()
@@ -48,8 +59,15 @@ const FileTreeRoot = React.memo(function FileTreeRoot({
{isConnected ? null :
}
-
-
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
+
+
@@ -59,7 +77,7 @@ const FileTreeRoot = React.memo(function FileTreeRoot({
)
})
-function FileTreeRootFolder() {
+function FileTreeRootFolder({ shouldShowVisualSelection }) {
useFileTreeSocketListener()
const { fileTreeData } = useFileTreeData()
@@ -75,6 +93,7 @@ function FileTreeRootFolder() {
classes={{ root: 'file-tree-list' }}
dropRef={dropRef}
isOver={isOver}
+ shouldShowVisualSelection={shouldShowVisualSelection}
>
@@ -82,6 +101,10 @@ function FileTreeRootFolder() {
)
}
+FileTreeRootFolder.propTypes = {
+ shouldShowVisualSelection: PropTypes.bool,
+}
+
FileTreeRoot.propTypes = {
onSelect: PropTypes.func.isRequired,
onInit: PropTypes.func.isRequired,
diff --git a/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js b/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js
index f15832b4c3..be69ef9f21 100644
--- a/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js
+++ b/services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.js
@@ -150,6 +150,7 @@ export function FileTreeSelectableProvider({ onSelect, children }) {
dispatch({ type: ACTION_TYPES.SELECT, id: found.entity._id })
}
+
window.addEventListener('editor.openDoc', handleOpenDoc)
return () => window.removeEventListener('editor.openDoc', handleOpenDoc)
}, [fileTreeData])
@@ -202,7 +203,11 @@ const editorContextPropTypes = {
permissionsLevel: PropTypes.oneOf(['readOnly', 'readAndWrite', 'owner']),
}
-export function useSelectableEntity(id, isFile) {
+export function useSelectableEntity(
+ id,
+ shouldShowVisualSelection = true,
+ isFile
+) {
const { view, setView } = useLayoutContext(layoutContextPropTypes)
const { selectedEntityIds, selectOrMultiSelectEntity } = useContext(
FileTreeSelectableContext
@@ -244,7 +249,8 @@ export function useSelectableEntity(id, isFile) {
[id, handleEvent, selectedEntityIds]
)
- const isVisuallySelected = isSelected && view !== 'pdf'
+ const isVisuallySelected =
+ shouldShowVisualSelection && isSelected && view !== 'pdf'
const props = useMemo(
() => ({
className: classNames({ selected: isVisuallySelected }),
diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx
index fb66c42445..b5950dd012 100644
--- a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx
+++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder-list.tsx
@@ -11,6 +11,7 @@ import { fileFinalPathname } from '../../utils/file-diff'
type HistoryFileTreeFolderListProps = {
folders: HistoryFileTree[]
docs: HistoryDoc[]
+ shouldShowVisualSelection: boolean
rootClassName?: string
children?: ReactNode
}
@@ -19,6 +20,7 @@ function HistoryFileTreeFolderList({
folders,
docs,
rootClassName,
+ shouldShowVisualSelection,
children,
}: HistoryFileTreeFolderListProps) {
const { selection, setSelection } = useHistoryContext()
@@ -64,6 +66,7 @@ function HistoryFileTreeFolderList({
name={folder.name}
folders={folder.folders}
docs={folder.docs ?? []}
+ shouldShowVisualSelection={shouldShowVisualSelection}
/>
))}
{docs.map(doc => (
@@ -72,6 +75,7 @@ function HistoryFileTreeFolderList({
name={doc.name}
file={doc}
selected={
+ shouldShowVisualSelection &&
!!selection.selectedFile &&
fileFinalPathname(selection.selectedFile) === doc.pathname
}
diff --git a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx
index 1ef4866996..260fb54c88 100644
--- a/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx
+++ b/services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx
@@ -11,12 +11,14 @@ type HistoryFileTreeFolderProps = {
name: string
folders: HistoryFileTree[]
docs: HistoryDoc[]
+ shouldShowVisualSelection: boolean
}
function HistoryFileTreeFolder({
name,
folders,
docs,
+ shouldShowVisualSelection,
}: HistoryFileTreeFolderProps) {
const { t } = useTranslation()
@@ -58,7 +60,11 @@ function HistoryFileTreeFolder({
{expanded ? (
-
+
) : null}
>
)
diff --git a/services/web/frontend/js/features/history/components/history-file-tree.tsx b/services/web/frontend/js/features/history/components/history-file-tree.tsx
index 39cf775936..e7d61e21c3 100644
--- a/services/web/frontend/js/features/history/components/history-file-tree.tsx
+++ b/services/web/frontend/js/features/history/components/history-file-tree.tsx
@@ -1,4 +1,4 @@
-import { useMemo } from 'react'
+import { useMemo, useEffect, useState } from 'react'
import { orderBy, reduce } from 'lodash'
import { useHistoryContext } from '../context/history-context'
import {
@@ -6,9 +6,12 @@ import {
reducePathsToTree,
} from '../utils/file-tree'
import HistoryFileTreeFolderList from './file-tree/history-file-tree-folder-list'
+import { fileTreeContainer } from './history-root'
export default function HistoryFileTree() {
const { selection } = useHistoryContext()
+ const [shouldShowVisualSelection, setShouldShowVisualSelection] =
+ useState(true)
const fileTree = useMemo(
() => reduce(selection.files, reducePathsToTree, []),
@@ -25,11 +28,29 @@ export default function HistoryFileTree() {
[sortedFileTree]
)
+ useEffect(() => {
+ const listener = function (e: MouseEvent) {
+ if ((e.target as HTMLElement).classList.contains('bottom-buffer')) {
+ setShouldShowVisualSelection(false)
+ return
+ }
+
+ setShouldShowVisualSelection(e.target !== e.currentTarget)
+ }
+
+ fileTreeContainer?.addEventListener('click', listener)
+
+ return () => {
+ fileTreeContainer?.removeEventListener('click', listener)
+ }
+ }, [])
+
return (
diff --git a/services/web/frontend/js/features/history/components/history-root.tsx b/services/web/frontend/js/features/history/components/history-root.tsx
index 71bfe2e4f8..69f3f0201e 100644
--- a/services/web/frontend/js/features/history/components/history-root.tsx
+++ b/services/web/frontend/js/features/history/components/history-root.tsx
@@ -8,7 +8,7 @@ import LoadingSpinner from '../../../shared/components/loading-spinner'
import { ErrorBoundaryFallback } from '../../../shared/components/error-boundary-fallback'
import withErrorBoundary from '../../../infrastructure/error-boundary'
-const fileTreeContainer = document.getElementById('history-file-tree')
+export const fileTreeContainer = document.getElementById('history-file-tree')
function Main() {
const { view } = useLayoutContext()
diff --git a/services/web/frontend/stylesheets/app/editor/file-tree.less b/services/web/frontend/stylesheets/app/editor/file-tree.less
index b40cac76c5..f3fc534ff9 100644
--- a/services/web/frontend/stylesheets/app/editor/file-tree.less
+++ b/services/web/frontend/stylesheets/app/editor/file-tree.less
@@ -90,8 +90,6 @@
ul.file-tree-list {
margin: 0;
overflow-x: hidden;
- height: 100%;
- flex-grow: 1;
position: relative;
overflow-y: auto;
diff --git a/services/web/frontend/stylesheets/app/editor/history-react.less b/services/web/frontend/stylesheets/app/editor/history-react.less
index b87d3ea9f0..eb5ab17abc 100644
--- a/services/web/frontend/stylesheets/app/editor/history-react.less
+++ b/services/web/frontend/stylesheets/app/editor/history-react.less
@@ -384,8 +384,6 @@ history-root {
ul.history-file-tree-list {
margin: 0;
overflow-x: hidden;
- height: 100%;
- flex-grow: 1;
position: relative;
overflow-y: auto;
diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js
index e9e94f01b0..4ad0613a36 100644
--- a/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js
+++ b/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js
@@ -305,4 +305,38 @@ describe('
', function () {
// multiple items selected: no menu button is visible
expect(screen.queryAllByRole('button', { name: 'Menu' })).to.have.length(0)
})
+
+ it('deselects files when clicked outside the list but inside wrapping container', function () {
+ const rootFolder = [
+ {
+ _id: 'root-folder-id',
+ name: 'rootFolder',
+ docs: [{ _id: '456def', name: 'main.tex' }],
+ folders: [],
+ fileRefs: [],
+ },
+ ]
+ renderWithEditorContext(
+
null}
+ setRefProviderEnabled={() => null}
+ setStartedFreeTrial={() => null}
+ onSelect={onSelect}
+ onInit={onInit}
+ isConnected
+ />,
+ {
+ rootFolder,
+ projectId: '123abc',
+ rootDocId: '456def',
+ features: {},
+ permissionsLevel: 'owner',
+ }
+ )
+
+ screen.getByRole('treeitem', { selected: true })
+ fireEvent.click(screen.getByTestId('file-tree-inner'))
+ expect(screen.queryByRole('treeitem', { selected: true })).to.be.null
+ })
})