From 1546b88fc9efe0b3aa51540cb3a077995b030d62 Mon Sep 17 00:00:00 2001 From: Jiawei <141555357+JiaweiZhu28@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:19:58 -0400 Subject: [PATCH 1/2] Tilt when dragged Make cards and lists tilt when dragged. --- examples/waspello/src/cards/MainPage.jsx | 444 +++++++++++++---------- 1 file changed, 249 insertions(+), 195 deletions(-) diff --git a/examples/waspello/src/cards/MainPage.jsx b/examples/waspello/src/cards/MainPage.jsx index e7defb8a5b..62763908e8 100644 --- a/examples/waspello/src/cards/MainPage.jsx +++ b/examples/waspello/src/cards/MainPage.jsx @@ -1,18 +1,18 @@ -import React, { useState, useRef, useContext } from 'react' -import { Plus, X, MoreHorizontal } from 'react-feather' -import { Popover } from 'react-tiny-popover' -import classnames from 'classnames' -import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' +import React, { useState, useRef, useContext } from "react"; +import { Plus, X, MoreHorizontal } from "react-feather"; +import { Popover } from "react-tiny-popover"; +import classnames from "classnames"; +import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; -import UserPageLayout from './UserPageLayout' +import UserPageLayout from "./UserPageLayout"; -import './Main.css' +import "./Main.css"; import { calcNewPosOfDndItemInsertedInAnotherList, calcNewPosOfDndItemMovedWithinList, PositionContext, - PositionProvider -} from './PositionContext' + PositionProvider, +} from "./PositionContext"; import { createList, @@ -26,47 +26,55 @@ import { } from "wasp/client/operations"; const createListIdToSortedCardsMap = (listsAndCards) => { - const listIdToSortedCardsMap = {} + const listIdToSortedCardsMap = {}; - listsAndCards.forEach(list => { - listIdToSortedCardsMap[list.id] = [...list.cards].sort((a, b) => a.pos - b.pos) - }) + listsAndCards.forEach((list) => { + listIdToSortedCardsMap[list.id] = [...list.cards].sort( + (a, b) => a.pos - b.pos + ); + }); - return listIdToSortedCardsMap -} + return listIdToSortedCardsMap; +}; const MainPage = ({ user }) => { - const { data: listsAndCards, isFetchingListsAndCards, errorListsAndCards } - = useQuery(getListsAndCards) + const { + data: listsAndCards, + isFetchingListsAndCards, + errorListsAndCards, + } = useQuery(getListsAndCards); // NOTE(matija): this is only a shallow copy. - const listsSortedByPos = listsAndCards && [...listsAndCards].sort((a, b) => a.pos - b.pos) + const listsSortedByPos = + listsAndCards && [...listsAndCards].sort((a, b) => a.pos - b.pos); // Create a map with listId -> cards sorted by pos. - const listIdToSortedCardsMap = listsAndCards && createListIdToSortedCardsMap(listsAndCards) + const listIdToSortedCardsMap = + listsAndCards && createListIdToSortedCardsMap(listsAndCards); const onDragEnd = async (result) => { // Item was dropped outside of the droppable area. if (!result.destination) { - return + return; } // TODO(matija): make an enum for type strings (BOARD, CARD). - if (result.type === 'BOARD') { - const newPos = - calcNewPosOfDndItemMovedWithinList( - listsSortedByPos, result.source.index, result.destination.index - ) + if (result.type === "BOARD") { + const newPos = calcNewPosOfDndItemMovedWithinList( + listsSortedByPos, + result.source.index, + result.destination.index + ); try { - const movedListId = listsSortedByPos[result.source.index].id - await updateList({ listId: movedListId, data: { pos: newPos } }) + const movedListId = listsSortedByPos[result.source.index].id; + await updateList({ listId: movedListId, data: { pos: newPos } }); } catch (err) { - window.alert('Error while updating list position: ' + err.message) + window.alert("Error while updating list position: " + err.message); } - } else if (result.type === 'CARD') { - const sourceListId = result.source.droppableId - const destListId = result.destination.droppableId + } else if (result.type === "CARD") { + const sourceListId = result.source.droppableId; + const destListId = result.destination.droppableId; // TODO(matija): this is not the nicest solution, we should have a consistent naming system // for draggable ids (for lists we put prefix in the id, while for cards we use // their db id directly, because that saves us a bit of work in the further code. @@ -75,53 +83,63 @@ const MainPage = ({ user }) => { // droppable areas. This is why for lists we didn't use a db id directly, because it would // overlap with card ids. And for cards it was handy to have db id as draggable id, because // then we can easily access the data for the specific card. - const movedCardId = Number(result.draggableId) + const movedCardId = Number(result.draggableId); - const destListCardsSortedByPos = listIdToSortedCardsMap[destListId] + const destListCardsSortedByPos = listIdToSortedCardsMap[destListId]; - let newPos = undefined - if (sourceListId === destListId) { // Card got moved within the same list. + let newPos = undefined; + if (sourceListId === destListId) { + // Card got moved within the same list. newPos = calcNewPosOfDndItemMovedWithinList( - destListCardsSortedByPos, result.source.index, result.destination.index - ) - } else { // Card got inserted from another list. + destListCardsSortedByPos, + result.source.index, + result.destination.index + ); + } else { + // Card got inserted from another list. newPos = calcNewPosOfDndItemInsertedInAnotherList( - destListCardsSortedByPos, result.destination.index - ) + destListCardsSortedByPos, + result.destination.index + ); } try { - await updateCard({ cardId: movedCardId, data: { pos: newPos, listId: destListId } }) + await updateCard({ + cardId: movedCardId, + data: { pos: newPos, listId: destListId }, + }); } catch (err) { - window.alert('Error while updating card position: ' + err.message) + window.alert("Error while updating card position: " + err.message); } } else { // TODO(matija): throw error. } - } + }; return ( -
-
-

