Files
grungnet/src/components/tile/SortableTiles.js

210 lines
5.8 KiB
JavaScript
Raw Normal View History

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,
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";
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";
import Vector2 from "../../helpers/Vector2";
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,
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();
const [overId, setOverId] = useState();
2021-05-13 16:26:59 +10:00
function handleDragStart({ active, over }) {
2021-05-13 16:26:59 +10:00
setDragId(active.id);
setOverId(over?.id);
2021-05-25 15:47:52 +10:00
if (!selectedGroupIds.includes(active.id)) {
onTileSelect(active.id);
}
}
function handleDragOver({ over }) {
setOverId(over?.id);
2021-05-13 16:26:59 +10:00
}
function handleDragEnd({ active, over }) {
setDragId();
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({
transform: !!dragId ? "scale(0.9)" : "scale(1)",
2021-05-13 16:26:59 +10:00
config: config.wobbly,
position: "relative",
2021-05-13 16:26:59 +10:00
});
const overGroupId =
overId && overId.startsWith("__group__") && overId.slice(9);
2021-05-25 15:47:52 +10:00
function renderSortableGroup(group, selectedGroups) {
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);
}
return renderTile(group);
}
2021-05-25 15:47:52 +10:00
function renderDragOverlays() {
let selectedIndices = selectedGroupIds.map((groupId) =>
groups.findIndex((group) => group.id === groupId)
);
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={{
transform: `translate(${coords[index].x}%, ${coords[index].y}%)`,
2021-05-25 15:47:52 +10:00
}}
>
<animated.div style={dragBounce}>
{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}
onDragOver={handleDragOver}
2021-05-13 16:26:59 +10:00
sensors={sensors}
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;