ff7de70a61
Build and Deploy Verso / deploy (push) Successful in 21m44s
Two bugs: 1. Converting when output already exists threw DuplicateNameError (400). Now overwrites existing doc via setDocument instead of failing. 2. Right-clicking an unselected file left contextMenuEntityId null, so the first click on Convert silently did nothing. Added contextMenuEntityId to FileTreeMainContext, set it on right-click and on the … button click; FileTreeItemMenuItems now uses it for the convert hooks rather than relying on selectedEntityIds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
159 lines
4.3 KiB
TypeScript
159 lines
4.3 KiB
TypeScript
import { ReactNode, useEffect, useRef } from 'react'
|
|
import classNames from 'classnames'
|
|
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
|
|
|
|
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
|
import { useFileTreeMainContext } from '../../contexts/file-tree-main'
|
|
import { useDraggable } from '../../contexts/file-tree-draggable'
|
|
|
|
import FileTreeItemName from './file-tree-item-name'
|
|
import FileTreeItemMenu from './file-tree-item-menu'
|
|
import SplitTestBadge from '@/shared/components/split-test-badge'
|
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
|
import { useFileTreeSelectable } from '../../contexts/file-tree-selectable'
|
|
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
|
|
import { useDragDropManager } from 'react-dnd'
|
|
|
|
function FileTreeItemInner({
|
|
id,
|
|
name,
|
|
type,
|
|
isSelected,
|
|
icons,
|
|
onClick,
|
|
}: {
|
|
id: string
|
|
name: string
|
|
type: string
|
|
isSelected: boolean
|
|
icons?: ReactNode
|
|
onClick?: () => void
|
|
}) {
|
|
const { fileTreeReadOnly } = useFileTreeData()
|
|
const { setContextMenuCoords, setContextMenuEntityId } =
|
|
useFileTreeMainContext()
|
|
const { isRenaming } = useFileTreeActionable()
|
|
const { selectedEntityIds } = useFileTreeSelectable()
|
|
|
|
const hasMenu =
|
|
!fileTreeReadOnly && isSelected && selectedEntityIds.size === 1
|
|
const showBibBadge =
|
|
useFeatureFlag('bibtex-visual-editor') &&
|
|
type !== 'folder' &&
|
|
name.toLowerCase().endsWith('.bib')
|
|
|
|
const { dragRef, setIsDraggable } = useDraggable(id)
|
|
|
|
const dragDropItem = useDragDropManager().getMonitor().getItem()
|
|
|
|
const itemRef = useRef<HTMLDivElement | null>(null)
|
|
|
|
useEffect(() => {
|
|
const item = itemRef.current
|
|
if (isSelected && item) {
|
|
// we're delaying scrolling due to a race condition with other elements,
|
|
// mainly the Outline, being resized inside the same panel, causing the
|
|
// FileTree to have its viewport shrinked after the selected item is
|
|
// scrolled into the view, hiding it again.
|
|
// See `left-pane-resize-all` in `file-tree-controller` for more information.
|
|
setTimeout(() => {
|
|
if (item) {
|
|
scrollIntoViewIfNeeded(item, {
|
|
scrollMode: 'if-needed',
|
|
})
|
|
}
|
|
}, 100)
|
|
}
|
|
}, [isSelected, itemRef])
|
|
|
|
function handleContextMenu(ev: React.MouseEvent<HTMLDivElement>) {
|
|
if (ev.shiftKey) {
|
|
setContextMenuCoords(null)
|
|
return
|
|
}
|
|
|
|
ev.preventDefault()
|
|
|
|
setContextMenuEntityId(id)
|
|
setContextMenuCoords({
|
|
top: ev.pageY,
|
|
left: ev.pageX,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={classNames('entity', {
|
|
'file-tree-entity-dragging': dragDropItem?.draggedEntityIds?.has(id),
|
|
})}
|
|
role="presentation"
|
|
ref={dragRef}
|
|
draggable={!isRenaming}
|
|
onContextMenu={handleContextMenu}
|
|
data-file-id={id}
|
|
data-file-type={type}
|
|
>
|
|
<div
|
|
className={classNames('entity-name', 'entity-name-react', {
|
|
'file-tree-has-bib-badge': showBibBadge,
|
|
})}
|
|
role="presentation"
|
|
ref={itemRef}
|
|
>
|
|
<FileTreeItemIconsAndName
|
|
name={name}
|
|
isSelected={isSelected}
|
|
icons={icons}
|
|
onClick={onClick}
|
|
setIsDraggable={setIsDraggable}
|
|
/>
|
|
{showBibBadge && (
|
|
<div className="file-tree-bib-badge text-white">
|
|
<SplitTestBadge
|
|
splitTestName="bibtex-visual-editor"
|
|
displayOnVariants={['enabled']}
|
|
/>
|
|
</div>
|
|
)}
|
|
{hasMenu ? <FileTreeItemMenu id={id} name={name} /> : null}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const FileTreeItemIconsAndName = ({
|
|
name,
|
|
isSelected,
|
|
icons,
|
|
onClick,
|
|
setIsDraggable,
|
|
}: {
|
|
name: string
|
|
isSelected: boolean
|
|
icons?: ReactNode
|
|
onClick?: () => void
|
|
setIsDraggable: (isDraggable: boolean) => void
|
|
}) => {
|
|
return onClick ? (
|
|
<button className="file-tree-entity-button" onClick={onClick}>
|
|
{icons}
|
|
<FileTreeItemName
|
|
name={name}
|
|
isSelected={isSelected}
|
|
setIsDraggable={setIsDraggable}
|
|
/>
|
|
</button>
|
|
) : (
|
|
<div className="file-tree-entity-details">
|
|
{icons}
|
|
<FileTreeItemName
|
|
name={name}
|
|
isSelected={isSelected}
|
|
setIsDraggable={setIsDraggable}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default FileTreeItemInner
|