2021-06-23 05:37:08 -04:00
|
|
|
import { useState, useEffect } from 'react'
|
2020-11-26 09:22:30 -05:00
|
|
|
import PropTypes from 'prop-types'
|
|
|
|
|
2021-05-18 06:56:56 -04:00
|
|
|
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
|
2020-11-26 09:22:30 -05:00
|
|
|
|
|
|
|
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
|
2021-06-03 09:45:24 -04:00
|
|
|
import { useFileTreeMainContext } from '../../contexts/file-tree-main'
|
2020-11-26 09:22:30 -05:00
|
|
|
|
2021-02-09 04:50:16 -05:00
|
|
|
function FileTreeItemName({ name, isSelected, setIsDraggable }) {
|
2021-06-03 09:45:24 -04:00
|
|
|
const { hasWritePermissions } = useFileTreeMainContext()
|
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
const {
|
|
|
|
isRenaming,
|
|
|
|
startRenaming,
|
|
|
|
finishRenaming,
|
2020-12-09 06:55:36 -05:00
|
|
|
error,
|
2021-04-27 03:52:58 -04:00
|
|
|
cancel,
|
2020-11-26 09:22:30 -05:00
|
|
|
} = useFileTreeActionable()
|
|
|
|
|
2020-12-09 06:55:36 -05:00
|
|
|
const isRenamingEntity = isRenaming && isSelected && !error
|
2020-11-26 09:22:30 -05:00
|
|
|
|
2021-02-09 04:50:16 -05:00
|
|
|
useEffect(() => {
|
2021-06-03 09:45:24 -04:00
|
|
|
setIsDraggable(hasWritePermissions && !isRenamingEntity)
|
|
|
|
}, [setIsDraggable, hasWritePermissions, isRenamingEntity])
|
2021-02-09 04:50:16 -05:00
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
if (isRenamingEntity) {
|
|
|
|
return (
|
|
|
|
<InputName
|
|
|
|
initialValue={name}
|
|
|
|
finishRenaming={finishRenaming}
|
|
|
|
cancel={cancel}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<DisplayName
|
|
|
|
name={name}
|
|
|
|
isSelected={isSelected}
|
|
|
|
startRenaming={startRenaming}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
FileTreeItemName.propTypes = {
|
|
|
|
name: PropTypes.string.isRequired,
|
2021-02-09 04:50:16 -05:00
|
|
|
isSelected: PropTypes.bool.isRequired,
|
2021-04-27 03:52:58 -04:00
|
|
|
setIsDraggable: PropTypes.func.isRequired,
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function DisplayName({ name, isSelected, startRenaming }) {
|
|
|
|
const [clicksInSelectedCount, setClicksInSelectedCount] = useState(0)
|
|
|
|
|
|
|
|
function onClick() {
|
|
|
|
setClicksInSelectedCount(clicksInSelectedCount + 1)
|
|
|
|
if (!isSelected) setClicksInSelectedCount(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
function onDoubleClick() {
|
|
|
|
// only start renaming if the button got two or more clicks while the item
|
|
|
|
// was selected. This is to prevent starting a rename on an unselected item.
|
|
|
|
// When the item is being selected it can trigger a loss of focus which
|
|
|
|
// causes UI problems.
|
|
|
|
if (clicksInSelectedCount < 2) return
|
|
|
|
startRenaming()
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<button
|
|
|
|
className="item-name-button"
|
|
|
|
onClick={onClick}
|
|
|
|
onDoubleClick={onDoubleClick}
|
|
|
|
>
|
|
|
|
<span>{name}</span>
|
|
|
|
</button>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
DisplayName.propTypes = {
|
|
|
|
name: PropTypes.string.isRequired,
|
|
|
|
startRenaming: PropTypes.func.isRequired,
|
2021-04-27 03:52:58 -04:00
|
|
|
isSelected: PropTypes.bool.isRequired,
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function InputName({ initialValue, finishRenaming, cancel }) {
|
|
|
|
const [value, setValue] = useState(initialValue)
|
|
|
|
|
|
|
|
// The react-bootstrap Dropdown re-focuses on the Dropdown.Toggle
|
|
|
|
// after a menu item is clicked, following the ARIA authoring practices:
|
|
|
|
// https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html
|
|
|
|
// To improve UX, we want to auto-focus to the input when renaming. We use
|
|
|
|
// requestAnimationFrame to immediately move focus to the input after it is
|
|
|
|
// shown
|
|
|
|
const { autoFocusedRef } = useRefWithAutoFocus()
|
|
|
|
|
|
|
|
function handleFocus(ev) {
|
|
|
|
const lastDotIndex = ev.target.value.lastIndexOf('.')
|
|
|
|
ev.target.setSelectionRange(0, lastDotIndex)
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleChange(ev) {
|
|
|
|
setValue(ev.target.value)
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleKeyDown(ev) {
|
|
|
|
if (ev.key === 'Enter') {
|
|
|
|
finishRenaming(value)
|
|
|
|
}
|
|
|
|
if (ev.key === 'Escape') {
|
|
|
|
cancel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleBlur() {
|
|
|
|
finishRenaming(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<span className="rename-input">
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
value={value}
|
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
onChange={handleChange}
|
|
|
|
onBlur={handleBlur}
|
|
|
|
onFocus={handleFocus}
|
|
|
|
ref={autoFocusedRef}
|
|
|
|
/>
|
|
|
|
</span>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
InputName.propTypes = {
|
|
|
|
initialValue: PropTypes.string.isRequired,
|
|
|
|
finishRenaming: PropTypes.func.isRequired,
|
2021-04-27 03:52:58 -04:00
|
|
|
cancel: PropTypes.func.isRequired,
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
export default FileTreeItemName
|