mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 20:13:46 -05:00
Highlight dragged and target items when dragging in the file tree (#15384)
GitOrigin-RevId: 4eaace34434753f6674724adafcf3b0754365d15
This commit is contained in:
parent
e22c1d70f3
commit
949d4facc7
5 changed files with 76 additions and 46 deletions
|
@ -1,6 +1,5 @@
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useDragLayer } from 'react-dnd'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
// a custom component rendered on top of a draggable area that renders the
|
// a custom component rendered on top of a draggable area that renders the
|
||||||
|
@ -8,12 +7,12 @@ import classNames from 'classnames'
|
||||||
// https://react-dnd.github.io/react-dnd/examples/drag-around/custom-drag-layer
|
// https://react-dnd.github.io/react-dnd/examples/drag-around/custom-drag-layer
|
||||||
// for more details.
|
// for more details.
|
||||||
// Also used to display a container border when hovered.
|
// Also used to display a container border when hovered.
|
||||||
function FileTreeDraggablePreviewLayer({ isOver }) {
|
function FileTreeDraggablePreviewLayer({
|
||||||
const { isDragging, item, clientOffset } = useDragLayer(monitor => ({
|
isOver,
|
||||||
isDragging: monitor.isDragging(),
|
isDragging,
|
||||||
item: monitor.getItem(),
|
item,
|
||||||
clientOffset: monitor.getClientOffset(),
|
clientOffset,
|
||||||
}))
|
}) {
|
||||||
const ref = useRef()
|
const ref = useRef()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -39,6 +38,11 @@ function FileTreeDraggablePreviewLayer({ isOver }) {
|
||||||
|
|
||||||
FileTreeDraggablePreviewLayer.propTypes = {
|
FileTreeDraggablePreviewLayer.propTypes = {
|
||||||
isOver: PropTypes.bool.isRequired,
|
isOver: PropTypes.bool.isRequired,
|
||||||
|
isDragging: PropTypes.bool.isRequired,
|
||||||
|
item: PropTypes.shape({
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
}),
|
||||||
|
clientOffset: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
function DraggablePreviewItem({ title }) {
|
function DraggablePreviewItem({ title }) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import FileTreeItemName from './file-tree-item-name'
|
||||||
import FileTreeItemMenu from './file-tree-item-menu'
|
import FileTreeItemMenu from './file-tree-item-menu'
|
||||||
import { useFileTreeSelectable } from '../../contexts/file-tree-selectable'
|
import { useFileTreeSelectable } from '../../contexts/file-tree-selectable'
|
||||||
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
|
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
|
||||||
|
import { useDragDropManager } from 'react-dnd'
|
||||||
|
|
||||||
function FileTreeItemInner({ id, name, isSelected, icons }) {
|
function FileTreeItemInner({ id, name, isSelected, icons }) {
|
||||||
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
|
||||||
|
@ -24,7 +25,9 @@ function FileTreeItemInner({ id, name, isSelected, icons }) {
|
||||||
isSelected &&
|
isSelected &&
|
||||||
selectedEntityIds.size === 1
|
selectedEntityIds.size === 1
|
||||||
|
|
||||||
const { isDragging, dragRef, setIsDraggable } = useDraggable(id)
|
const { dragRef, setIsDraggable } = useDraggable(id)
|
||||||
|
|
||||||
|
const dragDropItem = useDragDropManager().getMonitor().getItem()
|
||||||
|
|
||||||
const itemRef = useRef()
|
const itemRef = useRef()
|
||||||
|
|
||||||
|
@ -58,7 +61,7 @@ function FileTreeItemInner({ id, name, isSelected, icons }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('entity', {
|
className={classNames('entity', {
|
||||||
'dnd-draggable-dragging': isDragging,
|
'file-tree-entity-dragging': dragDropItem?.draggedEntityIds?.has(id),
|
||||||
})}
|
})}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
ref={dragRef}
|
ref={dragRef}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import { useDroppable } from '../contexts/file-tree-draggable'
|
||||||
import { useFileTreeSocketListener } from '../hooks/file-tree-socket-listener'
|
import { useFileTreeSocketListener } from '../hooks/file-tree-socket-listener'
|
||||||
import FileTreeModalCreateFile from './modals/file-tree-modal-create-file'
|
import FileTreeModalCreateFile from './modals/file-tree-modal-create-file'
|
||||||
import FileTreeInner from './file-tree-inner'
|
import FileTreeInner from './file-tree-inner'
|
||||||
|
import { useDragLayer } from 'react-dnd'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
const FileTreeRoot = React.memo(function FileTreeRoot({
|
const FileTreeRoot = React.memo(function FileTreeRoot({
|
||||||
refProviders,
|
refProviders,
|
||||||
|
@ -66,14 +68,24 @@ function FileTreeRootFolder() {
|
||||||
|
|
||||||
const { isOver, dropRef } = useDroppable(fileTreeData._id)
|
const { isOver, dropRef } = useDroppable(fileTreeData._id)
|
||||||
|
|
||||||
|
const dragLayer = useDragLayer(monitor => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
item: monitor.getItem(),
|
||||||
|
clientOffset: monitor.getClientOffset(),
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FileTreeDraggablePreviewLayer isOver={isOver} />
|
<FileTreeDraggablePreviewLayer isOver={isOver} {...dragLayer} />
|
||||||
<FileTreeFolderList
|
<FileTreeFolderList
|
||||||
folders={fileTreeData.folders}
|
folders={fileTreeData.folders}
|
||||||
docs={fileTreeData.docs}
|
docs={fileTreeData.docs}
|
||||||
files={fileTreeData.fileRefs}
|
files={fileTreeData.fileRefs}
|
||||||
classes={{ root: 'file-tree-list' }}
|
classes={{
|
||||||
|
root: classnames('file-tree-list', {
|
||||||
|
'file-tree-dragging': dragLayer.isDragging,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
dropRef={dropRef}
|
dropRef={dropRef}
|
||||||
dataTestId="file-tree-list-root"
|
dataTestId="file-tree-list-root"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -88,7 +88,7 @@ export function useDraggable(draggedEntityId) {
|
||||||
const [isDraggable, setIsDraggable] = useState(true)
|
const [isDraggable, setIsDraggable] = useState(true)
|
||||||
|
|
||||||
const item = { type: DRAGGABLE_TYPE }
|
const item = { type: DRAGGABLE_TYPE }
|
||||||
const [{ isDragging }, dragRef, preview] = useDrag({
|
const [{ isDragging, draggedEntityIds }, dragRef, preview] = useDrag({
|
||||||
item, // required, but overwritten by the return value of `begin`
|
item, // required, but overwritten by the return value of `begin`
|
||||||
begin: () => {
|
begin: () => {
|
||||||
const draggedEntityIds = getDraggedEntityIds(
|
const draggedEntityIds = getDraggedEntityIds(
|
||||||
|
@ -104,6 +104,7 @@ export function useDraggable(draggedEntityId) {
|
||||||
isDragging: !!monitor.isDragging(),
|
isDragging: !!monitor.isDragging(),
|
||||||
}),
|
}),
|
||||||
canDrag: () => permissionsLevel !== 'readOnly' && isDraggable,
|
canDrag: () => permissionsLevel !== 'readOnly' && isDraggable,
|
||||||
|
end: () => item,
|
||||||
})
|
})
|
||||||
|
|
||||||
// remove the automatic preview as we're using a custom preview via
|
// remove the automatic preview as we're using a custom preview via
|
||||||
|
@ -116,6 +117,7 @@ export function useDraggable(draggedEntityId) {
|
||||||
dragRef,
|
dragRef,
|
||||||
isDragging,
|
isDragging,
|
||||||
setIsDraggable,
|
setIsDraggable,
|
||||||
|
draggedEntityIds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,22 +67,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li.dnd-droppable-hover .entity-name,
|
|
||||||
li .entity-name.droppable-hover {
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: @file-tree-item-hover-bg;
|
|
||||||
.fake-full-width-bg(@file-tree-item-hover-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.droppable-hover li div.entity-name:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
.fake-full-width-bg(transparent);
|
|
||||||
&.droppable-hover {
|
|
||||||
background-color: @file-tree-item-hover-bg;
|
|
||||||
.fake-full-width-bg(@file-tree-item-hover-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.file-tree-list {
|
ul.file-tree-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -144,10 +128,10 @@
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
background-color: transparent;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: @file-tree-item-hover-bg;
|
background-color: @file-tree-item-hover-bg;
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
// When the entity is a subfolder, the DOM element is "indented" via margin-left. This makes the
|
// When the entity is a subfolder, the DOM element is "indented" via margin-left. This makes the
|
||||||
// element not fill the entire file-tree width (as it's spaced from the left-hand side via margin)
|
// element not fill the entire file-tree width (as it's spaced from the left-hand side via margin)
|
||||||
// and, in consequence, the background gets clipped. The ::before pseudo-selector is used to fill
|
// and, in consequence, the background gets clipped. The ::before pseudo-selector is used to fill
|
||||||
|
@ -163,13 +147,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity.dnd-draggable-dragging {
|
|
||||||
.entity-name:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
.fake-full-width-bg(transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i.fa {
|
i.fa {
|
||||||
color: @file-tree-item-icon-color;
|
color: @file-tree-item-icon-color;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -309,15 +286,18 @@
|
||||||
> .entity {
|
> .entity {
|
||||||
> .entity-name {
|
> .entity-name {
|
||||||
color: @file-tree-item-selected-color;
|
color: @file-tree-item-selected-color;
|
||||||
|
|
||||||
> div > i.fa,
|
> div > i.fa,
|
||||||
> button > i.fa,
|
> button > i.fa,
|
||||||
> i.fa,
|
> i.fa,
|
||||||
.entity-menu-toggle i.fa {
|
.entity-menu-toggle i.fa {
|
||||||
color: @file-tree-item-selected-color;
|
color: @file-tree-item-selected-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
> i.linked-file-highlight {
|
> i.linked-file-highlight {
|
||||||
color: @blue;
|
color: @blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
background-color: @file-tree-item-selected-bg;
|
background-color: @file-tree-item-selected-bg;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
|
@ -332,13 +312,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul.file-tree-list li.selected.dnd-droppable-hover {
|
}
|
||||||
> .entity {
|
|
||||||
> .entity-name {
|
// while dragging, the previously selected item gets no highlight
|
||||||
background-color: @file-tree-item-hover-bg;
|
ul.file-tree-list.file-tree-dragging li.selected .entity .entity-name {
|
||||||
.fake-full-width-bg(@file-tree-item-hover-bg);
|
font-weight: normal;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
.fake-full-width-bg(transparent);
|
||||||
|
color: @file-tree-item-color;
|
||||||
|
i.fa {
|
||||||
|
color: @file-tree-item-icon-color !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the items being dragged get the full "hover" colour
|
||||||
|
ul.file-tree-list.file-tree-dragging
|
||||||
|
li
|
||||||
|
.entity.file-tree-entity-dragging
|
||||||
|
.entity-name {
|
||||||
|
background-color: fade(@file-tree-item-hover-bg, 90%);
|
||||||
|
.fake-full-width-bg(fade(@file-tree-item-hover-bg, 90%));
|
||||||
|
color: @file-tree-item-color;
|
||||||
|
i.fa {
|
||||||
|
color: @file-tree-item-icon-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the drop target gets the "selected" colour
|
||||||
|
ul.file-tree-list.file-tree-dragging
|
||||||
|
li.dnd-droppable-hover
|
||||||
|
.entity
|
||||||
|
.entity-name {
|
||||||
|
background-color: @file-tree-item-selected-bg;
|
||||||
|
.fake-full-width-bg(@file-tree-item-selected-bg);
|
||||||
|
color: @file-tree-item-selected-color;
|
||||||
|
i.fa {
|
||||||
|
color: @file-tree-item-selected-color !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,8 +365,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnd-draggable-preview-item,
|
.dnd-draggable-preview-item {
|
||||||
.ui-draggable-dragging {
|
|
||||||
background-color: fade(@file-tree-item-selected-bg, 60%);
|
background-color: fade(@file-tree-item-selected-bg, 60%);
|
||||||
color: @file-tree-item-selected-color;
|
color: @file-tree-item-selected-color;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
|
|
Loading…
Reference in a new issue