Skip to content

Commit

Permalink
chore(web): refactor story dnd (#723)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkumbobeaty authored Oct 27, 2023
1 parent 8bc811e commit da57ac4
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 126 deletions.
17 changes: 8 additions & 9 deletions web/src/beta/components/DragAndDropList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useDragDropManager } from "react-dnd";

import { styled } from "@reearth/services/theme";
Expand All @@ -25,25 +25,24 @@ function DragAndDropList<Item extends { id: string } = { id: string }>({
gap,
}: Props<Item>) {
const [movingItems, setMovingItems] = useState<Item[]>(items);
const listRef = useRef<null | HTMLDivElement>(null);

const { updatePosition } = useScroll(listRef);

const { scrollContainerRef } = useScroll();
const dragDropManager = useDragDropManager();
const monitor = dragDropManager.getMonitor();
const [, setIsMonitor] = useState(0);

useEffect(() => {
const unsubscribe = monitor.subscribeToOffsetChange(() => {
const offset = monitor.getSourceClientOffset()?.y as number;
updatePosition({ position: offset, isScrollAllowed: true });
setIsMonitor(offset);
});
return unsubscribe;
}, [monitor, updatePosition]);
}, [monitor]);

const customDragHandler = (item: Item): boolean => {
// eslint-disable-next-line no-prototype-builtins
return item.hasOwnProperty("extensionId");
};

useEffect(() => {
setMovingItems(items);
}, [items]);
Expand Down Expand Up @@ -71,10 +70,10 @@ function DragAndDropList<Item extends { id: string } = { id: string }>({
}, [items]);

