mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:13:51 +01:00
Better drag for empty folders
This commit is contained in:
@@ -33,17 +33,19 @@ const icons = {
|
||||
chevron_left: lucide.ChevronLeftIcon,
|
||||
chevron_right: lucide.ChevronRightIcon,
|
||||
circle_alert: lucide.CircleAlertIcon,
|
||||
circle_dashed: lucide.CircleDashedIcon,
|
||||
circle_dollar_sign: lucide.CircleDollarSignIcon,
|
||||
circle_fading_arrow_up: lucide.CircleFadingArrowUpIcon,
|
||||
clock: lucide.ClockIcon,
|
||||
code: lucide.CodeIcon,
|
||||
columns_2: lucide.Columns2Icon,
|
||||
command: lucide.CommandIcon,
|
||||
corner_right_up: lucide.CornerRightUpIcon,
|
||||
credit_card: lucide.CreditCardIcon,
|
||||
cookie: lucide.CookieIcon,
|
||||
copy: lucide.CopyIcon,
|
||||
copy_check: lucide.CopyCheck,
|
||||
corner_right_up: lucide.CornerRightUpIcon,
|
||||
credit_card: lucide.CreditCardIcon,
|
||||
dot: lucide.DotIcon,
|
||||
download: lucide.DownloadIcon,
|
||||
ellipsis: lucide.EllipsisIcon,
|
||||
expand: lucide.ExpandIcon,
|
||||
@@ -55,8 +57,8 @@ const icons = {
|
||||
flame: lucide.FlameIcon,
|
||||
flask: lucide.FlaskConicalIcon,
|
||||
folder: lucide.FolderIcon,
|
||||
folder_cog: lucide.FolderCogIcon,
|
||||
folder_code: lucide.FolderCodeIcon,
|
||||
folder_cog: lucide.FolderCogIcon,
|
||||
folder_git: lucide.FolderGitIcon,
|
||||
folder_input: lucide.FolderInputIcon,
|
||||
folder_open: lucide.FolderOpenIcon,
|
||||
|
||||
@@ -267,10 +267,15 @@ function TreeInner<T extends { id: string }>(
|
||||
const collapsedMap = jotaiStore.get(collapsedFamily(treeId));
|
||||
const isHoveredItemCollapsed = hovered != null ? collapsedMap[hovered.item.id] : false;
|
||||
|
||||
if (hovered?.children != null && side === 'below' && !isHoveredItemCollapsed) {
|
||||
if (hovered?.children != null && side === 'below') {
|
||||
// Move into the folder if it's open and we're moving below it
|
||||
hoveredParent = hovered;
|
||||
hoveredChildIndex = 0;
|
||||
if (isHoveredItemCollapsed) {
|
||||
hoveredParent = hovered;
|
||||
hoveredChildIndex = 0;
|
||||
} else {
|
||||
hoveredParent = hovered;
|
||||
hoveredChildIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const parentId = hoveredParent?.item.id ?? null;
|
||||
|
||||
@@ -3,28 +3,30 @@ import { useAtomValue } from 'jotai';
|
||||
import { memo } from 'react';
|
||||
import { DropMarker } from '../../DropMarker';
|
||||
import { hoveredParentDepthFamily, isCollapsedFamily, isIndexHoveredFamily } from './atoms';
|
||||
import type { TreeNode } from './common';
|
||||
|
||||
export const TreeDropMarker = memo(function TreeDropMarker({
|
||||
export const TreeDropMarker = memo(function TreeDropMarker<T extends { id: string }>({
|
||||
className,
|
||||
treeId,
|
||||
itemId,
|
||||
node,
|
||||
index,
|
||||
}: {
|
||||
treeId: string;
|
||||
index: number;
|
||||
itemId: string | null;
|
||||
node: TreeNode<T> | null;
|
||||
className?: string;
|
||||
}) {
|
||||
const itemId = node?.item.id;
|
||||
const isHovered = useAtomValue(isIndexHoveredFamily({ treeId, index }));
|
||||
const parentDepth = useAtomValue(hoveredParentDepthFamily(treeId));
|
||||
const collapsed = useAtomValue(isCollapsedFamily({ treeId, itemId: itemId ?? undefined }));
|
||||
const collapsed = useAtomValue(isCollapsedFamily({ treeId, itemId }));
|
||||
|
||||
// Only show if we're hovering over this index
|
||||
if (!isHovered) return null;
|
||||
if (!isHovered) return null;
|
||||
|
||||
// Don't show if we're right under a collapsed folder. We have a separate delayed expansion
|
||||
// Don't show if we're right under a collapsed folder, or empty folder. We have a separate delayed expansion
|
||||
// animation for that.
|
||||
if (collapsed) return null;
|
||||
if (collapsed || node?.children?.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="drop-marker" style={{ paddingLeft: `${parentDepth}rem` }}>
|
||||
|
||||
@@ -51,7 +51,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
const isCollapsed = useAtomValue(isCollapsedFamily({ treeId, itemId: node.item.id }));
|
||||
const isLastSelected = useAtomValue(isLastFocusedFamily({ treeId, itemId: node.item.id }));
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
const [isDropHover, setIsDropHover] = useState<boolean>(false);
|
||||
const [dropHover, setDropHover] = useState<null | 'drop' | 'animate'>(null);
|
||||
const startedHoverTimeout = useRef<NodeJS.Timeout>(undefined);
|
||||
|
||||
const isAncestorCollapsedAtom = useMemo(
|
||||
@@ -147,29 +147,35 @@ function TreeItem_<T extends { id: string }>({
|
||||
}
|
||||
}, [getEditOptions, node.children, toggleCollapsed]);
|
||||
|
||||
const clearHoverTimer = () => {
|
||||
const clearDropHover = () => {
|
||||
if (startedHoverTimeout.current) {
|
||||
setIsDropHover(false); // NEW
|
||||
clearTimeout(startedHoverTimeout.current); // NEW
|
||||
startedHoverTimeout.current = undefined; // NEW
|
||||
clearTimeout(startedHoverTimeout.current);
|
||||
startedHoverTimeout.current = undefined;
|
||||
}
|
||||
setDropHover(null);
|
||||
};
|
||||
|
||||
// Toggle auto-expand of folders when hovering over them
|
||||
useDndMonitor({
|
||||
onDragEnd() {
|
||||
clearDropHover();
|
||||
},
|
||||
onDragMove(e: DragMoveEvent) {
|
||||
const side = computeSideForDragMove(node, e);
|
||||
const isFolderWithChildren = (node.children?.length ?? 0) > 0;
|
||||
const isFolder = node.children != null;
|
||||
const hasChildren = (node.children?.length ?? 0) > 0;
|
||||
const isCollapsed = jotaiStore.get(isCollapsedFamily({ treeId, itemId: node.item.id }));
|
||||
if (isCollapsed && isFolderWithChildren && side === 'below') {
|
||||
setIsDropHover(true);
|
||||
if (isCollapsed && isFolder && hasChildren && side === 'below') {
|
||||
setDropHover('animate');
|
||||
clearTimeout(startedHoverTimeout.current);
|
||||
startedHoverTimeout.current = setTimeout(() => {
|
||||
jotaiStore.set(isCollapsedFamily({ treeId, itemId: node.item.id }), false);
|
||||
setIsDropHover(false);
|
||||
clearDropHover();
|
||||
}, HOVER_CLOSED_FOLDER_DELAY);
|
||||
} else if (isFolder && !hasChildren && side === 'below') {
|
||||
setDropHover('drop');
|
||||
} else {
|
||||
clearHoverTimer();
|
||||
clearDropHover();
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -227,6 +233,9 @@ function TreeItem_<T extends { id: string }>({
|
||||
'tree-item',
|
||||
'h-sm',
|
||||
'grid grid-cols-[auto_minmax(0,1fr)]',
|
||||
editing && 'ring-1 focus-within:ring-focus',
|
||||
dropHover != null && 'relative z-10 ring-2 ring-primary',
|
||||
dropHover === 'animate' && 'animate-blinkRing',
|
||||
isSelected && 'selected',
|
||||
)}
|
||||
>
|
||||
@@ -235,10 +244,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
className={classNames(
|
||||
'tree-item-selectable',
|
||||
'text-text-subtle',
|
||||
isSelected && 'selected',
|
||||
'grid grid-cols-[auto_minmax(0,1fr)] items-center rounded-md',
|
||||
editing && 'ring-1 focus-within:ring-focus',
|
||||
isDropHover && 'relative z-10 ring-2 ring-primary animate-blinkRing',
|
||||
)}
|
||||
>
|
||||
{showContextMenu && (
|
||||
@@ -251,14 +257,12 @@ function TreeItem_<T extends { id: string }>({
|
||||
{node.children != null ? (
|
||||
<button tabIndex={-1} className="h-full pl-[0.5rem]" onClick={toggleCollapsed}>
|
||||
<Icon
|
||||
icon="chevron_right"
|
||||
icon={node.children.length === 0 ? 'dot' : 'chevron_right'}
|
||||
className={classNames(
|
||||
'transition-transform text-text-subtlest',
|
||||
'ml-auto',
|
||||
'w-[1rem] h-[1rem]',
|
||||
// node.children.length == 0 && 'opacity-0',
|
||||
!isCollapsed && 'rotate-90',
|
||||
// isHoveredAsParent && '!text-text',
|
||||
!isCollapsed && node.children.length > 0 && 'rotate-90',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -32,7 +32,7 @@ function TreeItemList_<T extends { id: string }>({
|
||||
}: TreeItemListProps<T>) {
|
||||
return (
|
||||
<ul role="tree" style={style} className={className}>
|
||||
<TreeDropMarker itemId={null} treeId={treeId} index={0} />
|
||||
<TreeDropMarker node={null} treeId={treeId} index={0} />
|
||||
{nodes.map((child, i) => (
|
||||
<Fragment key={getItemKey(child.node.item)}>
|
||||
<TreeItem
|
||||
@@ -46,7 +46,7 @@ function TreeItemList_<T extends { id: string }>({
|
||||
getItemKey={getItemKey}
|
||||
depth={forceDepth == null ? child.depth : forceDepth}
|
||||
/>
|
||||
<TreeDropMarker itemId={child.node.item.id} treeId={treeId} index={i+1} />
|
||||
<TreeDropMarker node={child.node} treeId={treeId} index={i+1} />
|
||||
</Fragment>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user