2021-05-13 16:26:59 +10:00
|
|
|
import React, { useState } from "react";
|
|
|
|
|
import { createPortal } from "react-dom";
|
|
|
|
|
import {
|
|
|
|
|
DndContext,
|
|
|
|
|
DragOverlay,
|
|
|
|
|
MouseSensor,
|
|
|
|
|
TouchSensor,
|
|
|
|
|
useSensor,
|
|
|
|
|
useSensors,
|
2021-05-21 15:12:25 +10:00
|
|
|
closestCenter,
|
2021-05-13 16:26:59 +10:00
|
|
|
} from "@dnd-kit/core";
|
|
|
|
|
import { SortableContext, arrayMove } from "@dnd-kit/sortable";
|
|
|
|
|
import { animated, useSpring, config } from "react-spring";
|
2021-05-27 15:46:14 +10:00
|
|
|
import { Badge } from "theme-ui";
|
2021-05-13 16:26:59 +10:00
|
|
|
|
2021-05-25 15:47:52 +10:00
|
|
|
import { moveGroups } from "../../helpers/group";
|
|
|
|
|
import { keyBy } from "../../helpers/shared";
|
2021-05-27 15:46:14 +10:00
|
|
|
import Vector2 from "../../helpers/Vector2";
|
2021-05-21 15:12:25 +10:00
|
|
|
|
|
|
|
|
import SortableTile from "./SortableTile";
|
|
|
|
|
|
2021-05-21 16:12:09 +10:00
|
|
|
function SortableTiles({
|
|
|
|
|
groups,
|
2021-05-25 15:47:52 +10:00
|
|
|
selectedGroupIds,
|
2021-05-21 16:12:09 +10:00
|
|
|
onGroupChange,
|
|
|
|
|
renderTile,
|
|
|
|
|
onTileSelect,
|
2021-05-24 13:34:21 +10:00
|
|
|
disableGrouping,
|
2021-05-24 17:11:46 +10:00
|
|
|
openGroupId,
|
2021-05-25 15:47:52 +10:00
|
|
|
columns,
|
2021-05-21 16:12:09 +10:00
|
|
|
}) {
|
2021-05-13 16:26:59 +10:00
|
|
|
const mouseSensor = useSensor(MouseSensor, {
|
|
|
|
|
activationConstraint: { delay: 250, tolerance: 5 },
|
|
|
|
|
});
|
|
|
|
|
const touchSensor = useSensor(TouchSensor, {
|
|
|
|
|
activationConstraint: { delay: 250, tolerance: 5 },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const sensors = useSensors(mouseSensor, touchSensor);
|
|
|
|
|
|
|
|
|
|
const [dragId, setDragId] = useState();
|
2021-05-21 15:12:25 +10:00
|
|
|
const [overId, setOverId] = useState();
|
2021-05-13 16:26:59 +10:00
|
|
|
|
2021-05-21 15:12:25 +10:00
|
|
|
function handleDragStart({ active, over }) {
|
2021-05-13 16:26:59 +10:00
|
|
|
setDragId(active.id);
|
2021-05-21 15:12:25 +10:00
|
|
|
setOverId(over?.id);
|
2021-05-25 15:47:52 +10:00
|
|
|
if (!selectedGroupIds.includes(active.id)) {
|
|
|
|
|
onTileSelect(active.id);
|
|
|
|
|
}
|
2021-05-21 15:12:25 +10:00
|
|
|
}
|
|
|
|
|
|
2021-05-24 17:44:49 +10:00
|
|
|
function handleDragOver({ over }) {
|
2021-05-21 15:12:25 +10:00
|
|
|
setOverId(over?.id);
|
2021-05-13 16:26:59 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDragEnd({ active, over }) {
|
|
|
|
|
setDragId();
|
2021-05-21 15:12:25 +10:00
|
|
|
setOverId();
|
|
|
|
|
if (!active || !over) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (over.id.startsWith("__group__")) {
|
2021-05-21 16:12:09 +10:00
|
|
|
const overId = over.id.slice(9);
|
|
|
|
|
if (overId === active.id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-05-25 15:47:52 +10:00
|
|
|
|
|
|
|
|
let newGroups = groups;
|
2021-05-21 16:12:09 +10:00
|
|
|
const overGroupIndex = groups.findIndex((group) => group.id === overId);
|
2021-05-25 15:47:52 +10:00
|
|
|
const selectedGroupIndices = selectedGroupIds.map((groupId) =>
|
|
|
|
|
groups.findIndex((group) => group.id === groupId)
|
|
|
|
|
);
|
|
|
|
|
onGroupChange(
|
|
|
|
|
moveGroups(newGroups, overGroupIndex, selectedGroupIndices)
|
|
|
|
|
);
|
2021-05-21 16:12:09 +10:00
|
|
|
onTileSelect();
|
|
|
|
|
} else if (active.id !== over.id) {
|
2021-05-25 15:47:52 +10:00
|
|
|
let newGroups = groups;
|
|
|
|
|
for (let groupId of selectedGroupIds) {
|
|
|
|
|
const oldIndex = newGroups.findIndex((group) => group.id === groupId);
|
|
|
|
|
const newIndex = newGroups.findIndex((group) => group.id === over.id);
|
|
|
|
|
newGroups = arrayMove(newGroups, oldIndex, newIndex);
|
|
|
|
|
}
|
|
|
|
|
onGroupChange(newGroups);
|
2021-05-13 16:26:59 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dragBounce = useSpring({
|
2021-05-21 15:12:25 +10:00
|
|
|
transform: !!dragId ? "scale(0.9)" : "scale(1)",
|
2021-05-13 16:26:59 +10:00
|
|
|
config: config.wobbly,
|
2021-05-27 15:46:14 +10:00
|
|
|
position: "relative",
|
2021-05-13 16:26:59 +10:00
|
|
|
});
|
|
|
|
|
|
2021-05-21 15:12:25 +10:00
|
|
|
const overGroupId =
|
|
|
|
|
overId && overId.startsWith("__group__") && overId.slice(9);
|
|
|
|
|
|
2021-05-25 15:47:52 +10:00
|
|
|
function renderSortableGroup(group, selectedGroups) {
|
2021-05-24 13:34:21 +10:00
|
|
|
if (overGroupId === group.id && dragId && group.id !== dragId) {
|
|
|
|
|
// If dragging over a group render a preview of that group
|
2021-05-25 15:47:52 +10:00
|
|
|
const previewGroup = moveGroups(
|
|
|
|
|
[group, ...selectedGroups],
|
|
|
|
|
0,
|
|
|
|
|
selectedGroups.map((_, i) => i + 1)
|
|
|
|
|
)[0];
|
|
|
|
|
return renderTile(previewGroup);
|
2021-05-24 13:34:21 +10:00
|
|
|
}
|
|
|
|
|
return renderTile(group);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-25 15:47:52 +10:00
|
|
|
function renderDragOverlays() {
|
|
|
|
|
let selectedIndices = selectedGroupIds.map((groupId) =>
|
|
|
|
|
groups.findIndex((group) => group.id === groupId)
|
|
|
|
|
);
|
2021-05-27 15:46:14 +10:00
|
|
|
const activeIndex = groups.findIndex((group) => group.id === dragId);
|
|
|
|
|
// Sort so the draging tile is the first element
|
|
|
|
|
selectedIndices = selectedIndices.sort((a, b) =>
|
|
|
|
|
a === activeIndex ? -1 : b === activeIndex ? 1 : 0
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
selectedIndices = selectedIndices.slice(0, 5);
|
|
|
|
|
|
|
|
|
|
let coords = selectedIndices.map(
|
|
|
|
|
(_, index) => new Vector2(5 * index, 5 * index)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Reverse so the first element is rendered on top
|
|
|
|
|
selectedIndices = selectedIndices.reverse();
|
|
|
|
|
coords = coords.reverse();
|
|
|
|
|
|
|
|
|
|
const selectedGroups = selectedIndices.map((index) => groups[index]);
|
|
|
|
|
|
|
|
|
|
return selectedGroups.map((group, index) => (
|
|
|
|
|
<DragOverlay dropAnimation={null} key={group.id}>
|
2021-05-25 15:47:52 +10:00
|
|
|
<div
|
|
|
|
|
style={{
|
2021-05-27 15:46:14 +10:00
|
|
|
transform: `translate(${coords[index].x}%, ${coords[index].y}%)`,
|
2021-05-25 15:47:52 +10:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<animated.div style={dragBounce}>
|
2021-05-27 15:46:14 +10:00
|
|
|
{renderTile(group)}
|
|
|
|
|
{index === selectedIndices.length - 1 &&
|
|
|
|
|
selectedGroupIds.length > 1 && (
|
|
|
|
|
<Badge
|
|
|
|
|
sx={{
|
|
|
|
|
position: "absolute",
|
|
|
|
|
top: 0,
|
|
|
|
|
right: 0,
|
|
|
|
|
transform: "translate(25%, -25%)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{selectedGroupIds.length}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
2021-05-25 15:47:52 +10:00
|
|
|
</animated.div>
|
|
|
|
|
</div>
|
|
|
|
|
</DragOverlay>
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderTiles() {
|
|
|
|
|
const groupsByIds = keyBy(groups, "id");
|
|
|
|
|
const selectedGroupIdsSet = new Set(selectedGroupIds);
|
|
|
|
|
let selectedGroups = [];
|
|
|
|
|
let hasSelectedContainerGroup = false;
|
|
|
|
|
for (let groupId of selectedGroupIds) {
|
|
|
|
|
const group = groupsByIds[groupId];
|
|
|
|
|
if (group) {
|
|
|
|
|
selectedGroups.push(group);
|
|
|
|
|
if (group.type === "group") {
|
|
|
|
|
hasSelectedContainerGroup = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return groups.map((group) => {
|
|
|
|
|
const isDragging = dragId && selectedGroupIdsSet.has(group.id);
|
|
|
|
|
const disableTileGrouping =
|
|
|
|
|
disableGrouping || isDragging || hasSelectedContainerGroup;
|
|
|
|
|
return (
|
|
|
|
|
<SortableTile
|
|
|
|
|
id={group.id}
|
|
|
|
|
key={group.id}
|
|
|
|
|
disableGrouping={disableTileGrouping}
|
|
|
|
|
hidden={group.id === openGroupId}
|
|
|
|
|
isDragging={isDragging}
|
|
|
|
|
>
|
|
|
|
|
{renderSortableGroup(group, selectedGroups)}
|
|
|
|
|
</SortableTile>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 16:26:59 +10:00
|
|
|
return (
|
|
|
|
|
<DndContext
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
onDragEnd={handleDragEnd}
|
2021-05-24 17:44:49 +10:00
|
|
|
onDragOver={handleDragOver}
|
2021-05-13 16:26:59 +10:00
|
|
|
sensors={sensors}
|
2021-05-21 15:12:25 +10:00
|
|
|
collisionDetection={closestCenter}
|
2021-05-13 16:26:59 +10:00
|
|
|
>
|
|
|
|
|
<SortableContext items={groups}>
|
2021-05-25 15:47:52 +10:00
|
|
|
{renderTiles()}
|
|
|
|
|
{createPortal(dragId && renderDragOverlays(), document.body)}
|
2021-05-13 16:26:59 +10:00
|
|
|
</SortableContext>
|
|
|
|
|
</DndContext>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default SortableTiles;
|