return (
<SWrapper gap={gap} ref={listRef}>
<SWrapper gap={gap} ref={scrollContainerRef}>
{movingItems.map((item, i) => {
const id = getId(item);
const shouldUseCustomHandler = customDragHandler(item); // Determine whether to use custom handler
const shouldUseCustomHandler = customDragHandler(item);
return (
<Item
itemGroupKey={uniqueKey}
Expand Down
80 changes: 23 additions & 57 deletions web/src/beta/components/DragAndDropList/scrollItem.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,29 @@
import { useEffect, useState, useRef, RefObject } from "react";

export interface IUseScroll {
position: number;
isScrollAllowed: boolean;
}

const BOUND_HEIGHT = 70;

function getScrollDirection({
position,
upperBounds = Infinity,
lowerBounds = -Infinity,
}: {
position: number | undefined;
upperBounds: number | undefined;
lowerBounds: number | undefined;
}): "top" | "bottom" | "stable" {
if (position === undefined) {
return "stable";
}
if (position > lowerBounds - BOUND_HEIGHT) {
return "bottom";
}
if (position > upperBounds + BOUND_HEIGHT) {
return "top";
}
return "stable";
}

export const useScroll = (ref: RefObject<HTMLElement | null>) => {
const [config, setConfig] = useState<Partial<IUseScroll>>({
position: 0,
isScrollAllowed: false,
});

const scrollTimer = useRef<null | NodeJS.Timeout>(null);

const scrollSpeed = 1;
const { position, isScrollAllowed } = config;

const bounds = ref.current?.getBoundingClientRect();
const direction = getScrollDirection({
position,
upperBounds: bounds?.top,
lowerBounds: bounds?.bottom,
});
import { useCallback, useEffect, useRef } from "react";

export const useScroll = () => {
const scrollContainerRef = useRef<null | HTMLDivElement>(null);

const handleMouseMove = useCallback(
(e: MouseEvent) => {
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) return;

const mouseY = e.clientY;
const scrollThreshold = scrollContainer.offsetHeight * 0.4;
mouseY < scrollThreshold
? (scrollContainer.scrollTop -= 10)
: mouseY > scrollContainer.offsetHeight - scrollThreshold &&
(scrollContainer.scrollTop += 10);
},
[scrollContainerRef],
);

useEffect(() => {
if (direction !== "stable" && isScrollAllowed) {
scrollTimer.current = setInterval(() => {
ref.current?.scrollBy(0, scrollSpeed * (direction === "top" ? -1 : 1));
}, 1);
}
document.addEventListener("mousemove", handleMouseMove);
return () => {
if (scrollTimer.current) {
clearInterval(scrollTimer.current);
}
document.removeEventListener("mousemove", handleMouseMove);
};
}, [isScrollAllowed, direction, ref, scrollSpeed]);
}, [handleMouseMove]);

return { updatePosition: setConfig } as const;
return { scrollContainerRef } as const;
};
29 changes: 2 additions & 27 deletions web/src/beta/lib/core/StoryPanel/SelectableArea/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import {
Dispatch,
MouseEvent,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react";

type Props = {
editMode?: boolean;
isSelected?: boolean;
property?: any;
setEditMode?: Dispatch<SetStateAction<boolean>>;
onClickAway?: () => void;
};

export default ({ editMode, isSelected, property, setEditMode, onClickAway }: Props) => {
const [isHovered, setHover] = useState(false);
export default ({ editMode, isSelected, setEditMode, onClickAway }: Props) => {
const [showPadding, setShowPadding] = useState(false);

useEffect(() => {
Expand All @@ -26,29 +16,14 @@ export default ({ editMode, isSelected, property, setEditMode, onClickAway }: Pr
}
}, [isSelected, editMode, setEditMode]);

const handleMouseOver = useCallback((e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
setHover(true);
}, []);

const handleMouseOut = useCallback(() => setHover(false), []);

const handleClickAway = useCallback(() => {
setShowPadding(false);
onClickAway?.();
}, [onClickAway]);

const panelSettings = useMemo(() => {
return property?.panel;
}, [property?.panel]);

return {
isHovered,
showPadding,
panelSettings,
setShowPadding,
handleMouseOver,
handleMouseOut,
handleClickAway,
};
};
54 changes: 21 additions & 33 deletions web/src/beta/lib/core/StoryPanel/SelectableArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,21 @@ const SelectableArea: React.FC<Props> = ({
isSelected,
children,
propertyId,
panelSettings,
dndEnabled,
showSettings,
editMode,
position,
noBorder,
isEditable,
panelSettings,
setEditMode,
onEditModeToggle,
onSettingsToggle,
onClick,
onClickAway,
onRemove,
}) => {
const {
isHovered,
showPadding,
setShowPadding,
handleMouseOver,
handleMouseOut,
handleClickAway,
} = useHooks({
const { showPadding, setShowPadding, handleClickAway } = useHooks({
editMode,
isSelected,
setEditMode,
Expand All @@ -66,30 +59,23 @@ const SelectableArea: React.FC<Props> = ({
<>{children}</>
) : (
<ClickAwayListener enabled={isSelected} onClickAway={handleClickAway}>
<Wrapper
isSelected={isSelected}
noBorder={noBorder}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
onClick={onClick}>
{(isHovered || isSelected) && (
<ActionPanel
title={title}
icon={icon}
isSelected={isSelected}
showSettings={showSettings}
showPadding={showPadding}
editMode={editMode}
propertyId={propertyId}
panelSettings={panelSettings}
dndEnabled={dndEnabled}
position={position}
setShowPadding={setShowPadding}
onEditModeToggle={onEditModeToggle}
onSettingsToggle={onSettingsToggle}
onRemove={onRemove}
/>
)}
<Wrapper isSelected={isSelected} noBorder={noBorder} onClick={onClick}>
<ActionPanel
title={title}
icon={icon}
isSelected={isSelected}
showSettings={showSettings}
showPadding={showPadding}
editMode={editMode}
propertyId={propertyId}
panelSettings={panelSettings}
dndEnabled={dndEnabled}
position={position}
setShowPadding={setShowPadding}
onEditModeToggle={onEditModeToggle}
onSettingsToggle={onSettingsToggle}
onRemove={onRemove}
/>
{children}
</Wrapper>
</ClickAwayListener>
Expand All @@ -104,8 +90,10 @@ const Wrapper = styled.div<{ isSelected?: boolean; noBorder?: boolean }>`
transition: all 0.3s;
padding: 1px;
position: relative;
overflow: hidden;
:hover {
border-color: ${({ isSelected, theme }) => !isSelected && theme.select.weaker};
overflow: visible;
}
`;

0 comments on commit da57ac4

Please sign in to comment.