Your board

+
+
+

Your board

- + {(provided, snapshot) => ( -
- { listsSortedByPos && listIdToSortedCardsMap && - - } + {listsSortedByPos && listIdToSortedCardsMap && ( + + )} {provided.placeholder}
@@ -129,65 +147,97 @@ const MainPage = ({ user }) => { )}
- - ) -} + ); +}; const Lists = ({ lists, listIdToCardsMap }) => { - // TODO(matija): what if some of the props is empty? Although we make sure not to add it - // to DOM in that case. + // TODO(matija): what if some of the props is empty? Although we make sure not to add it + // to DOM in that case. - return lists.map((list, index) => { - return ( - - ) - }) + return lists.map((list, index) => { + return ( + + ); + }); +}; + +function getStyle(style, snapshot) { + if (snapshot.isDragging) { + // Apply tilt only when dragging + const rotate = "rotate(5deg)"; + return { + ...style, + transform: `${style.transform || ""} ${rotate}`, + transition: "transform 0.2s ease", + }; + } + + if (snapshot.isDropAnimating) { + const { moveTo, curve, duration } = snapshot.dropAnimation; + const translate = `translate(${moveTo.x}px, ${moveTo.y}px)`; + + return { + ...style, + transform: `${translate}`, + // slowing down + transition: `all ${curve} ${duration + 0.1}s`, + }; + } + + return style; } const List = ({ list, index, cards }) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false) + const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isHeaderTargetShown, setIsHeaderTargetShown] = useState(true); const [isInEditMode, setIsInEditMode] = useState(false); - const { getPosOfItemInsertedInAnotherListAfter } = useContext(PositionContext); + const { getPosOfItemInsertedInAnotherListAfter } = + useContext(PositionContext); const textAreaRef = useRef(null); const handleListNameUpdated = async (listId, newName) => { try { - await updateList({ listId, data: { name: newName } }) + await updateList({ listId, data: { name: newName } }); } catch (err) { - window.alert('Error while updating list name: ' + err.message) + window.alert("Error while updating list name: " + err.message); } finally { - setIsHeaderTargetShown(true) + setIsHeaderTargetShown(true); } - } + }; const handleAddCard = async () => { setIsInEditMode(true); setIsPopoverOpen(false); - } + }; const handleCopyList = async (listId, idx) => { try { - await createListCopy({ listId, pos: getPosOfItemInsertedInAnotherListAfter(idx) }); + await createListCopy({ + listId, + pos: getPosOfItemInsertedInAnotherListAfter(idx), + }); } catch (err) { - window.alert('Error while copying list: ' + err.message) + window.alert("Error while copying list: " + err.message); } setIsPopoverOpen(false); - } + }; const handleDeleteList = async (listId) => { try { - await deleteList({ listId }) + await deleteList({ listId }); } catch (err) { - window.alert('Error while deleting list: ' + err.message) + window.alert("Error while deleting list: " + err.message); } - setIsPopoverOpen(false) - } + setIsPopoverOpen(false); + }; const handleHeadingClicked = (e) => { setIsHeaderTargetShown(false); @@ -196,29 +246,29 @@ const List = ({ list, index, cards }) => { const ListMenu = () => { return ( -
-
-
-
- List actions -
+ + List actions + +
-
-
    +
    +
    • - +
- ) - } + ); + }; return ( { index={index} > {(provided, snapshot) => ( -
-
-
- {isHeaderTargetShown ? ( +
+
+ {isHeaderTargetShown ? (
handleHeadingClicked(e)} @@ -259,47 +312,47 @@ const List = ({ list, index, cards }) => { <> )}